さよなら和敬塾南寮

技術の話はない、プライベートな日記です

寮をみてきた

私が大学生の時に住んでいた和敬塾南寮 <わけいじゅく みなみりょう> が今期を持って学生の入塾をやめるとのことで、友人と連れ添って見学にいってきた。

結局取り壊しするんだっけ? 大事な点を確認し忘れた

和敬塾とは

公益財団法人和敬塾は、東京都文京区目白台にある男子大学生・大学院生向けの学生寮。1955年、前川製作所の創業者である前川喜作によって創設された。

文京区 目白台に位置する学生寮で、ホテル椿山荘や東京カテドラル、日本女子大が立ち並ぶ目白通り沿に広大な敷地を確保して建っている.

続きを読む

Linux 版の Sysinternals ProcDump を試す (2)

前回の続きです

hiboma.hatenadiary.jp

実装の話

本エントリでは ProcDump が コアダンプをどのように採取するかを調べていく.

(現状の) Linux ProcDump は gcore を薄くラップして扱うバイナリと理解した

ダンプの採取方法を調べる 🔍

github.com

ソースコードの量は大したことないので git clone してざっと斜め読みするといい. スレッドを積極的に使う設計は Windows な流儀なのかな?

CoreDumpWriter.c が肝 📖

ファイルの量も少ないの順番に眺めていって、CoreDumpWriter.c がコアダンプ採取の責務を負っているソースだと判別をつけた.

詳細はすっ飛ばして、以下の行を見れば gcorepopen2() で呼び出しているのが確認できる

