上記エントリの続きです
前回まで調べていたこと
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.org と http://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). を手がかりに調べいくと下記がヒットする
- https://sourceware.org/bugzilla/show_bug.cgi?format=multiple&id=18520
- http://thread.gmane.org/gmane.comp.lib.glibc.alpha/52266
上記からコミットログを転載する
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 内でのみに閉じる用途で使うものぽい. あと TLS は Thread 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() を使うことでプログラムは IPv4 と IPv6 の違いに関する依存関係を なくすことができる。
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 のメンテナさんはは 上位のアプリケーションで問題が出ないように実装を進めてくれているのだろうが、今回のように不具合として踏んでしまうケースもあるのだろうなぁ
- glibc や POSIX の変更なんぞを普段全く気にすることなく生きている