.note.ABI-tagとはなにか

執筆者 : 箕浦 真


.note.ABI-tag?

今回はカーネルからは離れて番外編。前回記事を書くために、いくつかの実行ファイルでreadelfを実行した。気づいたのは、実行ファイルにある.note.ABI-tagセクション。

c8$ cat /etc/centos-release
CentOS Linux release 8.2.2004 (Core) 
c8$ readelf -n /bin/cat

Displaying notes found in: .note.gnu.property
  Owner                 Data size       Description
  GNU                  0x00000010       NT_GNU_PROPERTY_TYPE_0
      Properties: x86 feature: IBT, SHSTK

Displaying notes found in: .note.ABI-tag
  Owner                 Data size       Description
  GNU                  0x00000010       NT_GNU_ABI_TAG (ABI version tag)
    OS: Linux, ABI: 3.2.0

Displaying notes found in: .note.gnu.build-id
  Owner                 Data size       Description
  GNU                  0x00000014       NT_GNU_BUILD_ID (unique build ID bitstring)
    Build ID: 007217a03b8c26e9ea643d55aa19c19b120143b4

Displaying notes found in: .gnu.build.attributes
  Owner                 Data size       Description
  GA$<version>3a1      0x00000010       OPEN
    Applies to region from 0x14a8 to 0x278f
(以下略)
ub$ head -2 /etc/os-release 
NAME="Ubuntu"
VERSION="18.04.5 LTS (Bionic Beaver)"
ub$ readelf -n /bin/cat

Displaying notes found in: .note.ABI-tag
  Owner                 Data size       Description
  GNU                  0x00000010       NT_GNU_ABI_TAG (ABI version tag)
    OS: Linux, ABI: 3.2.0

Displaying notes found in: .note.gnu.build-id
  Owner                 Data size       Description
  GNU                  0x00000014       NT_GNU_BUILD_ID (unique build ID bitstring)
    Build ID: 747e524bc20d33ce25ed4aea108e3025e5c3b78f

Linux, ABI: 3.2.0という文字列からはわかりにくいが、CentOS 7環境などで見てみると、おや? という感じになる。

c7$ cat /etc/centos-release
CentOS Linux release 7.8.2003 (Core)
c7$ readelf -n /bin/cat

Displaying notes found at file offset 0x00000254 with length 0x00000020:
  Owner                 Data size       Description
  GNU                  0x00000010       NT_GNU_ABI_TAG (ABI version tag)
    OS: Linux, ABI: 2.6.32

Displaying notes found at file offset 0x00000274 with length 0x00000024:
  Owner                 Data size       Description
  GNU                  0x00000014       NT_GNU_BUILD_ID (unique build ID bitstring)
    Build ID: b97cc8358edb123e8a9e21390f30924fe66a5e55

2.6.32って、Linuxのバージョンなんでは。ちなみに、CentOS 6では2.6.12、ubuntu 18.04と20.04では3.2.0になる。でも、2.6.12、2.6.32、3.2.0の間で何かABI面で大きな変更があった記憶はない。いくつかシステムコールの追加はあったと思うけど。システムコールの呼び出しに、機械語のint命令を使うか、syscall命令を使うか、sysenter命令を使うか、というような話はもっと昔だったと思うしなぁ。

という訳で、この.note.ABI-tagについて調べてみた。

ABI?

ABIはApplication Binary Interfaceの略だ。システムコールの呼び出し方、仮想メモリ空間の使い方、レジスタの使い方などが規定されていて、ELFヘッダにも出てくる。