int WriteCoreDumpInternal(struct CoreDumpWriter *self)
{

// ...

    // assemble the command
    if(sprintf(command, "gcore -o %s_%s_%s %d 2>&1", name, desc, date, pid) < 0){
        Log(error, INTERNAL_ERROR);
        Trace("WriteCoreDumpInternal: failed sprintf gcore command");        
        exit(-1);
    }


    // generate core dump for given process
    commandPipe = popen2(command, "r", &gcorePid);
    self->Config->gcorePid = gcorePid;

popen2 はシェルを fork(2) して pipe(2) で結果を受け取る関数である. PythonRuby のインタフェースを真似た感じかな? (この関数の実装も同ファイルに載っているが、冗長なので省略する)

gcore とは何ですか?

gcore の中身はシェルスクリプトで、 gdb をラップしたコマンドである. gdb をインストールすると付属してくるコマンド

🔗 gcore のソースを載せた gist

gcore を呼び出すと、結局は gdb を呼びだすことになる. ProcDump 独自の実装でコアダンプを採取しているのかと思ったが、そんなことなかった 🙃 すでに gdb で出来ることを作り直すのは、大車輪の再実装になるもんね

プロセスをどのように見張っているのか?

前回のエントリでは CPU 使用率やメモリ(RSS) を閾値にしてコアダンプをとってみたが、どういった仕組みなのだろうか?

f:id:hiboma:20190217121748g:plain

ProcDump プロセスを strace すると /proc/$pid/stats を 1秒ごとに open(2), read(2) しているスレッドがトレースできる

# 💤  1秒ブロックすることを示す

[pid  3073] futex(0x60b5bc, FUTEX_WAIT_BITSET_PRIVATE|FUTEX_CLOCK_REALTIME, 25, {1550324077, 477259191}, ffffffff) = -1 ETIMEDOUT (Connection timed out) 💤
[pid  3073] futex(0x60b590, FUTEX_WAKE_PRIVATE, 1) = 0
[pid  3073] kill(3060, SIG_0)           = 0
[pid  3073] open("/proc/3060/stat", O_RDONLY) = 3 👈
[pid  3073] fstat(3, {st_mode=S_IFREG|0444, st_size=0, ...}) = 0
[pid  3073] read(3, "3060 (a.out) S 2854 3060 2854 34"..., 1024) = 301
[pid  3073] close(3)                    = 0
[pid  3073] kill(3060, SIG_0)           = 0
[pid  3073] futex(0x60b5bc, FUTEX_WAIT_BITSET_PRIVATE|FUTEX_CLOCK_REALTIME, 27, {1550324078, 479654480}, ffffffff) = -1 ETIMEDOUT (Connection timed out) 💤
[pid  3073] futex(0x60b590, FUTEX_WAKE_PRIVATE, 1) = 0
[pid  3073] kill(3060, SIG_0)           = 0
[pid  3073] open("/proc/3060/stat", O_RDONLY) = 3 👈
[pid  3073] fstat(3, {st_mode=S_IFREG|0444, st_size=0, ...}) = 0
[pid  3073] read(3, "3060 (a.out) S 2854 3060 2854 34"..., 1024) = 301
[pid  3073] close(3)                    = 0
[pid  3073] kill(3060, SIG_0)           = 0
[pid  3073] futex(0x60b5bc, FUTEX_WAIT_BITSET_PRIVATE|FUTEX_CLOCK_REALTIME, 29, {1550324079, 503662811}, ffffffff) = -1 ETIMEDOUT (Connection timed out) 💤
[pid  3073] futex(0x60b590, FUTEX_WAKE_PRIVATE, 1) = 0
[pid  3073] kill(3060, SIG_0)           = 0
[pid  3073] open("/proc/3060/stat", O_RDONLY) = 3 👈
[pid  3073] fstat(3, {st_mode=S_IFREG|0444, st_size=0, ...}) = 0
[pid  3073] read(3, "3060 (a.out) S 2854 3060 2854 34"..., 1024) = 301
[pid  3073] close(3)                    = 0
[pid  3073] kill(3060, SIG_0)           = 0

ここらの実装は TriggerThreadProcs.c に書いてある

github.com

初見で「phtread を抽象化して扱ってるし、難しいのかな ...? 」と身構えてしまったが、詳細を読んでいくと思ったよりも素朴な実装 + 設計である.

感想

/proc/$pid/stat や cgroup の値を閾値 最新のメトリクスだと PSI なんかを見張ったりすると、より精緻なトリガーを作れたりするのかなと思った

udzura.hatenablog.jp

コアダンプ採取のコードは gcore だった

  • バックトレースだけ獲れりゃ十分なケースもあると思うので gstack に置き換えられるといいかなぁ
  • プロセスのメトリクスをとってコマンドをトリガーするコードと、コアダンプを採取するコードとを分離すると汎用的なツールに消化できそうだが ( それ、monit じゃね??? とか ... )

Linux 版の Sysinternals ProcDump を試す (1)

先のエントリで書いたように最近は Windows も触っていて 主に Sysinternals ツールを使って Windows 探検をしている

hiboma.hatenadiary.jp

その流れで ProcDump の使い方を調べていた.

f:id:hiboma:20190217165143p:plain

Sysinternals の ProcDump

technet.microsoft.com

Windows 版の詳細は上記のリンクを辿って自分でご覧になって欲しい。その中で、CPU 使用率やメモリ使用量(コミットチャージ) をコアダンプ生成のトリガーにできるのが目に留まった

-c 
    CPU しきい値を指定します。このしきい値に達すると、プロセスのダンプが作成されます。

-m 
    メモリ コミットのしきい値を MB 単位で指定します。このしきい値に達すると、プロセスのダンプが作成されます。

gdb もこんな風に扱えたら便利かな〜??? と思っていたところ、ふと Linux 版の存在があるのを思い出したのだった!

Linux 版 🐧

下記のリポジトリで ProcDump の Linux 版が公開されている

github.com

ディストリビューション向けのパッケージが配布されており、インストールの手順はREADME.md に書かれている

CLI インタフェースを調べる

手順通りに Ubuntu Xenial にインストールして、man を読み何ができるのかをざっと調べる

man(8)                                                                                     procdump manpage                                                                                     man(8)

NAME
       procdump - generate coredumps based off performance triggers.

SYNOPSIS
       procdump [OPTIONS...] TARGET
             -C   CPU threshold at which to create a coredump of the process from 0 to 100 * nCPU
             -c   CPU threshold below which to create a coredump of the process from 0 to 100 * nCPU
             -M   Memory commit threshold in MB at which to create a coredump
             -m   Trigger when memory commit drops below specified MB value
             -n   Number of dumps to write before exiting
             -s   Consecutive seconds before dump is written (default is 10)
         TARGET must be exactly one of these:
             -p   pid of the process

DESCRIPTION
       procdump  is  a  Linux reimagining of the class ProcDump tool from the Sysinternals suite of tools for Windows. Procdump provides a convenient way for Linux developers to create core dumps of
       their application based on performance triggers.

1.0.1                                                                                         12/18/2017                                                                                        man(8)

Linux 版でも CPU使用率 やメモリ使用量をトリガーにしてコアダンプできる。コアダンプを採取する回数を指定できたり、採取の間隔を指定できる

アーキテクチャに依存しないエッセンスとなるオプションだけを実装した感じで、Windows 版よりだいぶ簡素かな?

検証(1): メモリの使用量を閾値にしてコアダンプを採取流 🔍

以下のようなテストプロセスを起動して、ProcDump でコアダンプを採取する

  1. 100MB の仮想メモリmalloc(3) するa
  2. 1 の仮想メモリでマイナーページフォルトを起こす
  3. 1 の仮想メモリを free(3) する
  4. 3秒スリープする
  5. 1-4 を3回繰り返す

さて、上記のプロセスを ProcDump したのを GIF 動画でにした

f:id:hiboma:20190217121748g:plain

コアダンプが合計3回採取されているのがわかるだろうか?

ソース 📖

上記のデモで使ったソースは下記の通り

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

void malloc_and_free() { 
    size_t size = 100 * 1024 * 1024;

    char *p = malloc(size);
    if (p == NULL) { 
        perror("failed to malloc");
        exit(1);
    }

    // memset でもええねん
    for (size_t i = 0; i < size; i++)  { 
        p[i] = '@';
    }
   
    sleep(3);
    free(p);
}

int main() { 
    sleep(5);
    malloc_and_free();
    malloc_and_free();
    malloc_and_free();
}

注意: ProcDump の動作を確認するためのコードなので、コアダンプを採取しても面白いことはなにもない

使ってみよう(2): CPU 使用率を閾値にする 🔍

CPU 使用率を監視してダンプを採取する GIF 動画が ProcDump のリポジトリにのっているので、引用として掲載する

f:id:hiboma:20190217121836g:plain

Source: GitHub - Microsoft/ProcDump-for-Linux: A Linux version of the ProcDump Sysinternals tool

採取したコアダンプを調査することで CPU 使用率が高い時のバックトレースを調べていくことができるだろう

ユースケース

網を張って気長に待つようなデバッグに向いてるだろう

長時間実行した際に特定の負荷のパターンでうごくプロセスがいるとして、そのようなケースで作業を半自動化できて便利だろう. ( 例: たまーに CPU時間が10数秒スパイクするプロセスがいて調査したいが、再現するタイミングがよくわからない )

Windows Sysinternals徹底解説 改訂新版』でもそのようなトラブルシューティングの例を出している

まとめ

  • ProcDump を使うと CPU使用率やメモリ使用量をトリガーにしてコアダンプをとれる
  • プロセスのリソースを見張ってトリガーにする単体のコマンドがあれば、コアダンプの採取以外にも使えて便利な気がする
  • コアダンプのサイズは大きくなりがちなので、バックトレースだけ取れたら十分かなという気もする

次回のエントリでは Procump の実装を見ていく

インサイド Windows 第七版、Windows Sysinternals 徹底解説 📚 、その他エッセイ

会社 で Windows Surface Go を支給してもらったので Windows 筋トレをしている

私の Windows レベル

腰を据えて Windows を触るのは WIndows XP を扱って以来で 10数年ぶり (2011年位に会社から 1-2ヶ月間だけ貸与してもらったことはあるけど).

f:id:hiboma:20190214235203p:plain

Surface Go を手に入れるまではコントロールパネルを開く方法も分からないくらいに退化していた

鈍器で筋トレだ 📘

ということで『インサイド Windows 第七版』と『Windows Sysinternals 徹底解説』を買った

インサイドWindows 第7版 上 システムアーキテクチャ、プロセス、スレッド、メモリ管理、他 (マイクロソフト公式解説書)

インサイドWindows 第7版 上 システムアーキテクチャ、プロセス、スレッド、メモリ管理、他 (マイクロソフト公式解説書)

Windows Sysinternals徹底解説 改訂新版 (マイクロソフト公式解説書)

Windows Sysinternals徹底解説 改訂新版 (マイクロソフト公式解説書)

低レイヤーの話が高密度で書き記されているが、Linux カーネルの問題に取り組んできた際に身につけた知識や経験も手伝って「Windows っておもろい OS 」なんだなと、たくさんの発見をしながら読みすすめられている.

Linux と同じように捉えていいモデルや概念や設計があれば、全く異種や未知のものもあり、Windows のことをほんと表層的にしか理解できてなかったのだと痛感している.

共通点を知る 🔍

VMMap でプロセスのメモリレイアウトを見るとこんなんになる

f:id:hiboma:20190215011716p:plain

VMMap を使うと Linux の pmap と 似た出力 を得られる.

似た と書いたが、プロセスのメモリレイアウトは CPU (x86アーキテクチャ) を反映している / 密結合するポイントのせいか、WindowsLinux とで大きくは変わらないようだ. フィールド名の差異はあれど pmap の出力を理解してれば VMMap も難なく理解できる (その逆も然りだろう).

違いを知る 🔎

Process Explorer でプロセス一覧をみる

f:id:hiboma:20190215012754p:plain

プロセスモデルは抽象化のアプローチが Linux とは全然違うもんなんだな〜 と素朴な感想を持っている. ハードから離れたレイヤでの抽象化は OS の個性が輝くところなのだろう

📕📘

このように Windows の本を読みながら Linux と比較しながら進めている.

known-unknown と技術書の話

ここからは全く別の話題

技術の勉強に励む際に 入門書を手にしてボトムアップで固めて攻めていくのは一つの勉強手段だ. 別の手段として、いろいろレベル感をすっ飛ばした書籍にチャレンジしてトップダウンで進める方法あるだろう. unknown-unknown の森に飛び込み known-unknown の中で迷子になりながら鍛えていく方法.

pyama.fun

その中でknown unknownという概念が紹介されている。それは、システムチューニングの局面においてknown-known,known-unknown,unknown-unknownという分類で説明されている。

さらに引用

known-knownは知っていることを知っていること、例えばtopコマンドでプロセスごとのCPUの使用率を見れることを知っているし、見たことがある。 known-unknownは知らないことを知っていること。topコマンドを知っているけど、使ったことはない(観測してない) unknown-unknownは知らないことを知らないこと。これはtopコマンドを知らないこと。

もう一個引用

これを少しでもknown-unknownにするためにはひたすら本を読むとか、識者に教えてもらうとか、インターネットで調べるとかそういう手段になると思うのだけど (...)

私の話: Linux カーネル本の話

自分の過去を振り返るならば、『詳細 Linux カーネル』を手にしたのは 10年も前のことだが、書籍を買った当初はどこを読んでもまったくもって何も理解ができずに圧倒されてしまった.

詳解 Linuxカーネル 第3版

詳解 Linuxカーネル 第3版

known-unknown の山 (鈍器) がそこに (物理的に) 存在することは確実に理解はした. ( なんで買ったんだこの本??? )

樹海で迷子になる

そもそも C言語を全く書けないのにカーネル本を手にしたのがすっ飛ばし過ぎてるようにも思い、ゆっくりと時間をかけて、C言語の入門書を読んだり、『詳解 UNIX プログラミング』にチャレンジしたり、途中で全く興味を失ったり、何かの機会に興味が再発したり、また飽きたり ... と紆余曲折を経ながらも少しずつ known-unknownknown-known に変えることができた.

現在では 会社のお仕事の場面でも知識 + 経験 + スキルとして生かせるレベルまで書籍の内容を吸収したと思う ( 未だコードは全然かけないけど 🙃 )

過去のお話の注意点

このように過去の話をふりかえるとバイアスが強烈にかかり、美化して話がちだ.

失敗している例もあって、技術的に理解が及ばないまま背伸びして買った書籍で内容を全然モノにしないまま挫折したのもたくさんある. 私はグラフィカルなレイヤを扱うのはどうも苦手で、そういったレイヤの技術書はどれもこれも途中で放り投げてしまった ( 懐かしの ActionScript3 や Processing や iOSアプリや ... )

何の技術がどうやって自分の強み・興味・自信のコアとして定着するかは、短い時間の中では推し量れないもんだね

抽象ソケットアドレス abstract socket address

抽象ソケットアドレス abstract socket address について 2011年にエントリを書いていた

d.hatena.ne.jp

しばらく存在を忘れていたのだけど、社内 slack に話題が上がって思い出した。以下のような UNXI ソケットの亜種である。

abstract (抽象): 抽象ソケットアドレスは、 sun_path[0] がヌルバイト ('\0') であることから区別できる。 sun_path の残りの全バイトによりソケットの「名前」が定義される (名前中のヌルバイトには特別な意味はない)。 この名前はファイルシステムのパス名とは何の関係もない。 この名前空間におけるソケットのアドレスは、 sun_path の残りのバイトで表される。 getsockname(2), getpeername(2), accept(2) が抽象ソケットのアドレスを返す際には、その長さは sizeof(struct sockaddr_un) であり、 sun_path に抽象名前空間の名前が格納される。 ソケットの抽象名前空間Linux による拡張であり、移植性はない。

Man page of UNIX

straec でトレースした場合

strace では @ 付きで表示される

vagrant@vagrant:~$ strace -econnect socat - abstract-connect:"hoge"
connect(5, {sa_family=AF_LOCAL, sun_path=@"hoge"}, 7) = -1 ECONNREFUSED (Connection refused)

うっかり @ ナルト を見落としてしまいそう

strace の実装

sockaddr.c に表示のロジックが書いてあります

static void
print_sockaddr_data_un(const void *const buf, const int addrlen)
{
        const struct sockaddr_un *const sa_un = buf;
        const int un_len = addrlen > (int) sizeof(*sa_un)
                           ? (int) sizeof(*sa_un) : addrlen;
        const int path_len = un_len - SIZEOF_SA_FAMILY;

        tprints("sun_path="); 
        if (sa_un->sun_path[0]) {
                print_quoted_string(sa_un->sun_path, path_len + 1,
                                    QUOTE_0_TERMINATED);
        } else {
                tprints("@"); 👈
                print_quoted_string(sa_un->sun_path + 1, path_len - 1, 0); 
        }   
}

ユースケース

web アプリケーションを作ってる時は特に必要とされるユースケースを見出せない。特殊なミドルウェアを実装する際に便利な使い方ができそう ( 社内 slack では D-Bbus の話題としてとりあげられていた ).

ファイルシステムに依存しないソケットとして扱える一方、セキュリティモデルはどう扱うべきなのだろうか

【まとめ】CentOS7 で glibc をアップデート後、 mod_php + httpd の logrotate (reload ) が失敗する: /lib64/libresolv.so.2: symbol __h_errno, version GLIBC_PRIVATE not defined in file libc.so.6 with link time reference

3つのエントリで問題を追いかけたのでまとめ

追いかけていた問題

httpd + mod_phpglibc をアップデート後、 logrotate 時に以下のログを出して失敗する問題を追っていた

Graceful restart requested, doing restart httpd: Syntax error on line 39 of /etc/httpd/conf/httpd.conf: Syntax error on line 1 of /etc/httpd/conf.modules.d/php7.load: /lib64/libresolv.so.2: symbol __h_errno, version GLIBC_PRIVATE not defined in file libc.so.6 with link time reference

hiboma.hatenadiary.jp

1つめのエントリで、再現の手順とコードをまとめた

hiboma.hatenadiary.jp

2つめのエントリで、原因を分析した

hiboma.hatenadiary.jp

3つめのエントリで、問題を起こす glibc のバージョンやパッチ・コミットログを追った

まとめと感想

  • コンテナ環境に移行することで glibc のアップデートでこのような問題にも遭遇しなくなり、ナレッジの価値が薄れていくかもしれない
  • 一方で、コンテナを提供するレイヤを扱う人や、依然として非コンテナ ( VM や オンプレーサーバー) で動き続ける環境では 上記にまとめた内容やデバッグ方法が何らかの形で役に立ってくれるとよいなと思ってる

【Vol.3 変更履歴の追跡】CentOS7 で glibc をアップデート後、 mod_php + httpd の logrotate (reload ) が失敗する: /lib64/libresolv.so.2: symbol __h_errno, version GLIBC_PRIVATE not defined in file libc.so.6 with link time reference

hiboma.hatenadiary.jp

hiboma.hatenadiary.jp

上記エントリの続きです

前回まで調べていたこと

  • CentOS7 で glibc をアップデート後、 mod_php + httpd の logrotate (reload ) が失敗するのを調べていた
  • 失敗する際に以下のログを出す

Graceful restart requested, doing restart httpd: Syntax error on line 39 of /etc/httpd/conf/httpd.conf: Syntax error on line 1 of /etc/httpd/conf.modules.d/php7.load: /lib64/libresolv.so.2: symbol __h_errno, version GLIBC_PRIVATE not defined in file libc.so.6 with link time reference

  • 前回のエントリまでで再現を取ることができた

本エントリのサマリ

前回のまとめに「glibc をアップデートすると再現する」とさらっと書いているが、「glibc をどのバージョンから、どのバージョンにアップデートすると再現するの?」 をさらに調べて本エントリにまとめている。さらに __h_errno の変更がどういった経緯で入ったのかも調べた

  • 問題となる __h_errno シンボルは、 CentOS 7.5.1804 でリリースされた glibc-2.17-222.el7.x86_64.rpm から登場する
  • それ以前は h_errno というシンボルだっただった
  • h_errno --> __h_errno への変更は、 glibc upstream が POSIX 2008 の変更に追随するためのパッチを取り込んだことによる

というサマリです

1. 調査: RPM をダウンロード, 展開, シンボルを調べる

http://mirror.centos.orghttp://vault.centos.org から RPM をダウンロードして展開、libc.so.6 のシンボルを地道に見ていく作業で調べた

cd /tmp
mkdir glibc-2.17-196
cd    glibc-2.17-196
wget http://vault.centos.org/7.4.1708/os/x86_64/Packages/glibc-2.17-196.el7.x86_64.rpm
rpm2cpio glibc-2.17-196.el7.x86_64.rpm  | cpio -idv
nm ./lib64/libc.so.6 | grep h_errno$

CentOS7.4.1708: glibc-2.17-196.el7.x86_64.rpm

$ nm ./lib64/libc.so.6 | grep h_errno$
000000000000007c b __libc_h_errno
000000000000007c B h_errno

http://vault.centos.org/7.4.1708/os/x86_64/Packages/glibc-2.17-196.el7.x86_64.rpm

CentOS7.4.1708: glibc-2.17-196.el7_4.2.x86_64.rpm

$ nm ./lib64/libc.so.6 | grep h_errno$
000000000000007c b __libc_h_errno
000000000000007c B h_errno

http://vault.centos.org/7.4.1708/updates/x86_64/Packages/glibc-2.17-196.el7_4.2.x86_64.rpm

CentOS7.5.1804: glibc-2.17-222.el7.x86_64.rpm

$ nm ./lib64/libc.so.6 | grep h_errno$
000000000000008c B __h_errno 👈
000000000000008c b __libc_h_errno

これだー!!!!!

http://vault.centos.org/7.5.1804/os/x86_64/Packages/glibc-2.17-222.el7.x86_64.rpm

表にまとめてみましょう

リリース バージョン __h_errno の有無
7.4.1708 glibc-2.17-196.el7.x86_64.rpm x
7.4.1708 glibc-2.17-196.el7_4.2.x86_64.rpm x
7.5.1804 glibc-2.17-222.el7.x86_64.rpm 👈 o
7.6.1810 glibc-2.17-260.el7.x86_64 o
7.6.1810 glibc-2.17-260.el7_6.3.x86_64 o

👈 をつけた CentOS7.5.1804 glibc-2.17-222.el7.x86_64.rpm から __h_errno が登場する

__h_errno の有無 が x から o の バージョンにアップデートすると、このエントリで追いかけている問題を踏んでしまうと絞り込める (実際には全部のケースで試していないので推論です)

2. 深追い: __h_errno に変更したパッチを探す

CentOS 7.5.1804 glibc-2.17-222.el7 のどういったパッチが入ったことで h_errno__h_errno に変更されたのかを調べていく

SRPM を展開して探す

古い SRPM は vault.centos.org から探し出します

$ wget https://vault.centos.org/7.5.1804/os/Source/SPackages/glibc-2.17-222.el7.src.rpm
$ rpm -ivh glibc-2.17-222.el7.src.rpm

で、適当に grep して以下を見つけました

~/rpmbuild/SOURCES/glibc-rh677316-h_errno.patch

パッチの中身は下記の通りです

Partial backport of this upstream commit:

commit 9acacaa02f3b75fddc07a56f3d848df45281a5de
Author: Joseph Myers <joseph@codesourcery.com>
Date:   Fri Jun 12 10:10:18 2015 +0000

    Fix h_errno namespace (bug 18520).

The linknamespace test changes in conform/Makefile are not included here
because glibc 2.17 did not have these tests.

diff --git a/include/netdb.h b/include/netdb.h
index b6d7b90bbf8abd2e..6a6dca9ef57aaa37 100644
--- a/include/netdb.h
+++ b/include/netdb.h
@@ -8,7 +8,7 @@
 #  if IS_IN (libc)
 #   define h_errno __libc_h_errno
 #  else
-#   define h_errno h_errno    /* For #ifndef h_errno tests.  */
+#   define h_errno __h_errno
 #  endif
 extern __thread int h_errno attribute_tls_model_ie;
 # endif /* IS_IN_LIB */
diff --git a/inet/herrno.c b/inet/herrno.c
index 1802d0e00563839a..0cd84445190728b3 100644
--- a/inet/herrno.c
+++ b/inet/herrno.c
@@ -24,7 +24,7 @@
 /* We need to have the error status variable of the resolver
    accessible in the libc.  */
 
-__thread int h_errno;
-extern __thread int __libc_h_errno __attribute__ ((alias ("h_errno")))
+__thread int __h_errno;
+extern __thread int __libc_h_errno __attribute__ ((alias ("__h_errno")))
   attribute_hidden;
 #define h_errno __libc_h_errno
diff --git a/nptl/herrno.c b/nptl/herrno.c
index c0488e4f6754873f..5056e3df88211123 100644
--- a/nptl/herrno.c
+++ b/nptl/herrno.c
@@ -23,12 +23,12 @@
 
 /* We need to have the error status variable of the resolver
    accessible in the libc.  */
-extern __thread int h_errno;
+extern __thread int __h_errno;
 
 
 /* When threaded, h_errno may be a per-thread variable.  */
 int *
 __h_errno_location (void)
 {
-  return &h_errno;
+  return &__h_errno;
 }
diff --git a/resolv/Versions b/resolv/Versions
index 93faf1e2f5faac79..152ef3f68f9a8b48 100644
--- a/resolv/Versions
+++ b/resolv/Versions
@@ -26,7 +26,7 @@ libc {
   GLIBC_PRIVATE {
     __gai_sigqueue;
 
-    h_errno; __resp;
+    __h_errno; __resp;
 
     __res_maybe_init; __res_iclose;
   }

コミットログが短い!!!1

3. オリジナナルのコミット/チケット/issue を探す

SRPM に含まれる libc-rh677316-h_errno.patch は、コミットログの詳細を削っているため変更の理由や文脈がよくわからない. 地道にググって探し出します

Fix h_errno namespace (bug 18520). を手がかりに調べいくと下記がヒットする

上記からコミットログを転載する

 The 2008 edition of POSIX removed h_errno, but some functions still
bring in references to the h_errno external symbol.  As this symbol is
not a part of the public ABI (only __h_errno_location is), this patch
fixes this by renaming the GLIBC_PRIVATE TLS symbol to __h_errno.

Tested for x86_64 and x86 (testsuite, and comparison of installed
shared libraries).  Disassembly of all shared libraries using h_errno
changes because of the renaming (and changes to associated TLS / GOT
offsets in some cases); disassembly of libpthread on x86_64 changes
more substantially because the enlargement of .dynsym affects
subsequent addresses.

2015-06-11  Joseph Myers  

    [BZ #18520]
    * inet/herrno.c (h_errno): Rename to __h_errno.
    (__libc_h_errno): Define as alias of __h_errno not h_errno.
    * include/netdb.h [IS_IN_LIB && !IS_IN (libc)] (h_errno): Define
    to __h_errno instead of h_errno.
    * nptl/herrno.c (h_errno): Rename to __h_errno.
    (__h_errno_location): Refer to __h_errno not h_errno.
    * resolv/Versions (h_errno): Rename to __h_errno.
    * conform/Makefile (test-xfail-XOPEN2K8/grp.h/linknamespace):
    Remove variable.
    (test-xfail-XOPEN2K8/pwd.h/linknamespace): Likewise.

なるほど? Google 翻訳を載せておきます

POSIXの2008年版はh_errnoを削除しました、しかし、いくつかの関数はまだh_errno外部シンボルへの参照を持ち込みます。このシンボルはパブリックABIの一部ではないため(h_errno_locationのみ)、GLIBC_PRIVATE TLSシンボルの名前をh_errnoに変更することでこれを修正します。

x86_64とx86用にテスト済み(testsuite、およびインストール済み共有ライブラリの比較)。 h_errnoを使用したすべての共有ライブラリの逆アセンブリは、名前の変更(および場合によっては関連するTLS / GOTオフセットの変更)によって変更されます。 x86_64でのlibpthreadの逆アセンブリは、.dynsymの拡大が後続のアドレスに影響を与えるため、より大幅に変更されます。

glibc upstream が POSIX 2008 に追随するための変更だったのだと理解した

GLIBC_PRIVATE TLS とは?

GLIBC_PRIVATE についてはここを読んだ

https://groups.google.com/forum/#!topic/uk.comp.os.linux/PX3rstGtZRQ

GLIBC_PRIVATE is the version given to symbols exported by the GNU C library only for the purpose of communicating with other parts of the library.

関数のスコープを glibc 内でのみに閉じる用途で使うものぽい. あと TLSThread Locl Storage かなぁ.

readelf で確認する

$ readelf -a ./lib64/libc.so.6  | grep h_errno
  1483: 000000000000007c     4 TLS     GLOBAL DEFAULT   23 h_errno@@GLIBC_PRIVATE
  1928: 0000000000110390    17 FUNC    GLOBAL DEFAULT   12 __h_errno_location@@GLIBC_2.2.5
  3875: 000000000000007c     4 TLS     LOCAL  DEFAULT   23 __libc_h_errno
  5135: 0000000000110390    17 FUNC    LOCAL  DEFAULT   12 __GI___h_errno_location
  6483: 0000000000110390    17 FUNC    GLOBAL DEFAULT   12 __h_errno_location
  7573: 000000000000007c     4 TLS     GLOBAL DEFAULT   23 h_errno
$ readelf -a ./lib64/libc.so.6  | grep h_errno
  1495: 000000000000008c     4 TLS     GLOBAL DEFAULT   23 h_errno@GLIBC_PRIVATE
  1859: 000000000000008c     4 TLS     GLOBAL DEFAULT   23 __h_errno@@GLIBC_PRIVATE
  1946: 0000000000118cf0    17 FUNC    GLOBAL DEFAULT   12 __h_errno_location@@GLIBC_2.2.5
  3925: 000000000000008c     4 TLS     LOCAL  DEFAULT   23 __libc_h_errno
  5213: 0000000000118cf0    17 FUNC    LOCAL  DEFAULT   12 __GI___h_errno_location
  6580: 0000000000118cf0    17 FUNC    GLOBAL DEFAULT   12 __h_errno_location
  7075: 000000000000008c     4 TLS     GLOBAL DEFAULT   23 __h_errno
  7147: 000000000000008c     4 TLS     GLOBAL DEFAULT   23 h_errno@GLIBC_PRIVATE 👈

4. POSIX 2008 とは何なのか?

さて、先のコミットログで 2008 edition of POSIX についての記述があったのでさらに調べる

まず見つけたのは gethostbyname(3) の man ページである

POSIX.1-2008 では gethostbyname(), gethostbyaddr(), h_errno の仕様が削除されている。 代わりに、 getaddrinfo(3) と getnameinfo(3) の使用が推奨されている。

getaddrinfo() 関数は、 gethostbyname(3) と getservbyname(3) の機能をまとめて一つのインターフェースにしたものであるが、 これらの関数と違い、 getaddrinfo() はリエントラントであり、 getaddrinfo() を使うことでプログラムは IPv4IPv6 の違いに関する依存関係を なくすことができる。

https://linuxjm.osdn.jp/html/LDP_man-pages/man3/gethostbyname.3.html

さらにググっていくと POSIX のページと思われる http://pubs.opengroup.org/onlinepubs/9699919799.2008edition/ を見つけた

h_errno

Applications are recommended not to use this error return code. Previously it was set by the gethostbyaddr() and gethostbyname() functions.

POSIX で変更が入った理由が良く分からないが、IPv4/IPv6 の話周りを整えるためなんだろうか?

まとめ

  • glibc のパッチまで踏み込んで追いかけた
  • glibc のメンテナさんはは 上位のアプリケーションで問題が出ないように実装を進めてくれているのだろうが、今回のように不具合として踏んでしまうケースもあるのだろうなぁ
  • glibcPOSIX の変更なんぞを普段全く気にすることなく生きている

参考