c8$ readelf -h /bin/ls
ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              DYN (Shared object file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x5e00
  Start of program headers:          64 (bytes into file)
  Start of section headers:          141384 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         10
  Size of section headers:           64 (bytes)
  Number of section headers:         31
  Section header string table index: 30

Linuxなのに、OS/ABI項は「UNIX - System V」。前回スルーしてしまったが、実はELFファイルではどのOSでもどういうわけか「UNIX - System V」だ。

n9$ uname; uname -r
NetBSD
9.0
n9$ readelf -h /bin/ls
ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              DYN (Shared object file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x1510
  Start of program headers:          64 (bytes into file)
  Start of section headers:          33504 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         8
  Size of section headers:           64 (bytes)
  Number of section headers:         33
  Section header string table index: 32
n9$ readelf -n /bin/ls

Displaying notes found in: .note.netbsd.ident
  Owner                 Data size       Description
  NetBSD                0x00000004      IDENT 900000000 (9.0.0)

Displaying notes found in: .note.netbsd.pax
  Owner                 Data size       Description
  NetBSD                0x00000004      PaX <>
s10$ uname ; uname -r
SunOS
5.10
s10$ greadelf -h /bin/ls
ELF Header:
  Magic:   7f 45 4c 46 01 02 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF32
  Data:                              2's complement, big endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           Sparc
  Version:                           0x1
  Entry point address:               0x10fc0
  Start of program headers:          52 (bytes into file)
  Start of section headers:          26460 (bytes into file)
  Flags:                             0x0
  Size of this header:               52 (bytes)
  Size of program headers:           32 (bytes)
  Number of program headers:         6
  Size of section headers:           40 (bytes)
  Number of section headers:         23
  Section header string table index: 21
s10$ /usr/sfw/bin/greadelf -n /bin/ls

ELF導入初期、私はNetBSDの開発に参加していたが、実行ファイルがNetBSDネイティブなのか、Linux向けなのか、FreeBSD向けなのか (NetBSDはLinuxやFreeBSDのコマンドを実行するバイナリエミュレーションの機能を持っている) 判別するのは、割とめんどくさいゾ、という議論があったのを記憶している。Linux向けかどうかを判定するのには、この.note.ABI-tagを利用しているようだ。ちなみに、ここでは.note.ABI-tagというセクション名は見ないで、中身で判断している。

だれか確認しているの?

Linuxもカーネルが確認しているのだろうか。

CentOS 6→7→8 (ということは、RHEL 6→7→8) と、このバージョンが上がっているということは、なんらかのチェックの目的で使われている可能性が高いと考えた。簡単なのは、「3.2.0」を謳うCentOS 8のバイナリを、2.6.32ベースのカーネルを使うCentOS 6環境に持っていくことだろう。とはいえ、あまり複雑なプログラムはlibcがうまくリンクできないかもしれない。Hello worldくらいなら動くだろうか。

c8$ cat test.c
#include <stdio.h>
int main(void) {
        printf("Hi, how are you?\n");
        return 0;
}
c8$ make test
cc     test.c   -o test
c8$ readelf -n test

Displaying notes found in: .note.ABI-tag
  Owner                 Data size       Description
  GNU                  0x00000010       NT_GNU_ABI_TAG (ABI version tag)
    OS: Linux, ABI: 3.2.0

Displaying notes found in: .note.gnu.build-id
  Owner                 Data size       Description
  GNU                  0x00000014       NT_GNU_BUILD_ID (unique build ID bitstring)
    Build ID: 99d3ae3b669059d6c9b9b916522d4a742772dc93

Displaying notes found in: .gnu.build.attributes
  Owner                 Data size       Description
(以下略)
c8$ scp test c6:
test                                                                                  100%   12KB   7.3MB/s   00:00    
c6$ cat /etc/centos-release 
CentOS release 6.9 (Final)
c6$ file test
test: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 3.2.0, not stripped
c6$ ./test
Hi, how are you?
c6$ uname -r
2.6.32-696.1.1.el6.x86_64

お、fileコマンドでABI tagのようなものが出ている。しかし、何事もなく実行されてしまった! ムーン、誰もチェックしていないのか、それとも3.2.0とか2.6.32とかは、いかにもLinuxのバージョンに見えて、そうではないのか。謎は深まった。

「OS: Linux」の部分は見ているのだろうか。試しにNetBSDでtest.cをコンパイルして、これをCentOSで実行してみよう。NetBSDとCentOSとでは、共有ライブラリやダイナミックリンカのパスが違うので、実行するところまではいけないと思われる。そこで、スタティックリンクにしてみよう。

n9$ cc -static test.c -o test-netbsd
n9$ scp test-netbsd c8:
c8$ ./test-netbsd 
Segmentation fault (core dumped)

実行されてしまった! 誤動作は当然だ。ちなみにこの逆にCentOSで作った (8ではスタティックリンクできないので、7で作って) hello world実行ファイルをNetBSDで実行すると、バイナリエミュレーションが働いて正常に実行される。

誰がつけているの?

このよくわからないABI tag、誰がつけているのかがわかれば、なんのためにつけられているのかわかるかもしれない。まず、どの段階でつくのだろうか。

c8$ rm -f test.o test
c8$ make test.o
cc    -c -o test.o test.c
c8$ make test
cc   test.o   -o test
c8$ readelf -n test.o
c8$ readelf -n test

Displaying notes found in: .note.ABI-tag
  Owner                 Data size       Description
  GNU                  0x00000010       NT_GNU_ABI_TAG (ABI version tag)
    OS: Linux, ABI: 3.2.0

Displaying notes found in: .note.gnu.build-id
  Owner                 Data size       Description
  GNU                  0x00000014       NT_GNU_BUILD_ID (unique build ID bitstring)
    Build ID: 99d3ae3b669059d6c9b9b916522d4a742772dc93

Displaying notes found in: .gnu.build.attributes
(以下略)

test.oにはないが、実行ファイルであるtestにはある。これはリンクフェーズでつくことを示すのだろう。

リンカはbinutilsの中に含まれているので、binutilsのソースコードを取り寄せてみよう。

c8$ dnf download --source binutils
(中略)
No package binutils-2.30-73.el8.src available.
Exiting due to strict setting.
Error: No package binutils-2.30-73.el8.src available.

がーん。仕方ないのでIIJのミラーから直接取り寄せよう。

c8$ wget ftp://ftp.iij.ad.jp/pub/linux/centos-vault/8.2.2004/BaseOS/Source/SPackages/binutils-2.30-73.el8.src.rpm
(中略)
c8$ rpm -ivh binutils-2.30-73.el8.src.rpm
Updating / installing...
(中略)
c8$ rpmbuild -bp --nodeps ~/rpmbuild/SPECS/binutils.spec
Executing(%prep): /bin/sh -e /var/tmp/rpm-tmp.Iqbqn1
(中略)
+ exit 0
c8$ cd rpmbuild/BUILD/binutils-2.30
c8$ grep -r ABI-tag .
./ld/emulparams/shlelf_linux.sh:  .note.ABI-tag ${RELOCATING-0} : { *(.note.ABI-tag) }"
./ld/testsuite/ld-gc/abi-note.d:.* .note.ABI-tag[       ]+NOTE.*
./ld/testsuite/ld-gc/abi-note.s:        .section ".note.ABI-tag", "a"
./gold/layout.cc:  // .note.ABI-tag (as of version 1.6), so that's the one we go with
./include/elf/common.h:/* Values used in GNU .note.ABI-tag notes (NT_GNU_ABI_TAG).  */
./include/elf/common.h:/* Values for FreeBSD .note.ABI-tag notes.  Note name is "FreeBSD".  */

うーん、なんかそれらしいのはないな。ただ、include/elf/common.hをみると、.note.ABI-tagにはNT_GNU_ABI_TAGというシンボルがついているらしいこと、それに

#define GNU_ABI_TAG_LINUX       0
#define GNU_ABI_TAG_HURD        1
#define GNU_ABI_TAG_SOLARIS     2
#define GNU_ABI_TAG_FREEBSD     3
#define GNU_ABI_TAG_NETBSD      4
#define GNU_ABI_TAG_SYLLABLE    5
#define GNU_ABI_TAG_NACL        6

というバリエーションがあることがわかる。ただ先に見た通り、SolarisでもNetBSDでも.note.ABI-tagはつかない。GNUとあるので、もしかすると、Debian GNU/NetBSDのようなもので使われるのかもしれない。

コンパイラがリンカに何か渡しているのだろうか。ccというコマンドは、入力ファイルや出力ファイルに合わせて、Cコンパイラ (cc1)、アセンブラ、リンカなどを適宜呼び出すだけのプログラムだ。各子プロセスを呼び出す際の引数は、-vオプションをつけると出てくる。

c8$ cc -v -o test test.c
Using built-in specs.
(中略)
Thread model: posix
gcc version 8.3.1 20191121 (Red Hat 8.3.1-5) (GCC)
COLLECT_GCC_OPTIONS='-v' '-o' 'test' '-mtune=generic' '-march=x86-64'
 /usr/libexec/gcc/x86_64-redhat-linux/8/cc1 -quiet -v test.c -quiet -dumpbase test.c -mtune=generic -march=x86-64 -auxbase test -version -o /tmp/ccHDAWoV.s
(中略)
COLLECT_GCC_OPTIONS='-v' '-o' 'test' '-mtune=generic' '-march=x86-64'
 /usr/libexec/gcc/x86_64-redhat-linux/8/collect2 -plugin /usr/libexec/gcc/x86_64-redhat-linux/8/liblto_plugin.so -plugin-opt=/usr/libexec/gcc/x86_64-redhat-linux/8/lto-wrapper -plugin-opt=-fresolution=/tmp/ccRkU91B.res -plugin-opt=-pass-through=-lgcc -plugin-opt=-pass-through=-lgcc_s -plugin-opt=-pass-through=-lc -plugin-opt=-pass-through=-lgcc -plugin-opt=-pass-through=-lgcc_s --build-id --no-add-needed --eh-frame-hdr --hash-style=gnu -m elf_x86_64 -dynamic-linker /lib64/ld-linux-x86-64.so.2 -o test /usr/lib/gcc/x86_64-redhat-linux/8/../../../../lib64/crt1.o /usr/lib/gcc/x86_64-redhat-linux/8/../../../../lib64/crti.o /usr/lib/gcc/x86_64-redhat-linux/8/crtbegin.o -L/usr/lib/gcc/x86_64-redhat-linux/8 -L/usr/lib/gcc/x86_64-redhat-linux/8/../../../../lib64 -L/lib/../lib64 -L/usr/lib/../lib64 -L/usr/lib/gcc/x86_64-redhat-linux/8/../../.. /tmp/ccdW3ZHL.o -lgcc --as-needed -lgcc_s --no-as-needed -lc -lgcc --as-needed -lgcc_s --no-as-needed /usr/lib/gcc/x86_64-redhat-linux/8/crtend.o /usr/lib/gcc/x86_64-redhat-linux/8/../../../../lib64/crtn.o
Collect_GCC_OPTIONS='-v' '-o' 'test' '-mtune=generic' '-march=x86-64'

collect2というのは、リンカのラッパプログラムで、C++のコンストラクタやデストラクタなどをどうにかする、というものだ。collect2がリンカを呼び出す際の様子までは出てこないと見える。しかし、collect2に渡された引数の中に、それらしいものは見当たらない。

これは、collect2のソースも見ないといかんかな、と考えてる途中でふと気づいた。test.cをコンパイルしたファイルの他に、/usr/lib64/crt1.o、/usr/crti.o、/usr/lib/gcc/x86_64-redhat-linux/8/crtbegin.o、/usr/lib/gcc/x86_64-redhat-linux/8/crtend.o、/usr/lib64/crtn.oをリンクしている (crtは、C RunTime)。これらは、main()が呼ばれる前後の処理をするモジュールだ。ここに.note.ABI-tagが含まれているのではないのか。

c8$ readelf -n /usr/lib64/crt1.o

Displaying notes found in: .note.gnu.property
  Owner                 Data size       Description
  GNU                  0x00000010       NT_GNU_PROPERTY_TYPE_0
      Properties: x86 feature: IBT, SHSTK

Displaying notes found in: .note.ABI-tag
  Owner                 Data size       Description
  GNU                  0x00000010       NT_GNU_ABI_TAG (ABI version tag)
    OS: Linux, ABI: 3.2.0

Displaying notes found in: .gnu.build.attributes.hot
(以下略)

ビンゴ! ここにある.note.ABI-tagが実行ファイルにリンクされたに違いない。CentOS 7環境では、

c7$ readelf -n /usr/lib64/crt1.o 

Displaying notes found at file offset 0x00000040 with length 0x00000020:
  Owner                 Data size       Description
  GNU                  0x00000010       NT_GNU_ABI_TAG (ABI version tag)
    OS: Linux, ABI: 2.6.32

Linux 2.6.32だ。

crt1.oは、

c8$ rpm -qf /usr/lib64/crt1.o 
glibc-devel-2.28-101.el8.x86_64

glibcに含まれるようだ。早速ソースを取り寄せてみよう。

c8$ wget ftp://ftp.iij.ad.jp/pub/linux/centos-vault/8.2.2004/BaseOS/Source/SPackages/glibc-2.28-101.el8.src.rpm
(中略)
c8$ rpm -ivh glibc-2.28-101.el8.src.rpm 
(中略)
c8$ rpmbuild -bp ~/rpmbuild/SPECS/glibc.spec 
(中略)
c8$ rpmbuild -bp --nodeps ~/rpmbuild/SPECS/glibc.spec 
c8$ cd ~/rpmbuild/BUILD/glibc-2.28/
c8$ grep -r ABI-tag .
./ChangeLog.old/ChangeLog.12:   (open_verify): Check .note.ABI-tag of the library if present.
./ChangeLog.old/ChangeLog.12:   the same directory have different .note.ABI-tag.  Record osversion in
./ChangeLog.old/ChangeLog.12:   (build-shlib, build-module-helper): Make sure .note.ABI-tag comes
./ChangeLog.old/ChangeLog.17:   * elf/dl-load.c (open_verify): Find .note.ABI-tag notes even
./Makerules:               -e 's/^.*\.gnu\.hash[        ]*:.*$$/  .note.ABI-tag : { *(.note.ABI-tag) } &/' \
./Makerules:               -e 's/^.*\.hash[     ]*:.*$$/  .note.ABI-tag : { *(.note.ABI-tag) } &/' \
./csu/abi-note.S:       .section ".note.ABI-tag", "a"
./elf/dl-load.c:      /* Check .note.ABI-tag if present.  */
./sysdeps/x86/dl-prop.h:      /* Nb: Note sections like .note.ABI-tag and .note.gnu.build-id are

csu/abi-note.Sにあるのが、.note.ABI-tagの定義に違いない。csuは、C Start Upを意味する。どちらもコメントだが、残るelf/dl-load.cとsysdeps/x86/dl-prop.hがチェックする部分だろうか。

dl-prop.hは、ざっと調べてみると、.note.gnu.propertyというのを調べているようで、関係なさそう。

elf/dl-load.cは、うーん、これ読んでみると、共有ライブラリなど (*.so) を読むところのようだ。そして、Makefileを見ていると、rtld (RunTime LoaDer、ダイナミックリンカ)、つまり、/lib64/ld-linux-x86-64.so.2の一部のように思える。確かに、

c8$ readelf -n /lib64/libc.so.6

Displaying notes found in: .note.gnu.build-id
  Owner                 Data size       Description
  GNU                  0x00000014       NT_GNU_BUILD_ID (unique build ID bitstring)
    Build ID: 5134b8e7ad70b3200eb2aba1a53a7016599f65ab

Displaying notes found in: .note.ABI-tag
  Owner                 Data size       Description
  GNU                  0x00000010       NT_GNU_ABI_TAG (ABI version tag)
    OS: Linux, ABI: 3.2.0

Displaying notes found in: .note.gnu.property
(以下略)

と、libcには.note.ABI-tagがついている。カーネルのサービスは、libcを経由して利用するので、これはこれで合理的な動作だ。ちなみに、比較した結果、バージョンが足りないと、

            osversion = (abi_note[5] & 0xff) * 65536
                        + (abi_note[6] & 0xff) * 256
                        + (abi_note[7] & 0xff);
            if (abi_note[4] != __ABI_TAG_OS
                || (GLRO(dl_osversion) && GLRO(dl_osversion) < osversion))
              {
              close_and_out:
                __close_nocancel (fd);
                __set_errno (ENOENT);
                fd = -1;
              }

ENOENT (No such file or directory) を返すようになっている。

試しに、CentOS 8のlibc (/lib64/libc.so.6) をCentOS 6環境に持っていってみよう。CentOS 8のlibcを/tmp/root/lib64/libc.so.6に配置し、ダイナミックリンカはCentOS 6の/lib64/ld-linux-x86-64.so.2を/tmp/root/lib64/ld-linux-x86-64.so.2にコピー、CentOS 6でコンパイルしてあったhello worldを/tmp/root/testに置いた上で/tmp/rootにchrootする。

c6$ sudo chroot /tmp/root /test
/test: error while loading shared libraries: libc.so.6: cannot open shared object file: No such file or directory

ちゃんとNo such file or directoryが出た。

なお、dl-load.cの中でも、NetBSDのバイナリエミュレーションと同様、.note.ABI-tagというセクション名は見ないで、中身を調べている。

どう比較しているのか

ABI-tagを比較して足りていなければENOENTになることは分かったが、その比較対象はなんだろう。前節で引用したコードに出てくるdl_osversionというのが比較対象だ。これは、sysdeps/unix/sysv/linux/dl-osinfo.hで定義されているマクロDL_SYSDEP_OSCHECKの中で、_dl_discover_osversion()の結果を設定している。_dl_discover_osversion()は、sysdeps/unix/sysv/linux/dl-sysdep.cで定義されており、uname()を呼んだり、/proc/sys/kernel/osreleaseを読んだりして実行環境のLinuxバージョンを取得している。

興味深いのは、その実行環境を取得するマクロDL_SYSDEP_OSCHECKの中で、dl_osversion (の元になるローカル変数version) と、定数__LINUX_KERNEL_VERSIONとを比較しているところだ。

    int version = _dl_discover_osversion ();                                  \
    if (__glibc_likely (version >= 0))                                        \
      {                                                                       \
        if (__builtin_expect (GLRO(dl_osversion) == 0, 1)                     \
            || GLRO(dl_osversion) > version)                                  \
          GLRO(dl_osversion) = version;                                       \
                                                                              \
        /* Now we can test with the required version.  */                     \
        if (__LINUX_KERNEL_VERSION > 0 && version < __LINUX_KERNEL_VERSION)   \
          /* Not sufficent.  */                                               \
          FATAL ("FATAL: kernel too old\n");                                  \
      }                                                                       \

__LINUX_KERNEL_VERSIONは、というと、sysdeps/unix/sysv/linux/configureでminimum_kernelというのから設定されていて、これはconfigureスクリプトのオプション--enable-kernelから設定されているようだ。CentOS 8では、glibcのspecファイルを見ると3.2に設定されている (規定値も3.2.0) ので、結局のところ比較対象であるdl_osversionを取得する段階でuname()の結果 (など) が3.2.0未満だとFATALとなることがわかる。なお、minimum_kernelは、先ほど見たabi-note.Sで.note.ABI-tagのバージョン項の__ABI_TAG_VERSIONを得るにも使われている。

DL_SYSDEP_OSCHECKを呼んでいるのは2箇所あり、1つはダイナミックリンカの中、もう一つはスタティックリンクの時のCスタートアップの中である。

試しに、libcだけではなく、ダイナミックリンカ (/lib64/ld-linux-x86-64.so.2) をCentOS 8環境から持っていってみよう。/tmp/root/lib64/ld-linux-x86-64.so.2に配置し、chrootでhello worldを実行してみる。

c6$ sudo chroot /tmp/root /test
FATAL: kernel too old

これはダイナミックリンカが出しているはずだ。CentOS 8ではstatic linkできないので、ubuntu 20.04 (ABI-tagはやはりLinux 3.2.0になる) でスタティックリンクしたhello worldを、CentOS 6に持ってきて実行すると、

/tmp/test-static 
FATAL: kernel too old
Aborted (core dumped)

こちらはhello worldにリンクされているCスタートアップが出していると考えられる。

ただ、これらが何と比べてtoo oldか、というと、.note.ABI-tagとは直接関係なく、glibcのconfigureスクリプトに渡される--enable-kernel=X.X.Xが起源である。.note.ABI-tagも同じconfigureオプションが起源であるという意味で、間接的な関係となる。

今回のまとめ

  • .note.ABI-tagは、OS (というか、カーネルの種類) とバージョンを持っている。名前は慣習に過ぎないようで、NOTEセクションにある特定の形式、という点が重要。
  • OS項は、NetBSDのLinuxバイナリエミュレーションが見ているが、Linux (カーネル) は見ていないので、CPUが合ったELFであればLinux用だろうがNetBSD用だろうが (おそらくFreeBSDやSolaris用でも) 実行しようとする。
  • libcのバージョン項は、ダイナミックリンク時にダイナミックリンカが見て、実行環境と比較する。カーネルを呼び出すのはlibcなので、これは合理的な動きである。
  • なおダイナミックリンカは全ての共有ライブラリのリンク時に.note.ABI-tagを見るが、libc以外の大抵のライブラリには.note.ABI-tagはついていない。
  • 実行ファイルのバージョン項を見ている人はいないので、新しい実行ファイルを古いOSで実行しようとしても実行できてしまう。
  • ただし、スタティックリンクの実行ファイルの場合、.note.ABI-tagにあるのと同じバージョンが実行ファイルの中 (Cスタートアップ内) に埋め込まれていて、main()呼び出し前に実行環境と比較する。スタティックリンクの場合はカーネルを呼び出すlibcも埋め込まれているので、これも合理的な動作であり、埋め込まれたバージョンを人間が知るために.note.ABI-tagは有用である。
  • ダイナミックリンクの実行ファイルの.note.ABI-tagは何のためにあるのか分からない (知ってる人は教えてください)。
  • ダイナミックリンカも最初に実行環境と埋め込まれたバージョンを比較する。ダイナミックリンカには.note.ABI-tagはないので、この埋め込まれたバージョンを人間が知る方法は逆アセンブルなどによるしかなさそう。またダイナミックリンカも、libcをリンクする前にファイルとしてのlibcを読んでくるので、カーネルを呼び出しているのは間違いない。したがって合理的な動作である。
  • CentOS 6→7→8で、ABIバージョンが2.6.12→2.6.32→3.2.0と上がっている理由を調べる前に力尽きた。

いうまでもないが、一般論として、新しい版向けのプログラムは、当然古い版では動作しない。今回hello worldが動作したのは偶然である。