情弱ログ

参考にならないので当てにしないでください

引数の数が関数の実行時間に及ぼす影響を調査した話

x86_64の呼び出し規約では、関数の引数は主にレジスタを使って渡すように定義されている。
主に、というのは引数の数が6個まではレジスタ(%edi、%esi、%edx、%ecx、%r8、%r9)を使って渡し、7個以上の場合はスタックに置いて渡す形式となっているからだ。

レジスタとメモリアクセスでは明らかに後者のほうが遅いため、関数の引数が7個以上になると6個以下の関数に比べて実行時間が増加するのでは?と思い検証してみた。

検証環境は以下の通り。

OS Gentoo Linux
カーネル Linux 4.9.76-gentoo-r1
コンパイラ GCC 6.4.0
CPU Intel Core i7-3770K(3.50GHz)

検証には以下のコードを用いた。マクロ内でこんな短くて衝突しやすい名前を使うべきではないとか、そもそもマクロを使うべきではないとか、色々とマサカリが怖いですが、良い子の皆さんは真似しないでくださいということでどうかご容赦ください。

#include <stdio.h>
#include <time.h>

#define LOOP_MAX (100000000)
#define TRY_COUNT (100)


void timespec_diff(struct timespec *start, struct timespec *end, struct timespec *diff)
{
	if ((end->tv_nsec - start->tv_nsec) < 0) {
		diff->tv_sec = end->tv_sec - start->tv_sec - 1;
		diff->tv_nsec = 1000000000 + end->tv_nsec - start->tv_nsec;
	} else {
		diff->tv_sec = end->tv_sec - start->tv_sec;
		diff->tv_nsec = end->tv_nsec - start->tv_nsec;
	}
}


int func_arg_one(int a)
{
	return a + a + a + a + a + a + a + a;
}


int func_arg_two(int a, int b)
{
	return a + b + a + a + a + a + a + a;
}


int func_arg_three(int a, int b, int c)
{
	return a + b + c + a + a + a + a + a;
}


int func_arg_four(int a, int b, int c, int d)
{
	return a + b + c + d + a + a + a + a;
}


int func_arg_five(int a, int b, int c, int d, int e)
{
	return a + b + c + d + e + a + a + a;
}


int func_arg_six(int a, int b, int c, int d, int e, int f)
{
	return a + b + c + d + e + f + a + a;
}


int func_arg_seven(int a, int b, int c, int d, int e, int f, int g)
{
	return a + b + c + d + e + f + g + a;
}


int func_arg_eight(int a, int b, int c, int d, int e, int f, int g, int h)
{
	return a + b + c + d + e + f + g + h;
}


#define MEASURE(filename, funcname, ...)							\
	do {															\
		FILE *fp = fopen(filename, "w");							\
		size_t i, j;												\
		struct timespec before, after, diff;							\
		for (i = 0; i < TRY_COUNT; i++) {								\
			clock_gettime(CLOCK_MONOTONIC, &before);					\
			for (j = 0; j < LOOP_MAX; j++) {							\
				funcname(__VA_ARGS__);									\
			}															\
			clock_gettime(CLOCK_MONOTONIC, &after);						\
			timespec_diff(&before, &after, &diff);						\
			fprintf(fp, "%lld.%.9ld\n", (long long)diff.tv_sec, diff.tv_nsec);		\
		}																\
		fclose(fp);														\
	} while(0)


int main(void)
{
	MEASURE("one.dat", func_arg_one, j);
	MEASURE("two.dat", func_arg_two, j, j);
	MEASURE("three.dat", func_arg_three, j, j, j);
	MEASURE("four.dat", func_arg_four, j, j, j, j);
	MEASURE("five.dat", func_arg_five, j, j, j, j, j);
	MEASURE("six.dat", func_arg_six, j, j, j, j, j, j);
	MEASURE("seven.dat", func_arg_seven, j, j, j, j, j, j, j);
	MEASURE("eight.dat", func_arg_eight, j, j, j, j, j, j, j, j);

	return 0;
}

1個から8個までの引数を取る関数を用意し、各関数は渡された引数を単純に足し算する。この時、引数が8個に満たないものは不足分を最初の引数から足すことにした。つまり、引数が2つの場合は「引数1 + 引数2 + 引数1 + 引数1 + 引数1 + 引数1 + 引数1 + 引数1」している。この計算をLOOP_MAX回繰り返したものを1セットとし、TRY_COUNTセット回の試行を各datファイルに出力する。また、コンパイラによる最適化を抑制するため、コンパイル時に-O0オプションを追加している。

出力された各datファイルの平均値をプロットした図が以下のグラフになる。

f:id:sugawarayusuke:20180503170516p:plain

引数の数が増えるほど時間が増加する傾向が見れる。しかし、期待した結果とは異なり、引数の数が7個以上であることで特段時間が増加する様子は見て取れなかった。

アセンブルして関数を確認すると以下のようになっていた。

00000000000008a4 <func_arg_one>:
     8a4:	55                   	push   %rbp
     8a5:	48 89 e5             	mov    %rsp,%rbp
     8a8:	89 7d fc             	mov    %edi,-0x4(%rbp)
     8ab:	8b 45 fc             	mov    -0x4(%rbp),%eax
     8ae:	8d 14 00             	lea    (%rax,%rax,1),%edx
     8b1:	8b 45 fc             	mov    -0x4(%rbp),%eax
     8b4:	01 c2                	add    %eax,%edx
     8b6:	8b 45 fc             	mov    -0x4(%rbp),%eax
     8b9:	01 c2                	add    %eax,%edx
     8bb:	8b 45 fc             	mov    -0x4(%rbp),%eax
     8be:	01 c2                	add    %eax,%edx
     8c0:	8b 45 fc             	mov    -0x4(%rbp),%eax
     8c3:	01 c2                	add    %eax,%edx
     8c5:	8b 45 fc             	mov    -0x4(%rbp),%eax
     8c8:	01 c2                	add    %eax,%edx
     8ca:	8b 45 fc             	mov    -0x4(%rbp),%eax
     8cd:	01 d0                	add    %edx,%eax
     8cf:	5d                   	pop    %rbp
     8d0:	c3                   	retq   

...

0000000000000a2c <func_arg_eight>:
     a2c:	55                   	push   %rbp
     a2d:	48 89 e5             	mov    %rsp,%rbp
     a30:	89 7d fc             	mov    %edi,-0x4(%rbp)
     a33:	89 75 f8             	mov    %esi,-0x8(%rbp)
     a36:	89 55 f4             	mov    %edx,-0xc(%rbp)
     a39:	89 4d f0             	mov    %ecx,-0x10(%rbp)
     a3c:	44 89 45 ec          	mov    %r8d,-0x14(%rbp)
     a40:	44 89 4d e8          	mov    %r9d,-0x18(%rbp)
     a44:	8b 55 fc             	mov    -0x4(%rbp),%edx
     a47:	8b 45 f8             	mov    -0x8(%rbp),%eax
     a4a:	01 c2                	add    %eax,%edx
     a4c:	8b 45 f4             	mov    -0xc(%rbp),%eax
     a4f:	01 c2                	add    %eax,%edx
     a51:	8b 45 f0             	mov    -0x10(%rbp),%eax
     a54:	01 c2                	add    %eax,%edx
     a56:	8b 45 ec             	mov    -0x14(%rbp),%eax
     a59:	01 c2                	add    %eax,%edx
     a5b:	8b 45 e8             	mov    -0x18(%rbp),%eax
     a5e:	01 c2                	add    %eax,%edx
     a60:	8b 45 10             	mov    0x10(%rbp),%eax
     a63:	01 c2                	add    %eax,%edx
     a65:	8b 45 18             	mov    0x18(%rbp),%eax
     a68:	01 d0                	add    %edx,%eax
     a6a:	5d                   	pop    %rbp
     a6b:	c3                   	retq   

最適化を抑制しているため、引数を一度メモリ上に保持し、そこから毎回計算しているようだ。このため、引数の個数は関係なく全ての関数でメモリアクセスが発生しているため、期待した結果が得られなかったものと予想できる。

ならばと最適化(-O1)してメモリアクセスが極力発生しないようにし、再検証してみた。逆アセンブルしたコードは以下のとおり。

0000000000000860 <func_arg_one>:
 860:	8d 04 fd 00 00 00 00 	lea    0x0(,%rdi,8),%eax
 867:	c3                   	retq   

0000000000000868 <func_arg_two>:
 868:	8d 04 be             	lea    (%rsi,%rdi,4),%eax
 86b:	8d 04 78             	lea    (%rax,%rdi,2),%eax
 86e:	01 f8                	add    %edi,%eax
 870:	c3                   	retq   

0000000000000871 <func_arg_three>:
 871:	8d 04 be             	lea    (%rsi,%rdi,4),%eax
 874:	8d 04 10             	lea    (%rax,%rdx,1),%eax
 877:	8d 04 78             	lea    (%rax,%rdi,2),%eax
 87a:	c3                   	retq   

000000000000087b <func_arg_four>:
 87b:	01 fe                	add    %edi,%esi
 87d:	01 f2                	add    %esi,%edx
 87f:	01 d1                	add    %edx,%ecx
 881:	8d 04 b9             	lea    (%rcx,%rdi,4),%eax
 884:	c3                   	retq   

0000000000000885 <func_arg_five>:
 885:	01 fe                	add    %edi,%esi
 887:	01 f2                	add    %esi,%edx
 889:	01 d1                	add    %edx,%ecx
 88b:	44 01 c1             	add    %r8d,%ecx
 88e:	8d 04 79             	lea    (%rcx,%rdi,2),%eax
 891:	01 f8                	add    %edi,%eax
 893:	c3                   	retq   

0000000000000894 <func_arg_six>:
 894:	01 fe                	add    %edi,%esi
 896:	01 f2                	add    %esi,%edx
 898:	01 d1                	add    %edx,%ecx
 89a:	44 01 c1             	add    %r8d,%ecx
 89d:	44 01 c9             	add    %r9d,%ecx
 8a0:	8d 04 79             	lea    (%rcx,%rdi,2),%eax
 8a3:	c3                   	retq   

00000000000008a4 <func_arg_seven>:
 8a4:	01 fe                	add    %edi,%esi
 8a6:	01 f2                	add    %esi,%edx
 8a8:	01 d1                	add    %edx,%ecx
 8aa:	44 01 c1             	add    %r8d,%ecx
 8ad:	44 01 c9             	add    %r9d,%ecx
 8b0:	89 c8                	mov    %ecx,%eax
 8b2:	03 44 24 08          	add    0x8(%rsp),%eax
 8b6:	01 f8                	add    %edi,%eax
 8b8:	c3                   	retq   

00000000000008b9 <func_arg_eight>:
 8b9:	01 f7                	add    %esi,%edi
 8bb:	01 fa                	add    %edi,%edx
 8bd:	01 d1                	add    %edx,%ecx
 8bf:	44 01 c1             	add    %r8d,%ecx
 8c2:	44 01 c9             	add    %r9d,%ecx
 8c5:	89 c8                	mov    %ecx,%eax
 8c7:	03 44 24 08          	add    0x8(%rsp),%eax
 8cb:	03 44 24 10          	add    0x10(%rsp),%eax
 8cf:	c3                   	retq   

lea命令で計算していて読みづらいが、引数が7個以上の関数(func_arg_seven、func_arg_eight)のみメモリアクセスが発生している。

この状態では実行がすぐに終わってしまうので、LOOP_MAXを10倍(100000000→1000000000)し、先ほどと同様に平均値をプロットしたものが以下のグラフとなる。

f:id:sugawarayusuke:20180503174925p:plain

引数が7個以上の関数において実行時間が増加する傾向が見て取れるが、今度は引数が5個と6個の場合に実行時間が何故か減少してしまった。引数が1個の時より短いんだけど何これ…

まとめとして、確かに引数の数が増加すると処理時間が増加することがあるが、微々たる差であるため引数を無理に減らすよりも他の部分で最適化をしたほうが建設的であると言える。
無理矢理まとめたけどこれ以上の調査はCPI(?)とかの世界になってきそうなので、僕には無理です。誰かやってください。

ひろしま学生IT勉強会第3回(3/9開催)

speakerdeck.com

ひろしま学生IT勉強会で発表した資料です。

Linuxの共有ライブラリ呼び出し(呼び出し側編)

Linuxで共有ライブラリの関数を呼び出す際、PLTを経由して関数が呼び出される。PLTとはProcedure Linkage Tableの略で、PLT経由で呼び出す関数のアドレスは動的リンカが解決してくれる。この機構を追ってみた。

今回用意したソースコードはこんな感じ。

#include <stdio.h>

int main()
{
	printf("Hello, World!\n");
	printf("Goodbye, World!\n");

	return 0;
}

Gentoo (profile 17.0以降)だとPIEがデフォルトで有効なので、明示的に無効化してコンパイルする。

$ gcc -fno-pie -no-pie hello.c -o hello_c

アセンブルするとこんな感じ。(main()だけ)

00000000004004d6 <main>:
  4004d6:	55                   	push   %rbp
  4004d7:	48 89 e5             	mov    %rsp,%rbp
  4004da:	bf 74 05 40 00       	mov    $0x400574,%edi
  4004df:	e8 ec fe ff ff       	callq  4003d0 <puts@plt>
  4004e4:	bf 82 05 40 00       	mov    $0x400582,%edi
  4004e9:	e8 e2 fe ff ff       	callq  4003d0 <puts@plt>
  4004ee:	b8 00 00 00 00       	mov    $0x0,%eax
  4004f3:	5d                   	pop    %rbp
  4004f4:	c3                   	retq
  4004f5:	66 2e 0f 1f 84 00 00 	nopw   %cs:0x0(%rax,%rax,1)
  4004fc:	00 00 00
  4004ff:	90                   	nop

コンパイラが最適化してprintfがputsになっている程度で、後は大体雰囲気でいける。0x400574および0x400582は文字列のアドレスとなっている。
main関数が呼び出しているputs@pltを見てみよう。

Disassembly of section .plt:

00000000004003c0 <.plt>:
  4003c0:	ff 35 42 0c 20 00    	pushq  0x200c42(%rip)        # 601008 <_GLOBAL_OFFSET_TABLE_+0x8>
  4003c6:	ff 25 44 0c 20 00    	jmpq   *0x200c44(%rip)        # 601010 <_GLOBAL_OFFSET_TABLE_+0x10>
  4003cc:	0f 1f 40 00          	nopl   0x0(%rax)

00000000004003d0 <puts@plt>:
  4003d0:	ff 25 42 0c 20 00    	jmpq   *0x200c42(%rip)        # 601018 <puts@GLIBC_2.2.5>
  4003d6:	68 00 00 00 00       	pushq  $0x0
  4003db:	e9 e0 ff ff ff       	jmpq   4003c0 <.plt>

puts@pltは3個の命令だけであり、しかもおおよそ通常の関数とは異なった形式を持っている。
0x200c42(%rip) (右のコメントにあるように、rip=0x4003d6なので0x4003d6+0x200c42 = 0x601018)の中身が見てみたいが、実行時のメモリからでないと分からないので、gdbから起動してみる。

(gdb) b main
Breakpoint 1 at 0x4004da
(gdb) r
Starting program: /home/vicco/Scripts/hello/hello_c

Breakpoint 1, 0x00000000004004da in main ()
(gdb) x 0x601018
0x601018:	0x004003d6

結果より、jmpq *0x200c42(%rip)は0x004003d6、すなわち次の行(puts@pltの二行目)へのjmp命令であることが分かる。先の処理を追ってみる。
まず、puts@pltは0x0をスタックにpushし、.pltにjmpする。.pltは更に0x601008をスタックにpushし、*0x601010へjmpする。callではなくjmpなので、push以外でスタックは変動しないことに注意したい。ちなみに.pltはgdbから逆アセンブルできなかった。関数らしき形式を取っていないからだろうか。

gdbのステップ実行で追いかけてみる。「disp/i $pc」はプログラムカウンタの指す命令列を表示するようgdbに指示する命令である。

(gdb) disp/i $pc
1: x/i $pc
=> 0x4004da <main+4>:	mov    $0x400574,%edi
(gdb) si
0x00000000004004df in main ()
1: x/i $pc
=> 0x4004df <main+9>:	callq  0x4003d0 <puts@plt>
(gdb) si
0x00000000004003d0 in puts@plt ()
1: x/i $pc
=> 0x4003d0 <puts@plt>:	jmpq   *0x200c42(%rip)        # 0x601018
(gdb) si
0x00000000004003d6 in puts@plt ()
1: x/i $pc
=> 0x4003d6 <puts@plt+6>:	pushq  $0x0
(gdb) si
0x00000000004003db in puts@plt ()
1: x/i $pc
=> 0x4003db <puts@plt+11>:	jmpq   0x4003c0
(gdb) si
0x00000000004003c0 in ?? ()
1: x/i $pc
=> 0x4003c0:	pushq  0x200c42(%rip)        # 0x601008
(gdb) si
0x00000000004003c6 in ?? ()
1: x/i $pc
=> 0x4003c6:	jmpq   *0x200c44(%rip)        # 0x601010
(gdb) si
0x00007ffff7def7b0 in ?? () from /lib64/ld-linux-x86-64.so.2
1: x/i $pc
=> 0x7ffff7def7b0:	vorpd  %ymm0,%ymm1,%ymm8

.pltのjmpq *0x200c44(%rip) により、/lib64/ld-linux-x86-64.so.2の命令列にjmpしたようだ。vorpd等見たこともない命令が出てきてしんどいので、ここは飛ばしてしまおう。

(gdb) fin
Run till exit from #0  0x00007ffff7def7b0 in ?? () from /lib64/ld-linux-x86-64.so.2
Hello, World!
0x00000000004004e4 in main ()
1: x/i $pc
=> 0x4004e4 <main+14>:	mov    $0x400582,%edi

Hello, World!と出力されたので、puts@pltの処理が終了してしまったことが分かる。気を取り直して2回目のputs@pltを実行しよう。

(gdb) si
0x00000000004004e9 in main ()
1: x/i $pc
=> 0x4004e9 <main+19>:	callq  0x4003d0 <puts@plt>
(gdb) si
0x00000000004003d0 in puts@plt ()
1: x/i $pc
=> 0x4003d0 <puts@plt>:	jmpq   *0x200c42(%rip)        # 0x601018
(gdb) si
0x00007ffff7a98de0 in puts () from /lib64/libc.so.6
1: x/i $pc
=> 0x7ffff7a98de0 <puts>:	push   %r13

先ほどと同じくputs@pltに入ったはいいものの、その後のjmp先が異なることにお気づきいただけただろうか。前回のputs@pltでは1行目のjmpでputs@pltの2行目に飛んでいた。しかし、今回は全く異なる、しかも本来呼び出したい関数である/lib64/libc.so.6のputs()に飛んでいることが分かる。明らかに怪しい0x601018の参照先の値を見てみる。

(gdb) x/g 0x601018
0x601018:	0x00007ffff7a98de0

対比として先程の値を再掲する。

(gdb) x 0x601018
0x601018:	0x004003d6

このように、先程とは異なる値が格納されている。(x/gとしているのは64bitの数値をダンプするためです。)

pltを経由して呼び出したライブラリ関数は1回目のみ/lib64/ld-linux-x86-64.so.2で定義された何かしらの処理が行われ、2回目以降は直接jmpできるようになる。この時、pltが最初に参照しているテーブル(今回なら0x601018)をGOT(Global Offset Table)と呼び、1回目に/lib64/ld-linux-x86-64.so.2で定義された動的リンカがGOTを書き換えることで2回目以降の呼び出しにかかるコストを低減できる。このように関数の呼び出しに応じて動的リンクを行う方式を遅延リンクや遅延バインドと呼ぶらしい。

動的リンカはスタックにpushされた2つの値を元にGOTを書き換える。最初の0x0は.rela.pltセクションのオフセット、二番目の0x601008はGOTのどっかのエントリ。多分。
.rela.pltセクションをダンプしてみよう。

$ readelf -x .rela.plt hello_c

Hex dump of section '.rela.plt':
  0x00400390 18106000 00000000 07000000 01000000 ..`.............
  0x004003a0 00000000 00000000                   ........

少し見づらいのでodでダンプする。

$ od -tx8 -Ax -j 0x390 -N 0x18 hello_c
000390 0000000000601018 0000000100000007
0003a0 0000000000000000
0003a8

.rela.pltの構造は以下の構造体で定義されている。

typedef struct
{
  Elf64_Addr    r_offset;               /* Address */
  Elf64_Xword   r_info;                 /* Relocation type and symbol index */
  Elf64_Sxword  r_addend;               /* Addend */
} Elf64_Rela;
typedef uint64_t Elf64_Addr;
typedef uint64_t Elf64_Xword;
typedef int64_t  Elf64_Sxword;

r_offsetを見ると0x601018が格納されており、これはputs@pltが最初に見るGOTのアドレスに一致する。
r_infoは一部のビットフィールドが情報を持つため、そのままではなく以下のマクロを用いて情報を取り出す。

/* How to extract and insert information held in the r_info field.  */

#define ELF64_R_SYM(i)                  ((i) >> 32)
#define ELF64_R_TYPE(i)                 ((i) & 0xffffffff)
#define ELF64_R_INFO(sym,type)          ((((Elf64_Xword) (sym)) << 32) + (type))

ELF64_R_SYM(r_info)とすれば1が得られ、ELF64_R_TYPE(r_info)とすれば7が得られる。以下、前者をsym、後者をtypeと呼ぶ。先にtypeを見ていこう。これは以下のマクロで定義された値となる。

/* AMD x86-64 relocations.  */
#define R_X86_64_JUMP_SLOT	7	/* Create PLT entry */

次にsymを見ていく。symは.dynsymセクションのインデックスとなっており、.dynsymセクションの構造は以下の構造体で定義される。

typedef struct
{
  Elf64_Word    st_name;                /* Symbol name (string tbl index) */
  unsigned char st_info;                /* Symbol type and binding */
  unsigned char st_other;               /* Symbol visibility */
  Elf64_Section st_shndx;               /* Section index */
  Elf64_Addr    st_value;               /* Symbol value */
  Elf64_Xword   st_size;                /* Symbol size */
} Elf64_Sym;
typedef uint32_t Elf64_Word;
typedef uint16_t Elf64_Section;
typedef uint64_t Elf64_Addr;
typedef uint64_t Elf64_Xword;

一つのエントリーにつき24byteとなっているようだ。

$ readelf -x .dynsym hello_c

Hex dump of section '.dynsym':
  0x00400298 00000000 00000000 00000000 00000000 ................
  0x004002a8 00000000 00000000 0b000000 12000000 ................
  0x004002b8 00000000 00000000 00000000 00000000 ................
  0x004002c8 10000000 12000000 00000000 00000000 ................
  0x004002d8 00000000 00000000 2e000000 20000000 ............ ...
  0x004002e8 00000000 00000000 00000000 00000000 ................

インデックスが1の部分だけを抜き出すと以下のような形となる。

  0x004002a8                   0b000000 12000000 ................
  0x004002b8 00000000 00000000 00000000 00000000 ................

これより、st_nameは0x0000000bであることが分かる。これは.dynstrセクションのインデックスとなっている。

$ readelf -p .dynstr hello_c

String dump of section '.dynstr':
  [     1]  libc.so.6
  [     b]  puts
  [    10]  __libc_start_main
  [    22]  GLIBC_2.2.5
  [    2e]  __gmon_start__

0xbはputsとなっており、期待したものと一致する。
先の.dynsymのダンプを見てみると、st_name以外にst_infoが定義されており、その値は0x12となっている。この値もビットフィールドにより意味が分かれており、以下のマクロで取り出すことができる。

/* How to extract and insert information held in the st_info field.  */

#define ELF32_ST_BIND(val)              (((unsigned char) (val)) >> 4)
#define ELF32_ST_TYPE(val)              ((val) & 0xf)
#define ELF32_ST_INFO(bind, type)       (((bind) << 4) + ((type) & 0xf))

/* Both Elf32_Sym and Elf64_Sym use the same one-byte st_info field.  */
#define ELF64_ST_BIND(val)              ELF32_ST_BIND (val)
#define ELF64_ST_TYPE(val)              ELF32_ST_TYPE (val)
#define ELF64_ST_INFO(bind, type)       ELF32_ST_INFO ((bind), (type))

st_infoは0x12なので、ELF64_ST_BIND(st_info)は1、ELF64_ST_TYPE(st_info)は2となる。これらはそれぞれ以下のような意味を表す。

#define STB_GLOBAL      1               /* Global symbol */
#define STT_FUNC        2               /* Symbol is a code object */

ちなみにreadelfコマンドに-rオプションをつけるとこれらの情報をひとまとめにして表示できる。

$ readelf -r hello_c

Relocation section '.rela.dyn' at offset 0x360 contains 2 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
000000600ff0  000200000006 R_X86_64_GLOB_DAT 0000000000000000 __libc_start_main@GLIBC_2.2.5 + 0
000000600ff8  000300000006 R_X86_64_GLOB_DAT 0000000000000000 __gmon_start__ + 0

Relocation section '.rela.plt' at offset 0x390 contains 1 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
000000601018  000100000007 R_X86_64_JUMP_SLO 0000000000000000 puts@GLIBC_2.2.5 + 0

putsの後のGLIBC_2.2.5が気になるが、今見れる範囲では正直分からんので宿題にする。

動的リンカが実行時にこれらの情報が取れるよう、プログラムヘッダのDYNAMICセクションにこれらのアドレスがまとまっている。(はずなんだけど実行時のプログラムヘッダってどうやって見るんだろう。) readelfに-dオプションをつけるとこれらを確認できる。

$ readelf -d hello_c

Dynamic section at offset 0xe20 contains 24 entries:
  Tag        Type                         Name/Value
 0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]
 0x000000000000000c (INIT)               0x4003a8
 0x000000000000000d (FINI)               0x400564
 0x0000000000000019 (INIT_ARRAY)         0x600e08
 0x000000000000001b (INIT_ARRAYSZ)       8 (bytes)
 0x000000000000001a (FINI_ARRAY)         0x600e10
 0x000000000000001c (FINI_ARRAYSZ)       8 (bytes)
 0x000000006ffffef5 (GNU_HASH)           0x400278
 0x0000000000000005 (STRTAB)             0x4002f8
 0x0000000000000006 (SYMTAB)             0x400298
 0x000000000000000a (STRSZ)              61 (bytes)
 0x000000000000000b (SYMENT)             24 (bytes)
 0x0000000000000015 (DEBUG)              0x0
 0x0000000000000003 (PLTGOT)             0x601000
 0x0000000000000002 (PLTRELSZ)           24 (bytes)
 0x0000000000000014 (PLTREL)             RELA
 0x0000000000000017 (JMPREL)             0x400390
 0x0000000000000007 (RELA)               0x400360
 0x0000000000000008 (RELASZ)             48 (bytes)
 0x0000000000000009 (RELAENT)            24 (bytes)
 0x000000006ffffffe (VERNEED)            0x400340
 0x000000006fffffff (VERNEEDNUM)         1
 0x000000006ffffff0 (VERSYM)             0x400336
 0x0000000000000000 (NULL)               0x0

他にも色々気になることはあるが、呼び出し側からするとこれ以上は厳しいので、次回は動的リンカのソースコードを追っていこう。

参考文献
再配置 (リンカーとライブラリ)
https://docs.oracle.com/cd/E19620-01/805-5821/chapter6-42444/index.html
ELFの動的リンク(1) - 七誌の開発日記
【ELF形式】.dynsym セクション - ゆずさん研究所
ELFの再配置シンボルの解決 | ψ(プサイ)の興味関心空間
http://ukai.jp/debuan/2002w/elf.txt
How is glibc loaded at runtime? | Dustin Schultz — Pluralsight Author & Senior Software Engineer

Linuxのプロセス実行を見てみる execve(2)編

Linuxのプロセス実行を見てみたクソ長い備忘録。全然まとまってません。

通常、Linuxではプロセスを実行する際、execシステムコールが呼び出される。execシステムコールとは現在のプロセスが開いているファイルディスクリプタ等を引き継ぎ、別のプロセスに差し替わるシステムコールである。また、新しいプロセスに渡す引数や環境変数によって呼び出すプロトタイプが異なっており、POSIX標準では以下の6種類が定義されている。

int execl(char const *path, char const *arg0, ...);
int execle(char const *path, char const *arg0, ..., char const * const *envp);
int execlp(char const *file, char const *arg0, ...);
int execv(char const *path, char const * const * argv);
int execve(char const *path, char const * const *argv, char const * const *envp);
int execvp(char const *file, char const * const *argv);

この内、今回はexecveに絞って実装を見ていく。

http://www.coins.tsukuba.ac.jp/~yas/coins/os2-2010/2011-01-25/

LinuxシステムコールはSYSCALL_DEFINEnマクロを用いて定義される。nはシステムコールに渡す引数の数が入る。
execveの引数は3個であるため、SYSCALL_DEFINE3\(execveでgrepをかけるとfs/exec.cで定義されていることが分かった。

SYSCALL_DEFINE3(execve,
		const char __user *, filename,
		const char __user *const __user *, argv,
		const char __user *const __user *, envp)
{
	return do_execve(getname(filename), argv, envp);
}

マクロなので引数の取り方が気持ち悪いが、filenameがファイル名、argvが引数、envpが環境変数に対応している。ここでの処理は単純にchar型ポインタで渡されたfilenameをstruct filenameに変換してdo_execve()に渡しているだけのようだ。do_execve()を見ていこう。

int do_execve(struct filename *filename,
	const char __user *const __user *__argv,
	const char __user *const __user *__envp)
{
	struct user_arg_ptr argv = { .ptr.native = __argv };
	struct user_arg_ptr envp = { .ptr.native = __envp };
	return do_execveat_common(AT_FDCWD, filename, argv, envp, 0);
}

これもchar型ポインタの__argvと__envpをそれぞれstruct user_arg_ptrに格納してdo_execveat_common()を呼び出しているだけのようだ。AT_FDCWDはinclude/uapi/linux/fcntl.hで定義されているマクロであり、定義元に併記されたコメントよりopenatにカレントディレクトリを使用するよう指し示す特殊な値であるようだ。

do_execveat_common()のプロトタイプ宣言は以下のようになっている。

static int do_execveat_common(int fd, struct filename *filename,
			      struct user_arg_ptr argv,
			      struct user_arg_ptr envp,
			      int flags)

fdにAT_FDCWDを、flagsに0を格納している。処理を追っていこう。

if (IS_ERR(filename))
	return PTR_ERR(filename);

まず、filenameに不備がないかをチェックする。

/*
 * We move the actual failure in case of RLIMIT_NPROC excess from
 * set*uid() to execve() because too many poorly written programs
 * don't check setuid() return code.  Here we additionally recheck
 * whether NPROC limit is still exceeded.
 */
if ((current->flags & PF_NPROC_EXCEEDED) &&
    atomic_read(&current_user()->processes) > rlimit(RLIMIT_NPROC)) {
	retval = -EAGAIN;
	goto out_ret;
}

/* We're below the limit (still or again), so we don't want to make
 * further execve() calls fail. */
current->flags &= ~PF_NPROC_EXCEEDED;

その後、プロセス数が最大値(RLIMIT_NPROC)を超えていないかチェックする。fork爆弾に対する制限らしい。コメントより、多くのプログラムがsetuid()の戻り値をチェックしておらず、このためRLIMIT_NPROCを超えた場合のエラー処理をsetuid()からexecve()に移動したとのこと。

以下がその経緯なのだろうか。日本人が関わっているとテンションが上がりますね。
革命の日々! 3.1からRLIMIT_NPROCの挙動が変わった件について

currentマクロについては以下のページが詳しい(少し古いため現在の実装とは異なる)。
currentマクロ - Linuxの備忘録とか・・・(目次へ)
current_user()->processesも同じくcurrentマクロからcredentialsを参照し、そのuserメンバを指している。正直struct task_structもstruct credもよく分かってない…

脱線したが、最大プロセス数のチェックが終わるとcurrent->flagsからPF_NPROC_EXCEEDEDを下ろす。これにより、更なるexecve(2)時の失敗が起きないようにする。

retval = unshare_files(&displaced);
if (retval)
	goto out_ret;

次に、unshare_files()によってタスクが共有しているファイルをコピー(duplicate)する。この関数はkernel/fork.cで定義されている。

/*
 *	Helper to unshare the files of the current task.
 *	We don't want to expose copy_files internals to
 *	the exec layer of the kernel.
 */

int unshare_files(struct files_struct **displaced)
{
	struct task_struct *task = current;
	struct files_struct *copy = NULL;
	int error;

	error = unshare_fd(CLONE_FILES, &copy);
	if (error || !copy) {
		*displaced = NULL;
		return error;
	}
	*displaced = task->files;
	task_lock(task);
	task->files = copy;
	task_unlock(task);
	return 0;
}

unshare_fd()は以下のようになっている。

/*
 * Unshare file descriptor table if it is being shared
 */
static int unshare_fd(unsigned long unshare_flags, struct files_struct **new_fdp)
{
	struct files_struct *fd = current->files;
	int error = 0;

	if ((unshare_flags & CLONE_FILES) &&
	    (fd && atomic_read(&fd->count) > 1)) {
		*new_fdp = dup_fd(fd, &error);
		if (!*new_fdp)
			return error;
	}

	return 0;
}

unshare_fd()はcurrent->filesをチェックし、current->filesの参照カウンタが1より大きい場合は共有していると判断し、dup_fd()によってコピーしたファイルテーブルを返す。その後、unshare_files()側でcurrent->filesをコピーしたものに差し替え、古いファイルテーブルを第一引数(displaced)に格納しているようだ。これによりファイル(ディスクリプタ?)テーブルを他のタスクと切り離している。
この処理を行うことでファイルテーブルのリークを防いでいる(らしい)。

次に、struct linux_binprm型ポインタであるbprmに確保したメモリを格納する。struct linux_binprmはexec処理の中核的な役割を果たす構造体で、以降の処理はbprmの初期化が主な仕事になっていく。

retval = -ENOMEM;
bprm = kzalloc(sizeof(*bprm), GFP_KERNEL);
if (!bprm)
	goto out_files;

まず、bprmに対しcredentialsの初期化を行う。

retval = prepare_bprm_creds(bprm);
if (retval)
	goto out_free;

これによりbprm->credに新しいcredentialsが格納される。credentialsよく分からんので飛ばす。

check_unsafe_exec(bprm);
current->in_execve = 1;
/*
 * determine how safe it is to execute the proposed program
 * - the caller must hold ->cred_guard_mutex to protect against
 *   PTRACE_ATTACH or seccomp thread-sync
 */
static void check_unsafe_exec(struct linux_binprm *bprm)
{
	struct task_struct *p = current, *t;
	unsigned n_fs;

	if (p->ptrace)
		bprm->unsafe |= LSM_UNSAFE_PTRACE;

	/*
	 * This isn't strictly necessary, but it makes it harder for LSMs to
	 * mess up.
	 */
	if (task_no_new_privs(current))
		bprm->unsafe |= LSM_UNSAFE_NO_NEW_PRIVS;

	t = p;
	n_fs = 1;
	spin_lock(&p->fs->lock);
	rcu_read_lock();
	while_each_thread(p, t) {
		if (t->fs == p->fs)
			n_fs++;
	}
	rcu_read_unlock();

	if (p->fs->users > n_fs)
		bprm->unsafe |= LSM_UNSAFE_SHARE;
	else
		p->fs->in_exec = 1;
	spin_unlock(&p->fs->lock);
}

check_unsafe_exec()はLSM(Linux Security Modules)用にbprm->unsafeに各種のフラグを設定する。これが終わるとcurrent->in_execveフラグを立てる。この変数もLSMが使用すると定義元のコメントに書かれていた。
以降は実際にファイルを開いて処理を行っていく。

file = do_open_execat(fd, filename, flags);
retval = PTR_ERR(file);
if (IS_ERR(file))
	goto out_unmark;
static struct file *do_open_execat(int fd, struct filename *name, int flags)
{
	struct file *file;
	int err;
	struct open_flags open_exec_flags = {
		.open_flag = O_LARGEFILE | O_RDONLY | __FMODE_EXEC,
		.acc_mode = MAY_EXEC,
		.intent = LOOKUP_OPEN,
		.lookup_flags = LOOKUP_FOLLOW,
	};

	if ((flags & ~(AT_SYMLINK_NOFOLLOW | AT_EMPTY_PATH)) != 0)
		return ERR_PTR(-EINVAL);
	if (flags & AT_SYMLINK_NOFOLLOW)
		open_exec_flags.lookup_flags &= ~LOOKUP_FOLLOW;
	if (flags & AT_EMPTY_PATH)
		open_exec_flags.lookup_flags |= LOOKUP_EMPTY;

	file = do_filp_open(fd, name, &open_exec_flags);
	if (IS_ERR(file))
		goto out;

	err = -EACCES;
	if (!S_ISREG(file_inode(file)->i_mode))
		goto exit;

	if (path_noexec(&file->f_path))
		goto exit;

	err = deny_write_access(file);
	if (err)
		goto exit;

	if (name->name[0] != '\0')
		fsnotify_open(file);

out:
	return file;

exit:
	fput(file);
	return ERR_PTR(err);
}

do_open_execat()により実際にファイルをオープンする。ファイルシステム何も分からんので雰囲気で読んでいく。まず、flagsは0となっているため、最初の条件式は全て無視できる。S_ISREG()は通常(regular)ファイルであるかのチェックを行い、path_noexec()によりマウントしているディスクの実行パーミッションをチェックし、deny_write_access()によって書き込みを禁止する。その後、filenameがNULL文字でなければfsnotify_open()を実行し、ファイルがオープンされたことを通知する。多分inotifyあたりが使用するのだろう。以上の操作でファイルがオープンされる。

sched_exec();

sched_exec()はsched/core.cで定義されており、空いているCPUにタスクを移動させる。execve()でこれを行うのはタスクの持つ有効なメモリとキャッシュのフットプリントが最も小さいためとコメントに書かれている。

bprm->file = file;
if (fd == AT_FDCWD || filename->name[0] == '/') {
	bprm->filename = filename->name;
} else {
	if (filename->name[0] == '\0')
		pathbuf = kasprintf(GFP_KERNEL, "/dev/fd/%d", fd);
	else
		pathbuf = kasprintf(GFP_KERNEL, "/dev/fd/%d/%s",
				    fd, filename->name);
	if (!pathbuf) {
		retval = -ENOMEM;
		goto out_unmark;
	}
	/*
	 * Record that a name derived from an O_CLOEXEC fd will be
	 * inaccessible after exec. Relies on having exclusive access to
	 * current->files (due to unshare_files above).
	 */
	if (close_on_exec(fd, rcu_dereference_raw(current->files->fdt)))
		bprm->interp_flags |= BINPRM_FLAGS_PATH_INACCESSIBLE;
	bprm->filename = pathbuf;
}
bprm->interp = bprm->filename;

bprm->filenameとbprm->interpの設定を行う。do_execveat_common()を呼び出す際、その第一引数にAT_FDCWDを渡したことを思い出してほしい。これにより、bprm->filenameはfilename->nameが設定される。また、filename->name[0]が"/"である場合、すなわち絶対パスでファイル名が渡されている場合も同様の値が設定される。それ以外の場合、まずfilename->nameが空である場合は/dev/fd/${fd}が、そうでない場合は/dev/fd/${fd}/${filename->name}がbprm->filenameとして設定される。また、O_CLOEXECフラグが有効な場合、exec後にファイル名にアクセスできなくなるため、あらかじめBINPRM_FLAGS_PATH_INACCESSIBLEフラグを立てておく。これが終わるとbprm->interpに先程設定したbprm->filenameを格納する。

以降、メモリの適切な位置にほげほげしていく処理が入ってくるので、以下のリンク先の画像を見ながら追っていきたい。
stackoverflow.com

retval = bprm_mm_init(bprm);
if (retval)
	goto out_unmark;
/*
 * Create a new mm_struct and populate it with a temporary stack
 * vm_area_struct.  We don't have enough context at this point to set the stack
 * flags, permissions, and offset, so we use temporary values.  We'll update
 * them later in setup_arg_pages().
 */
static int bprm_mm_init(struct linux_binprm *bprm)
{
	int err;
	struct mm_struct *mm = NULL;

	bprm->mm = mm = mm_alloc();
	err = -ENOMEM;
	if (!mm)
		goto err;

	err = __bprm_mm_init(bprm);
	if (err)
		goto err;

	return 0;

err:
	if (mm) {
		bprm->mm = NULL;
		mmdrop(mm);
	}

	return err;
}
static int __bprm_mm_init(struct linux_binprm *bprm)
{
	int err;
	struct vm_area_struct *vma = NULL;
	struct mm_struct *mm = bprm->mm;

	bprm->vma = vma = kmem_cache_zalloc(vm_area_cachep, GFP_KERNEL);
	if (!vma)
		return -ENOMEM;

	if (down_write_killable(&mm->mmap_sem)) {
		err = -EINTR;
		goto err_free;
	}
	vma->vm_mm = mm;

	/*
	 * Place the stack at the largest stack address the architecture
	 * supports. Later, we'll move this to an appropriate place. We don't
	 * use STACK_TOP because that can depend on attributes which aren't
	 * configured yet.
	 */
	BUILD_BUG_ON(VM_STACK_FLAGS & VM_STACK_INCOMPLETE_SETUP);
	vma->vm_end = STACK_TOP_MAX;
	vma->vm_start = vma->vm_end - PAGE_SIZE;
	vma->vm_flags = VM_SOFTDIRTY | VM_STACK_FLAGS | VM_STACK_INCOMPLETE_SETUP;
	vma->vm_page_prot = vm_get_page_prot(vma->vm_flags);
	INIT_LIST_HEAD(&vma->anon_vma_chain);

	err = insert_vm_struct(mm, vma);
	if (err)
		goto err;

	mm->stack_vm = mm->total_vm = 1;
	arch_bprm_mm_init(mm, vma);
	up_write(&mm->mmap_sem);
	bprm->p = vma->vm_end - sizeof(void *);
	return 0;
err:
	up_write(&mm->mmap_sem);
err_free:
	bprm->vma = NULL;
	kmem_cache_free(vm_area_cachep, vma);
	return err;
}

bprm_mm_init()によってbprm->mmを設定する。struct mm_structはプロセスのメモリ領域を管理するための構造体のようだ。そして__bprm_mm_init()によってbprm->mmとbprm->vmaの設定を行う。struct vm_area_structは仮想メモリを管理する構造体であり、複数のvm_area_structが一つのmm_structに紐付くような形を取る(らしい)。コメントにあるように、これは一時的なスタック領域の割り当てであり、後にsetup_arg_pages()によって更新するらしい。これが終わるとbprm->pに設定したvma->vm_end(STACK_TOP_MAX)からポインタ一個分小さいアドレスを格納する。STACK_TOP_MAXは番兵的な役割を果たすのだろうか?なお、bprm->pは後でプロセスのスタック領域の開始地点を指すことになる。

bprm->argc = count(argv, MAX_ARG_STRINGS);
if ((retval = bprm->argc) < 0)
	goto out;

bprm->envc = count(envp, MAX_ARG_STRINGS);
if ((retval = bprm->envc) < 0)
	goto out;

argvとenvpの個数をそれぞれbprm->argcとbprm->envcに格納する。ちなみにMAX_ARG_STRINGSは0x7FFFFFFFとなっていた。

retval = prepare_binprm(bprm);
if (retval < 0)
	goto out;
/*
 * Fill the binprm structure from the inode.
 * Check permissions, then read the first 128 (BINPRM_BUF_SIZE) bytes
 *
 * This may be called multiple times for binary chains (scripts for example).
 */
int prepare_binprm(struct linux_binprm *bprm)
{
	int retval;
	loff_t pos = 0;

	bprm_fill_uid(bprm);

	/* fill in binprm security blob */
	retval = security_bprm_set_creds(bprm);
	if (retval)
		return retval;
	bprm->called_set_creds = 1;

	memset(bprm->buf, 0, BINPRM_BUF_SIZE);
	return kernel_read(bprm->file, bprm->buf, BINPRM_BUF_SIZE, &pos);
}

prepare_binprm()によりbprm->credにeffective UIDとeffective GIDの情報がセットされる。正直この辺何も分からん。
https://www.kernel.org/doc/Documentation/security/credentials.txt

その後、security_bprm_set_creds()によってbprm_set_credsリストに追加されたLSMのhook処理が呼び出される。AppArmor、SELinux、SMACK、TOMOYO Linuxなどが利用できるようだ。これが終わるとbprm->called_set_credsフラグが1となる。

ssize_t kernel_read(struct file *file, void *buf, size_t count, loff_t *pos)
{
	mm_segment_t old_fs;
	ssize_t result;

	old_fs = get_fs();
	set_fs(get_ds());
	/* The cast to a user pointer is valid due to the set_fs() */
	result = vfs_read(file, (void __user *)buf, count, pos);
	set_fs(old_fs);
	return result;
}
/* sizeof(linux_binprm->buf) */
#define BINPRM_BUF_SIZE 128

credentialsの設定が終わるとkernel_read()によってbprm->fileが指すファイルの先頭から128byteがbprm->bufに読み出される。kernel_read()はfs/read_write.cで定義されている。先頭128byteのみを読み込むのは実行ファイルがバイナリ(a.outやELF)なのかスクリプト(#!で始まるテキストファイル)なのかを以降の処理で判断するため。

retval = copy_strings_kernel(1, &bprm->filename, bprm);
if (retval < 0)
	goto out;

bprm->exec = bprm->p;
retval = copy_strings(bprm->envc, envp, bprm);
if (retval < 0)
	goto out;

retval = copy_strings(bprm->argc, argv, bprm);
if (retval < 0)
	goto out;

bprmに環境変数と引数の設定をしてく。copy_strings_kernel()およびcopy_strings()のプロトタイプは以下のようになっている。

/*
 * Like copy_strings, but get argv and its values from kernel memory.
 */
int copy_strings_kernel(int argc, const char *const *__argv,
			struct linux_binprm *bprm)
/*
 * 'copy_strings()' copies argument/environment strings from the old
 * processes's memory to the new process's stack.  The call to get_user_pages()
 * ensures the destination page is created and not swapped out.
 */
static int copy_strings(int argc, struct user_arg_ptr argv,
			struct linux_binprm *bprm)

copy_strings()は古いプロセスのメモリから新しいプロセスのスタックへargvとenvpのコピーを行う。copy_strings_kernel()はcopy_strings()と同等の処理を行うが、argvがユーザーランドのメモリ領域ではなくchar型のポインタとなっている点が異なる。

まず、一番最初のcopy_strings_kernel()によりfilenameをbprmに設定する。よく分かってないがargv[0]の差し替えをしているんだと思う。その後のbprm->pはファイル名を指しているため、bprm->execにコピーしているらしい。これが終わるとenvpとargvをそれぞれコピーしていく。

would_dump(bprm, bprm->file);
void would_dump(struct linux_binprm *bprm, struct file *file)
{
	struct inode *inode = file_inode(file);
	if (inode_permission(inode, MAY_READ) < 0) {
		struct user_namespace *old, *user_ns;
		bprm->interp_flags |= BINPRM_FLAGS_ENFORCE_NONDUMP;

		/* Ensure mm->user_ns contains the executable */
		user_ns = old = bprm->mm->user_ns;
		while ((user_ns != &init_user_ns) &&
		       !privileged_wrt_inode_uidgid(user_ns, inode))
			user_ns = user_ns->parent;

		if (old != user_ns) {
			bprm->mm->user_ns = get_user_ns(user_ns);
			put_user_ns(old);
		}
	}
}

would_dump()はbprm->fileのパーミッションを調べ、読み込み権限であればbprm->interp_flagsにBINPRM_FLAGS_ENFORCE_NONDUMPを立てる。その後、bprm->mm->user_nsからprivileged_wrt_inode_uidgid(user_ns, inode)がtrueとなるまでparentを辿り続ける。よく分かってない。
個々までの処理でbprmの設定が終了する。以降は実行ファイルの種類ごとに処理が異なる。

retval = exec_binprm(bprm);
if (retval < 0)
	goto out;
static int exec_binprm(struct linux_binprm *bprm)
{
	pid_t old_pid, old_vpid;
	int ret;

	/* Need to fetch pid before load_binary changes it */
	old_pid = current->pid;
	rcu_read_lock();
	old_vpid = task_pid_nr_ns(current, task_active_pid_ns(current->parent));
	rcu_read_unlock();

	ret = search_binary_handler(bprm);
	if (ret >= 0) {
		audit_bprm(bprm);
		trace_sched_process_exec(current, old_pid, bprm);
		ptrace_event(PTRACE_EVENT_EXEC, old_vpid);
		proc_exec_connector(current);
	}

	return ret;
}
#define printable(c) (((c)=='\t') || ((c)=='\n') || (0x20<=(c) && (c)<=0x7e))
/*
 * cycle the list of binary formats handler, until one recognizes the image
 */
int search_binary_handler(struct linux_binprm *bprm)
{
	bool need_retry = IS_ENABLED(CONFIG_MODULES);
	struct linux_binfmt *fmt;
	int retval;

	/* This allows 4 levels of binfmt rewrites before failing hard. */
	if (bprm->recursion_depth > 5)
		return -ELOOP;

	retval = security_bprm_check(bprm);
	if (retval)
		return retval;

	retval = -ENOENT;
 retry:
	read_lock(&binfmt_lock);
	list_for_each_entry(fmt, &formats, lh) {
		if (!try_module_get(fmt->module))
			continue;
		read_unlock(&binfmt_lock);
		bprm->recursion_depth++;
		retval = fmt->load_binary(bprm);
		read_lock(&binfmt_lock);
		put_binfmt(fmt);
		bprm->recursion_depth--;
		if (retval < 0 && !bprm->mm) {
			/* we got to flush_old_exec() and failed after it */
			read_unlock(&binfmt_lock);
			force_sigsegv(SIGSEGV, current);
			return retval;
		}
		if (retval != -ENOEXEC || !bprm->file) {
			read_unlock(&binfmt_lock);
			return retval;
		}
	}
	read_unlock(&binfmt_lock);

	if (need_retry) {
		if (printable(bprm->buf[0]) && printable(bprm->buf[1]) &&
		    printable(bprm->buf[2]) && printable(bprm->buf[3]))
			return retval;
		if (request_module("binfmt-%04x", *(ushort *)(bprm->buf + 2)) < 0)
			return retval;
		need_retry = false;
		goto retry;
	}

	return retval;
}

exec_binprm()により、実際にbprmを元にexecの処理が行われる。search_binary_handler()ではまずbprm->recursion_depthを調べ、規定の回数よりも再帰呼び出しされている場合は処理を修了する。次に、security_bprm_check()によってbprm_check_securityリストに追加されたLSMのhook処理が呼び出される。上で見たsecurity_bprm_set_creds()はcredentials関係だと思われるため、こちらは純粋にexecの実行権限をチェックしているのだろうか。ちなみにTOMOYO Linuxのみ利用可能なようだ。これらのチェックが終わるとformatsリストに登録されたハンドラ(fmt->load_binary)を次々と呼び出し、いずれかのハンドラにファイルがマッチするまでこの処理を続ける。各ハンドラはELFやa.outといったフォーマットに対応しており、現段階ではa.out、IA32 a.out、ELF、FDPIC ELF、EM86、FLAT、Scriptに対応している。知らないものばかりだが、今回はELFを対象に処理を追っていく。

ELF用のハンドラはfs/binfmt_elf.cで定義されている。formatsリストへの追加はregister_binfmt()により行われる。

static int __init init_elf_binfmt(void)
{
	register_binfmt(&elf_format);
	return 0;
}
static struct linux_binfmt elf_format = {
	.module		= THIS_MODULE,
	.load_binary	= load_elf_binary,
	.load_shlib	= load_elf_library,
	.core_dump	= elf_core_dump,
	.min_coredump	= ELF_EXEC_PAGESIZE,
};

struct linux_binfmtのうち、load_binary、load_shlib、core_dump、min_coredumpは関数ポインタとなっている。ここではfmt->load_binaryを見るため、load_elf_binaryを追っていく。

struct {
	struct elfhdr elf_ex;
	struct elfhdr interp_elf_ex;
} *loc;
// 〜
loc = kmalloc(sizeof(*loc), GFP_KERNEL);
if (!loc) {
	retval = -ENOMEM;
	goto out_ret;
}
// 〜
/* Get the exec-header */
loc->elf_ex = *((struct elfhdr *)bprm->buf);

まず、無名構造体locを初期化し、loc->elf_exに先程bprm->bufに読み込んだファイルの中身(の先頭128byte)をコピーする。

retval = -ENOEXEC;
/* First of all, some simple consistency checks */
if (memcmp(loc->elf_ex.e_ident, ELFMAG, SELFMAG) != 0)
	goto out;

if (loc->elf_ex.e_type != ET_EXEC && loc->elf_ex.e_type != ET_DYN)
	goto out;
if (!elf_check_arch(&loc->elf_ex))
	goto out;
if (!bprm->file->f_op->mmap)
	goto out;

loc->elf_exにファイルの中身をコピーしたので、ELFヘッダのマジックナンバー(0x7F 0x45 0x4C 0x46)の比較や実行形式のチェック、そしてアーキテクチャごとのチェックを行い、mmapが定義されていることをチェックする。アーキテクチャごとのチェックを行うelf_check_arch()はx86_64ではelf_ex.e_machineがEM_X86_64かどうかのみチェックしている。

elf_phdata = load_elf_phdrs(&loc->elf_ex, bprm->file);
if (!elf_phdata)
	goto out;
/**
 * load_elf_phdrs() - load ELF program headers
 * @elf_ex:   ELF header of the binary whose program headers should be loaded
 * @elf_file: the opened ELF binary file
 *
 * Loads ELF program headers from the binary file elf_file, which has the ELF
 * header pointed to by elf_ex, into a newly allocated array. The caller is
 * responsible for freeing the allocated data. Returns an ERR_PTR upon failure.
 */
static struct elf_phdr *load_elf_phdrs(struct elfhdr *elf_ex,
				       struct file *elf_file)
{
	struct elf_phdr *elf_phdata = NULL;
	int retval, size, err = -1;
	loff_t pos = elf_ex->e_phoff;

	/*
	 * If the size of this structure has changed, then punt, since
	 * we will be doing the wrong thing.
	 */
	if (elf_ex->e_phentsize != sizeof(struct elf_phdr))
		goto out;

	/* Sanity check the number of program headers... */
	if (elf_ex->e_phnum < 1 ||
		elf_ex->e_phnum > 65536U / sizeof(struct elf_phdr))
		goto out;

	/* ...and their total size. */
	size = sizeof(struct elf_phdr) * elf_ex->e_phnum;
	if (size > ELF_MIN_ALIGN)
		goto out;

	elf_phdata = kmalloc(size, GFP_KERNEL);
	if (!elf_phdata)
		goto out;

	/* Read in the program headers */
	retval = kernel_read(elf_file, elf_phdata, size, &pos);
	if (retval != size) {
		err = (retval < 0) ? retval : -EIO;
		goto out;
	}

	/* Success! */
	err = 0;
out:
	if (err) {
		kfree(elf_phdata);
		elf_phdata = NULL;
	}
	return elf_phdata;
}

ELFヘッダの情報を元にプログラムヘッダを読み込む。load_elf_phdrs()は細かい部分を省けばelf_ex->e_phoffからsizeof(struct elf_phdr) * elf_ex->e_phnumバイトをbprm->fileより読み出し、その結果を返す関数のようだ。

elf_ppnt = elf_phdata;
elf_bss = 0;
elf_brk = 0;

start_code = ~0UL;
end_code = 0;
start_data = 0;
end_data = 0;

各種メモリ空間と対応する変数を初期化する。elf_ppntは現在見ているプログラムヘッダテーブルの要素を指しており、以降の処理で継続的に変更される。

for (i = 0; i < loc->elf_ex.e_phnum; i++) {
	if (elf_ppnt->p_type == PT_INTERP) {
		/* This is the program interpreter used for
		 * shared libraries - for now assume that this
		 * is an a.out format binary
		 */
		retval = -ENOEXEC;
		if (elf_ppnt->p_filesz > PATH_MAX || 
		    elf_ppnt->p_filesz < 2)
			goto out_free_ph;

		retval = -ENOMEM;
		elf_interpreter = kmalloc(elf_ppnt->p_filesz,
					  GFP_KERNEL);
		if (!elf_interpreter)
			goto out_free_ph;

		pos = elf_ppnt->p_offset;
		retval = kernel_read(bprm->file, elf_interpreter,
				     elf_ppnt->p_filesz, &pos);
		if (retval != elf_ppnt->p_filesz) {
			if (retval >= 0)
				retval = -EIO;
			goto out_free_interp;
		}
		/* make sure path is NULL terminated */
		retval = -ENOEXEC;
		if (elf_interpreter[elf_ppnt->p_filesz - 1] != '\0')
			goto out_free_interp;

		interpreter = open_exec(elf_interpreter);
		retval = PTR_ERR(interpreter);
		if (IS_ERR(interpreter))
			goto out_free_interp;

		/*
		 * If the binary is not readable then enforce
		 * mm->dumpable = 0 regardless of the interpreter's
		 * permissions.
		 */
		would_dump(bprm, interpreter);

		/* Get the exec headers */
		pos = 0;
		retval = kernel_read(interpreter, &loc->interp_elf_ex,
				     sizeof(loc->interp_elf_ex), &pos);
		if (retval != sizeof(loc->interp_elf_ex)) {
			if (retval >= 0)
				retval = -EIO;
			goto out_free_dentry;
		}

		break;
	}
	elf_ppnt++;
}

プログラムヘッダテーブルを舐めていき、PT_INTERPである場合にインタプリタをloc->interp_elf_exに読み込む処理を行っている。まず、elf_interpreterには/lib/ld-linux.so.2といったインタプリタのファイル名が格納される。そしてopen_exec()によりstruct file型のinterpreterに読み出され、kernel_read()によってファイルの中身が読み出される。ここで、読み込まれるサイズはsizeof(loc->interp_elf_ex)、すなわちsizeof(struct elfhdr)となっていることに注意したい。そしてこの処理が終わるとfor文はbreakするため、PT_INTERPなタイプのプログラムヘッダが2個以上あった場合は2個目以降は無視される。

elf_ppnt = elf_phdata;
for (i = 0; i < loc->elf_ex.e_phnum; i++, elf_ppnt++)
	switch (elf_ppnt->p_type) {
	case PT_GNU_STACK:
		if (elf_ppnt->p_flags & PF_X)
			executable_stack = EXSTACK_ENABLE_X;
		else
			executable_stack = EXSTACK_DISABLE_X;
		break;

	case PT_LOPROC ... PT_HIPROC:
		retval = arch_elf_pt_proc(&loc->elf_ex, elf_ppnt,
					  bprm->file, false,
					  &arch_state);
		if (retval)
			goto out_free_dentry;
		break;
	}

何だこの気持ち悪いぶら下がりfor文は。再度プログラムヘッダテーブル舐め、execstackの設定とプロセッサ特有の処理を行う。 PT_LOPROC ... PT_HIPROCはプロセッサ特有の処理を行うための予約タイプであり、arch_elf_pt_proc()は見た限りではMIPSとs390のみ対応しているようなので無視する。

/* Some simple consistency checks for the interpreter */
if (elf_interpreter) {
	retval = -ELIBBAD;
	/* Not an ELF interpreter */
	if (memcmp(loc->interp_elf_ex.e_ident, ELFMAG, SELFMAG) != 0)
		goto out_free_dentry;
	/* Verify the interpreter has a valid arch */
	if (!elf_check_arch(&loc->interp_elf_ex))
		goto out_free_dentry;

	/* Load the interpreter program headers */
	interp_elf_phdata = load_elf_phdrs(&loc->interp_elf_ex,
					   interpreter);
	if (!interp_elf_phdata)
		goto out_free_dentry;

	/* Pass PT_LOPROC..PT_HIPROC headers to arch code */
	elf_ppnt = interp_elf_phdata;
	for (i = 0; i < loc->interp_elf_ex.e_phnum; i++, elf_ppnt++)
		switch (elf_ppnt->p_type) {
		case PT_LOPROC ... PT_HIPROC:
			retval = arch_elf_pt_proc(&loc->interp_elf_ex,
						  elf_ppnt, interpreter,
						  true, &arch_state);
			if (retval)
				goto out_free_dentry;
			break;
		}
}

elf_interpreterがNULLでない場合にインタプリタのチェックを行う。ELFヘッダのマジックナンバーのチェックを行い、アーキテクチャごとのチェックを行う。上で行ったバリデーションのうちのいくつか(e_typeとmmapの有無)が抜けてるように見えるが大丈夫なのだろうか。その後、interp_elf_phdataにインタプリタのプログラムヘッダテーブルを読み込み、先ほどと同じくプロセッサ特有の処理を行う。

/*
 * Allow arch code to reject the ELF at this point, whilst it's
 * still possible to return an error to the code that invoked
 * the exec syscall.
 */
retval = arch_check_elf(&loc->elf_ex,
			!!interpreter, &loc->interp_elf_ex,
			&arch_state);
if (retval)
	goto out_free_dentry;

arch_check_elf()はアーキテクチャ固有の処理を行うらしいが、先ほどと同じくMIPSとs390のみ定義されているようなので飛ばす。コメントにはまだexecシステムコールを発行したプログラムにエラーを返すことができる地点だと書いてある。

/* Flush all traces of the currently running executable */
retval = flush_old_exec(bprm);
if (retval)
	goto out_free_dentry;
/*
 * Calling this is the point of no return. None of the failures will be
 * seen by userspace since either the process is already taking a fatal
 * signal (via de_thread() or coredump), or will have SEGV raised
 * (after exec_mmap()) by search_binary_handlers (see below).
 */
int flush_old_exec(struct linux_binprm * bprm)
{
	int retval;

	/*
	 * Make sure we have a private signal table and that
	 * we are unassociated from the previous thread group.
	 */
	retval = de_thread(current);
	if (retval)
		goto out;

	/*
	 * Must be called _before_ exec_mmap() as bprm->mm is
	 * not visibile until then. This also enables the update
	 * to be lockless.
	 */
	set_mm_exe_file(bprm->mm, bprm->file);

	/*
	 * Release all of the old mmap stuff
	 */
	acct_arg_size(bprm, 0);
	retval = exec_mmap(bprm->mm);
	if (retval)
		goto out;

	/*
	 * After clearing bprm->mm (to mark that current is using the
	 * prepared mm now), we have nothing left of the original
	 * process. If anything from here on returns an error, the check
	 * in search_binary_handler() will SEGV current.
	 */
	bprm->mm = NULL;

	set_fs(USER_DS);
	current->flags &= ~(PF_RANDOMIZE | PF_FORKNOEXEC | PF_KTHREAD |
					PF_NOFREEZE | PF_NO_SETAFFINITY);
	flush_thread();
	current->personality &= ~bprm->per_clear;

	/*
	 * We have to apply CLOEXEC before we change whether the process is
	 * dumpable (in setup_new_exec) to avoid a race with a process in userspace
	 * trying to access the should-be-closed file descriptors of a process
	 * undergoing exec(2).
	 */
	do_close_on_exec(current->files);
	return 0;

out:
	return retval;
}

flush_old_exec()によって現在実行しているプロセスの情報が削除される。コメントにあるように、この関数の呼び出し以降はpoint of no returnとなっており、今まで設定してきたbprmを実際にメモリに反映していく処理が始まる。まず、de_thread()によりcurrentタスクの属するスレッドグループのスレッドを全てkillする。

/*
 * Must be called _before_ exec_mmap() as bprm->mm is
 * not visibile until then. This also enables the update
 * to be lockless.
 */
set_mm_exe_file(bprm->mm, bprm->file);
/**
 * set_mm_exe_file - change a reference to the mm's executable file
 *
 * This changes mm's executable file (shown as symlink /proc/[pid]/exe).
 *
 * Main users are mmput() and sys_execve(). Callers prevent concurrent
 * invocations: in mmput() nobody alive left, in execve task is single
 * threaded. sys_prctl(PR_SET_MM_MAP/EXE_FILE) also needs to set the
 * mm->exe_file, but does so without using set_mm_exe_file() in order
 * to do avoid the need for any locks.
 */
void set_mm_exe_file(struct mm_struct *mm, struct file *new_exe_file)
{
	struct file *old_exe_file;

	/*
	 * It is safe to dereference the exe_file without RCU as
	 * this function is only called if nobody else can access
	 * this mm -- see comment above for justification.
	 */
	old_exe_file = rcu_dereference_raw(mm->exe_file);

	if (new_exe_file)
		get_file(new_exe_file);
	rcu_assign_pointer(mm->exe_file, new_exe_file);
	if (old_exe_file)
		fput(old_exe_file);
}

set_mm_exe_file()によりbprm->mm->exe_fileをbprm->fileに差し替える。コメントにあるように、これによって実行ファイルへのシンボリックリンクである/proc/[pid]/exeが差し替わる。

/*
 * Release all of the old mmap stuff
 */
acct_arg_size(bprm, 0);
retval = exec_mmap(bprm->mm);
if (retval)
	goto out;
/*
 * The nascent bprm->mm is not visible until exec_mmap() but it can
 * use a lot of memory, account these pages in current->mm temporary
 * for oom_badness()->get_mm_rss(). Once exec succeeds or fails, we
 * change the counter back via acct_arg_size(0).
 */
static void acct_arg_size(struct linux_binprm *bprm, unsigned long pages)
{
	struct mm_struct *mm = current->mm;
	long diff = (long)(pages - bprm->vma_pages);

	if (!mm || !diff)
		return;

	bprm->vma_pages = pages;
	add_mm_counter(mm, MM_ANONPAGES, diff);
}

次にcurrent->mmを解放していく。まずはacct_arg_size()によりbprm->vma_pagesを0にし、current->mm->rss_stat.count[MM_ANONPAGES]から今までのbprm->vma_pagesを引く。MM_ANONPAGESはanonymous pagesの略のようだ。正直良く分からん。

static int exec_mmap(struct mm_struct *mm)
{
	struct task_struct *tsk;
	struct mm_struct *old_mm, *active_mm;

	/* Notify parent that we're no longer interested in the old VM */
	tsk = current;
	old_mm = current->mm;
	mm_release(tsk, old_mm);

	if (old_mm) {
		sync_mm_rss(old_mm);
		/*
		 * Make sure that if there is a core dump in progress
		 * for the old mm, we get out and die instead of going
		 * through with the exec.  We must hold mmap_sem around
		 * checking core_state and changing tsk->mm.
		 */
		down_read(&old_mm->mmap_sem);
		if (unlikely(old_mm->core_state)) {
			up_read(&old_mm->mmap_sem);
			return -EINTR;
		}
	}
	task_lock(tsk);
	active_mm = tsk->active_mm;
	tsk->mm = mm;
	tsk->active_mm = mm;
	activate_mm(active_mm, mm);
	tsk->mm->vmacache_seqnum = 0;
	vmacache_flush(tsk);
	task_unlock(tsk);
	if (old_mm) {
		up_read(&old_mm->mmap_sem);
		BUG_ON(active_mm != old_mm);
		setmax_mm_hiwater_rss(&tsk->signal->maxrss, old_mm);
		mm_update_next_owner(old_mm);
		mmput(old_mm);
		return 0;
	}
	mmdrop(active_mm);
	return 0;
}

次にcurrent->mmをbprm->mmに切り替えていく。まず、mm_release()によってcurrent->mmが解放される(はず)。

/* Please note the differences between mmput and mm_release.
 * mmput is called whenever we stop holding onto a mm_struct,
 * error success whatever.
 *
 * mm_release is called after a mm_struct has been removed
 * from the current process.
 *
 * This difference is important for error handling, when we
 * only half set up a mm_struct for a new process and need to restore
 * the old one.  Because we mmput the new mm_struct before
 * restoring the old one. . .
 * Eric Biederman 10 January 1998
 */
void mm_release(struct task_struct *tsk, struct mm_struct *mm)
{
	// 〜

	/* Get rid of any cached register state */
	deactivate_mm(tsk, mm);

	// 〜
}
#ifdef CONFIG_X86_32
#define deactivate_mm(tsk, mm)			\
do {						\
	lazy_load_gs(0);			\
} while (0)
#else
#define deactivate_mm(tsk, mm)			\
do {						\
	load_gs_index(0);			\
	loadsegment(fs, 0);			\
} while (0)
#endif

deactivate_mm()ではgsレジスタとfsレジスタを設定しているようだ。この2つはプロテクトモードでも使用されるレジスタで、TLS(Thread Local Strage)な領域を保持するために使用している(らしい)。よく分からん。

assembly - What is the "FS"/"GS" register intended for? - Stack Overflow

void sync_mm_rss(struct mm_struct *mm)
{
	int i;

	for (i = 0; i < NR_MM_COUNTERS; i++) {
		if (current->rss_stat.count[i]) {
			add_mm_counter(mm, i, current->rss_stat.count[i]);
			current->rss_stat.count[i] = 0;
		}
	}
	current->rss_stat.events = 0;
}

その後、sync_mm_rss()によりcurrent->mm->rss_statにcurrent->rss_statを足し合わせ、current->rss_stat.count[i]を0に設定する。これが終わると差し替えに入る。exec_mmap()の一部を以下に再掲する。

task_lock(tsk);
active_mm = tsk->active_mm;
tsk->mm = mm;
tsk->active_mm = mm;
activate_mm(active_mm, mm);
tsk->mm->vmacache_seqnum = 0;
vmacache_flush(tsk);
task_unlock(tsk);
if (old_mm) {
	up_read(&old_mm->mmap_sem);
	BUG_ON(active_mm != old_mm);
	setmax_mm_hiwater_rss(&tsk->signal->maxrss, old_mm);
	mm_update_next_owner(old_mm);
	mmput(old_mm);
	return 0;
}
mmdrop(active_mm);

tskはcurrentを指している。current->mmとcurrent->active_mmをbprm->mmに差し替え、activate_mm()を呼び出す(activate_mm()は何やってるのかいまいち分からん)。その後、vmacacheを初期化し、今までcurrent->mmだったold_mmの後処理をして差し替えが終了する。

flush_old_exec()に戻ろう。

/*
 * After clearing bprm->mm (to mark that current is using the
 * prepared mm now), we have nothing left of the original
 * process. If anything from here on returns an error, the check
 * in search_binary_handler() will SEGV current.
 */
bprm->mm = NULL;

まず、用済みとなったbprm->mmはNULLを指すよう変更する。

set_fs(USER_DS);
current->flags &= ~(PF_RANDOMIZE | PF_FORKNOEXEC | PF_KTHREAD |
				PF_NOFREEZE | PF_NO_SETAFFINITY);
flush_thread();
current->personality &= ~bprm->per_clear;
#define USER_DS 	MAKE_MM_SEG(TASK_SIZE_MAX)

static inline void set_fs(mm_segment_t fs)
{
	current->thread.addr_limit = fs;
	/* On user-mode return, check fs is correct */
	set_thread_flag(TIF_FSCHECK);
}

まず、arch/x86/include/asm/uaccess.hで定義されているset_fs()によりcurrent->thread.addr_limitを変更する。その後、current->flagsからいくつかのフラグを下ろしているようだ。

void flush_thread(void)
{
	struct task_struct *tsk = current;

	flush_ptrace_hw_breakpoint(tsk);
	memset(tsk->thread.tls_array, 0, sizeof(tsk->thread.tls_array));

	fpu__clear(&tsk->thread.fpu);
}
/*
 * Release the user breakpoints used by ptrace
 */
void flush_ptrace_hw_breakpoint(struct task_struct *tsk)
{
	int i;
	struct thread_struct *t = &tsk->thread;

	for (i = 0; i < HBP_NUM; i++) {
		unregister_hw_breakpoint(t->ptrace_bps[i]);
		t->ptrace_bps[i] = NULL;
	}

	t->debugreg6 = 0;
	t->ptrace_dr7 = 0;
}
/*
 * Clear the FPU state back to init state.
 *
 * Called by sys_execve(), by the signal handler code and by various
 * error paths.
 */
void fpu__clear(struct fpu *fpu)
{
	WARN_ON_FPU(fpu != &current->thread.fpu); /* Almost certainly an anomaly */

	fpu__drop(fpu);

	/*
	 * Make sure fpstate is cleared and initialized.
	 */
	if (static_cpu_has(X86_FEATURE_FPU)) {
		preempt_disable();
		fpu__initialize(fpu);
		user_fpu_begin();
		copy_init_fpstate_to_fpregs();
		preempt_enable();
	}
}

次にarch/x86/kernel/process.cで定義されているflush_thread()によりTLSやFPUの初期化を行う。flush_ptrace_hw_breakpoint()はarch/x86/kernel/hw_breakpoint.cで、fpu__clear()はarch/x86/kernel/fpu/core.cでそれぞれ定義されている。
TLSはともかくFPUもper-threadな機構なのだろうか。

/*
 * We have to apply CLOEXEC before we change whether the process is
 * dumpable (in setup_new_exec) to avoid a race with a process in userspace
 * trying to access the should-be-closed file descriptors of a process
 * undergoing exec(2).
 */
do_close_on_exec(current->files);
void do_close_on_exec(struct files_struct *files)
{
	unsigned i;
	struct fdtable *fdt;

	/* exec unshares first */
	spin_lock(&files->file_lock);
	for (i = 0; ; i++) {
		unsigned long set;
		unsigned fd = i * BITS_PER_LONG;
		fdt = files_fdtable(files);
		if (fd >= fdt->max_fds)
			break;
		set = fdt->close_on_exec[i];
		if (!set)
			continue;
		fdt->close_on_exec[i] = 0;
		for ( ; set ; fd++, set >>= 1) {
			struct file *file;
			if (!(set & 1))
				continue;
			file = fdt->fd[fd];
			if (!file)
				continue;
			rcu_assign_pointer(fdt->fd[fd], NULL);
			__put_unused_fd(files, fd);
			spin_unlock(&files->file_lock);
			filp_close(file, files);
			cond_resched();
			spin_lock(&files->file_lock);
		}

	}
	spin_unlock(&files->file_lock);
}

次にclose-on-execの処理を行う。struct fdtableの構造がよく分かっていないが、fdt->close_on_exec[i]は32bitでfdの有無を管理しており、fdを持つ場合はfdt->fd[fd]からfileを参照でき、これをfilp_close(file, files)によってclose()している。多分。
これでflush_old_exec()の処理が終わる。load_elf_binary()に戻ろう。

/* Do this immediately, since STACK_TOP as used in setup_arg_pages
   may depend on the personality.  */
SET_PERSONALITY2(loc->elf_ex, &arch_state);
if (elf_read_implies_exec(loc->elf_ex, executable_stack))
	current->personality |= READ_IMPLIES_EXEC;

if (!(current->personality & ADDR_NO_RANDOMIZE) && randomize_va_space)
	current->flags |= PF_RANDOMIZE;
void set_personality_64bit(void)
{
	/* inherit personality from parent */

	/* Make sure to be in 64bit mode */
	clear_thread_flag(TIF_IA32);
	clear_thread_flag(TIF_ADDR32);
	clear_thread_flag(TIF_X32);
	/* Pretend that this comes from a 64bit execve */
	task_pt_regs(current)->orig_ax = __NR_execve;

	/* Ensure the corresponding mm is not marked. */
	if (current->mm)
		current->mm->context.ia32_compat = 0;

	/* TBD: overwrites user setup. Should have two bits.
	   But 64bit processes have always behaved this way,
	   so it's not too bad. The main problem is just that
	   32bit childs are affected again. */
	current->personality &= ~READ_IMPLIES_EXEC;
}

SET_PERSONALITY2()はMIPSのみ定義されており、x86では単にset_personality_64bit()の呼び出しになっている。

/*
 * An executable for which elf_read_implies_exec() returns TRUE will
 * have the READ_IMPLIES_EXEC personality flag set automatically.
 */
#define elf_read_implies_exec(ex, executable_stack)	\
	(executable_stack != EXSTACK_DISABLE_X)

x86ではexecutable_stackのチェックのみを行っている。

そしてrandomize_va_spaceが有効な場合にcurrent->flagsのPF_RANDOMIZEを立てる。randomize_va_spaceは/proc/sys/kernel/randomize_va_spaceにより制御される。

setup_new_exec(bprm);
void setup_new_exec(struct linux_binprm * bprm)
{
	/*
	 * Once here, prepare_binrpm() will not be called any more, so
	 * the final state of setuid/setgid/fscaps can be merged into the
	 * secureexec flag.
	 */
	bprm->secureexec |= bprm->cap_elevated;

	if (bprm->secureexec) {
		/* Make sure parent cannot signal privileged process. */
		current->pdeath_signal = 0;

		/*
		 * For secureexec, reset the stack limit to sane default to
		 * avoid bad behavior from the prior rlimits. This has to
		 * happen before arch_pick_mmap_layout(), which examines
		 * RLIMIT_STACK, but after the point of no return to avoid
		 * needing to clean up the change on failure.
		 */
		if (current->signal->rlim[RLIMIT_STACK].rlim_cur > _STK_LIM)
			current->signal->rlim[RLIMIT_STACK].rlim_cur = _STK_LIM;
	}

	arch_pick_mmap_layout(current->mm);

	current->sas_ss_sp = current->sas_ss_size = 0;

	/* Figure out dumpability. */
	if (bprm->interp_flags & BINPRM_FLAGS_ENFORCE_NONDUMP ||
	    bprm->secureexec)
		set_dumpable(current->mm, suid_dumpable);
	else
		set_dumpable(current->mm, SUID_DUMP_USER);

	arch_setup_new_exec();
	perf_event_exec();
	__set_task_comm(current, kbasename(bprm->filename), true);

	/* Set the new mm task size. We have to do that late because it may
	 * depend on TIF_32BIT which is only updated in flush_thread() on
	 * some architectures like powerpc
	 */
	current->mm->task_size = TASK_SIZE;

	/* An exec changes our domain. We are no longer part of the thread
	   group */
	current->self_exec_id++;
	flush_signal_handlers(current, 0);
}
void arch_pick_mmap_layout(struct mm_struct *mm)
{
	if (mmap_is_legacy())
		mm->get_unmapped_area = arch_get_unmapped_area;
	else
		mm->get_unmapped_area = arch_get_unmapped_area_topdown;

	arch_pick_mmap_base(&mm->mmap_base, &mm->mmap_legacy_base,
			arch_rnd(mmap64_rnd_bits), task_size_64bit(0));

#ifdef CONFIG_HAVE_ARCH_COMPAT_MMAP_BASES
	/*
	 * The mmap syscall mapping base decision depends solely on the
	 * syscall type (64-bit or compat). This applies for 64bit
	 * applications and 32bit applications. The 64bit syscall uses
	 * mmap_base, the compat syscall uses mmap_compat_base.
	 */
	arch_pick_mmap_base(&mm->mmap_compat_base, &mm->mmap_compat_legacy_base,
			arch_rnd(mmap32_rnd_bits), task_size_32bit());
#endif
}
/*
 * This function, called very early during the creation of a new
 * process VM image, sets up which VM layout function to use:
 */
static void arch_pick_mmap_base(unsigned long *base, unsigned long *legacy_base,
		unsigned long random_factor, unsigned long task_size)
{
	*legacy_base = mmap_legacy_base(random_factor, task_size);
	if (mmap_is_legacy())
		*base = *legacy_base;
	else
		*base = mmap_base(random_factor, task_size);
}

bprm->secureexecの設定をし、arch_pick_mmap_layout()によりcurrent->mm->get_unmapped_areaの設定を行う。また、arch_pick_mmap_base()によってmm->mmap_baseとmm->mmap_legacy_baseの設定を行う。

current->sas_ss_sp = current->sas_ss_sizeを0に設定する。よく分からんけどこれはsignal関係が使っている。

次に、bprm->interp_flagsをチェックし、BINPRM_FLAGS_ENFORCE_NONDUMPであった場合にset_dumpable()によりcurrent->mm->flagsを変更する。BINPRM_FLAGS_ENFORCE_NONDUMPはdo_execveat_common()でwould_dump()により設定されている。

/*
 * Called immediately after a successful exec.
 */
void arch_setup_new_exec(void)
{
	/* If cpuid was previously disabled for this task, re-enable it. */
	if (test_thread_flag(TIF_NOCPUID))
		enable_cpuid();
}

cpiudを再有功化する。何も分からん。

void perf_event_exec(void)
{
	struct perf_event_context *ctx;
	int ctxn;

	rcu_read_lock();
	for_each_task_context_nr(ctxn) {
		ctx = current->perf_event_ctxp[ctxn];
		if (!ctx)
			continue;

		perf_event_enable_on_exec(ctxn);

		perf_iterate_ctx(ctx, perf_event_addr_filters_exec, NULL,
				   true);
	}
	rcu_read_unlock();
}
/*
 * These functions flushes out all traces of the currently running executable
 * so that a new one can be started
 */

void __set_task_comm(struct task_struct *tsk, const char *buf, bool exec)
{
	task_lock(tsk);
	trace_task_rename(tsk, buf);
	strlcpy(tsk->comm, buf, sizeof(tsk->comm));
	task_unlock(tsk);
	perf_event_comm(tsk, exec);
}

perfコマンド関係だろうか。全く分からん。

/* Set the new mm task size. We have to do that late because it may
 * depend on TIF_32BIT which is only updated in flush_thread() on
 * some architectures like powerpc
 */
current->mm->task_size = TASK_SIZE;
#define TASK_SIZE		(test_thread_flag(TIF_ADDR32) ? \
					IA32_PAGE_OFFSET : TASK_SIZE_MAX)
#define TIF_ADDR32		29	/* 32-bit address space on 64 bits */
#define IA32_PAGE_OFFSET	((current->personality & ADDR_LIMIT_3GB) ? \
					0xc0000000 : 0xFFFFe000)

struct mm_structの定義元に併記されたコメントより、task_sizeメンバはタスクの仮想メモリ空間の大きさを表している。TIF_ADDR32マクロのコメントより、x86_64で32bitなアドレス空間を使用する場合のフラグであるため、おそらくi386なバイナリをamd64なプロセッサで動かす場合のフラグだろう。current->personalityにADDR_LIMIT_3GBを設定した箇所はおそらくないので、前のプロセスの情報を引き継いでいるのだろうか?そもそもADDR_LIMIT_3GBが有効に使われている場所が見当たらなかったので、TIF_ADDR32が有効な場合はおそらく常に0xFFFFe000となる。

/* An exec changes our domain. We are no longer part of the thread
   group */
current->self_exec_id++;
flush_signal_handlers(current, 0);
/*
 * Flush all handlers for a task.
 */

void
flush_signal_handlers(struct task_struct *t, int force_default)
{
	int i;
	struct k_sigaction *ka = &t->sighand->action[0];
	for (i = _NSIG ; i != 0 ; i--) {
		if (force_default || ka->sa.sa_handler != SIG_IGN)
			ka->sa.sa_handler = SIG_DFL;
		ka->sa.sa_flags = 0;
#ifdef __ARCH_HAS_SA_RESTORER
		ka->sa.sa_restorer = NULL;
#endif
		sigemptyset(&ka->sa.sa_mask);
		ka++;
	}
}

current->self_exec_idはスレッドグループのIDとなっている(多分)。その後、flush_signal_handlers()によりSIG_IGN以外のシグナルハンドラをSIG_DFLに変更していく。

install_exec_creds(bprm);
/*
 * install the new credentials for this executable
 */
void install_exec_creds(struct linux_binprm *bprm)
{
	security_bprm_committing_creds(bprm);

	commit_creds(bprm->cred);
	bprm->cred = NULL;

	/*
	 * Disable monitoring for regular users
	 * when executing setuid binaries. Must
	 * wait until new credentials are committed
	 * by commit_creds() above
	 */
	if (get_dumpable(current->mm) != SUID_DUMP_USER)
		perf_event_exit_task(current);
	/*
	 * cred_guard_mutex must be held at least to this point to prevent
	 * ptrace_attach() from altering our determination of the task's
	 * credentials; any time after this it may be unlocked.
	 */
	security_bprm_committed_creds(bprm);
	mutex_unlock(&current->signal->cred_guard_mutex);
}

credentials何も分からん。bprm->credを現在のタスクに適用する。その後、bprm->credはNULLにしておく。
これが終わるとスタック領域のランダマイズ処理を行う。

/* Do this so that we can load the interpreter, if need be.  We will
   change some of these later */
retval = setup_arg_pages(bprm, randomize_stack_top(STACK_TOP),
			 executable_stack);
if (retval < 0)
	goto out_free_dentry;

current->mm->start_stack = bprm->p;
/*
 * Finalizes the stack vm_area_struct. The flags and permissions are updated,
 * the stack is optionally relocated, and some extra space is added.
 */
int setup_arg_pages(struct linux_binprm *bprm,
		    unsigned long stack_top,
		    int executable_stack)
{
	unsigned long ret;
	unsigned long stack_shift;
	struct mm_struct *mm = current->mm;
	struct vm_area_struct *vma = bprm->vma;
	struct vm_area_struct *prev = NULL;
	unsigned long vm_flags;
	unsigned long stack_base;
	unsigned long stack_size;
	unsigned long stack_expand;
	unsigned long rlim_stack;

	stack_top = arch_align_stack(stack_top);
	stack_top = PAGE_ALIGN(stack_top);

	if (unlikely(stack_top < mmap_min_addr) ||
	    unlikely(vma->vm_end - vma->vm_start >= stack_top - mmap_min_addr))
		return -ENOMEM;

	stack_shift = vma->vm_end - stack_top;

	bprm->p -= stack_shift;
	mm->arg_start = bprm->p;

	if (bprm->loader)
		bprm->loader -= stack_shift;
	bprm->exec -= stack_shift;

	if (down_write_killable(&mm->mmap_sem))
		return -EINTR;

	vm_flags = VM_STACK_FLAGS;

	/*
	 * Adjust stack execute permissions; explicitly enable for
	 * EXSTACK_ENABLE_X, disable for EXSTACK_DISABLE_X and leave alone
	 * (arch default) otherwise.
	 */
	if (unlikely(executable_stack == EXSTACK_ENABLE_X))
		vm_flags |= VM_EXEC;
	else if (executable_stack == EXSTACK_DISABLE_X)
		vm_flags &= ~VM_EXEC;
	vm_flags |= mm->def_flags;
	vm_flags |= VM_STACK_INCOMPLETE_SETUP;

	ret = mprotect_fixup(vma, &prev, vma->vm_start, vma->vm_end,
			vm_flags);
	if (ret)
		goto out_unlock;
	BUG_ON(prev != vma);

	/* Move stack pages down in memory. */
	if (stack_shift) {
		ret = shift_arg_pages(vma, stack_shift);
		if (ret)
			goto out_unlock;
	}

	/* mprotect_fixup is overkill to remove the temporary stack flags */
	vma->vm_flags &= ~VM_STACK_INCOMPLETE_SETUP;

	stack_expand = 131072UL; /* randomly 32*4k (or 2*64k) pages */
	stack_size = vma->vm_end - vma->vm_start;
	/*
	 * Align this down to a page boundary as expand_stack
	 * will align it up.
	 */
	rlim_stack = rlimit(RLIMIT_STACK) & PAGE_MASK;

	if (stack_size + stack_expand > rlim_stack)
		stack_base = vma->vm_end - rlim_stack;
	else
		stack_base = vma->vm_start - stack_expand;

	current->mm->start_stack = bprm->p;
	ret = expand_stack(vma, stack_base);
	if (ret)
		ret = -EFAULT;

out_unlock:
	up_write(&mm->mmap_sem);
	return ret;
}
static unsigned long randomize_stack_top(unsigned long stack_top)
{
	unsigned long random_variable = 0;

	if (current->flags & PF_RANDOMIZE) {
		random_variable = get_random_long();
		random_variable &= STACK_RND_MASK;
		random_variable <<= PAGE_SHIFT;
	}

	return PAGE_ALIGN(stack_top) - random_variable;
}
#define STACK_TOP		TASK_SIZE_LOW
#define TASK_SIZE_LOW		(test_thread_flag(TIF_ADDR32) ? \
					IA32_PAGE_OFFSET : DEFAULT_MAP_WINDOW)
#define DEFAULT_MAP_WINDOW	((1UL << 47) - PAGE_SIZE)
#define PAGE_SHIFT		12
#define PAGE_SIZE		(_AC(1,UL) << PAGE_SHIFT)
#ifdef __ASSEMBLY__
#define _AC(X,Y)	X
#define _AT(T,X)	X
#else
#define __AC(X,Y)	(X##Y)
#define _AC(X,Y)	__AC(X,Y)
#define _AT(T,X)	((T)(X))
#endif

x86ではスタックは下方伸長するのでCONFIG_STACK_GROWSUPはfalseとなる。これを踏まえて上に貼ったコードはいくつかのプリプロセッサ命令(#ifdef〜#else〜#endif)を外している。
まず、setup_arg_pages()に渡す第二引数としてrandomize_stack_top(STACK_TOP)とし、ランダマイズ処理を行っている。x86_64の場合、STACK_TOPは0x7ffffffff000となる(実際に調べたので合ってるはず)。また、上で見たようにrandomize_va_spaceが有効な場合にのみcurrent->flagsのPF_RANDOMIZEフラグがtrueとなり、STACK_TOPに乱数が足し合わされることでスタック領域のランダマイズ処理が行われる。
次にsetup_arg_pages()を見ていく。第二引数で渡されたstack_topをアライメントし、そこからbprm->vma->vm_endから引いた値をstack_shiftとしている。一つのmm_structに対し複数のvm_area_structが双方向のリスト構造でひっつく形となるらしいが、bprm_mm_init()のコメントにあったように現段階では一時的なものであるため要素は一つとなっている。この時点でのvma->vm_endは__bprm_mm_init()で設定された値が格納されている。コードを以下に再掲する。

vma->vm_end = STACK_TOP_MAX;
vma->vm_start = vma->vm_end - PAGE_SIZE;

STACK_TOP_MAXはSTACK_TOPと同じく0x7ffffffff000となっている。このため、ASLRが無効な場合はstack_shiftは0となるようだ。なお、vma->vm_startは0x7fffffffe000となっている。どうやらstack_shiftが加減算されている変数はスタック領域のランダマイズに伴うシフトを行っているようだ。
次に、vm_flagsを適宜変更し、mprotect_fixupを呼び出す。おそらくスタック領域のランダマイズに伴うアクセス保護領域の変更だろう。
stack_sizeは0x1000(=4096)となる。rlimはresource limitの略だと思われるため、rlim_stackはスタックのリソースの限界値だろう。多分ulimit -sとかで変更する値。rlim_stackで境界値チェックを行った後はexpand_stack()によりスタック領域を拡張する。拡張と言っても実際は一時的な割り当て領域しか持っていないため、確保というのが正しそう。

current->mm->start_stack = bprm->p;
ret = expand_stack(vma, stack_base);
if (ret)
	ret = -EFAULT;

current->mm->start_stackにbprm->pを保持しておく。ASLRが無効な場合、bprm->pは__bprm_mm_init()で設定された値(STACK_TOP_MAX - sizeof(void *))が入っている。

int expand_stack(struct vm_area_struct *vma, unsigned long address)
{
	return expand_downwards(vma, address);
}
/*
 * vma is the first one with address < vma->vm_start.  Have to extend vma.
 */
int expand_downwards(struct vm_area_struct *vma,
				   unsigned long address)
{
	struct mm_struct *mm = vma->vm_mm;
	struct vm_area_struct *prev;
	int error;

	address &= PAGE_MASK;
	error = security_mmap_addr(address);
	if (error)
		return error;

	/* Enforce stack_guard_gap */
	prev = vma->vm_prev;
	/* Check that both stack segments have the same anon_vma? */
	if (prev && !(prev->vm_flags & VM_GROWSDOWN) &&
			(prev->vm_flags & (VM_WRITE|VM_READ|VM_EXEC))) {
		if (address - prev->vm_end < stack_guard_gap)
			return -ENOMEM;
	}

	/* We must make sure the anon_vma is allocated. */
	if (unlikely(anon_vma_prepare(vma)))
		return -ENOMEM;

	/*
	 * vma->vm_start/vm_end cannot change under us because the caller
	 * is required to hold the mmap_sem in read mode.  We need the
	 * anon_vma lock to serialize against concurrent expand_stacks.
	 */
	anon_vma_lock_write(vma->anon_vma);

	/* Somebody else might have raced and expanded it already */
	if (address < vma->vm_start) {
		unsigned long size, grow;

		size = vma->vm_end - address;
		grow = (vma->vm_start - address) >> PAGE_SHIFT;

		error = -ENOMEM;
		if (grow <= vma->vm_pgoff) {
			error = acct_stack_growth(vma, size, grow);
			if (!error) {
				/*
				 * vma_gap_update() doesn't support concurrent
				 * updates, but we only hold a shared mmap_sem
				 * lock here, so we need to protect against
				 * concurrent vma expansions.
				 * anon_vma_lock_write() doesn't help here, as
				 * we don't guarantee that all growable vmas
				 * in a mm share the same root anon vma.
				 * So, we reuse mm->page_table_lock to guard
				 * against concurrent vma expansions.
				 */
				spin_lock(&mm->page_table_lock);
				if (vma->vm_flags & VM_LOCKED)
					mm->locked_vm += grow;
				vm_stat_account(mm, vma->vm_flags, grow);
				anon_vma_interval_tree_pre_update_vma(vma);
				vma->vm_start = address;
				vma->vm_pgoff -= grow;
				anon_vma_interval_tree_post_update_vma(vma);
				vma_gap_update(vma);
				spin_unlock(&mm->page_table_lock);

				perf_event_mmap(vma);
			}
		}
	}
	anon_vma_unlock_write(vma->anon_vma);
	khugepaged_enter_vma_merge(vma, vma->vm_flags);
	validate_mm(mm);
	return error;
}

expand_downwards()はmm/mmap.cで定義されている。この時点ではbprm->vma->vm_prevはNULLであるため最初の処理は無視できる。下方伸長なのでメモリ領域はvma->vm_start方向に確保される。なのでvma->vm_startが既にstack_baseより小さい場合は伸長済みであると判断できる(伸長後はvma->vm_start < stack_base < vma->vm_endとなる。要はstack_baseがvma->vm_startとvma->vm_endの間になるようにする)。

/*
 * Verify that the stack growth is acceptable and
 * update accounting. This is shared with both the
 * grow-up and grow-down cases.
 */
static int acct_stack_growth(struct vm_area_struct *vma,
			     unsigned long size, unsigned long grow)
{
	struct mm_struct *mm = vma->vm_mm;
	unsigned long new_start;

	/* address space limit tests */
	if (!may_expand_vm(mm, vma->vm_flags, grow))
		return -ENOMEM;

	/* Stack limit test */
	if (size > rlimit(RLIMIT_STACK))
		return -ENOMEM;

	/* mlock limit tests */
	if (vma->vm_flags & VM_LOCKED) {
		unsigned long locked;
		unsigned long limit;
		locked = mm->locked_vm + grow;
		limit = rlimit(RLIMIT_MEMLOCK);
		limit >>= PAGE_SHIFT;
		if (locked > limit && !capable(CAP_IPC_LOCK))
			return -ENOMEM;
	}

	/* Check to ensure the stack will not grow into a hugetlb-only region */
	new_start = (vma->vm_flags & VM_GROWSUP) ? vma->vm_start :
			vma->vm_end - size;
	if (is_hugepage_only_range(vma->vm_mm, new_start, size))
		return -EFAULT;

	/*
	 * Overcommit..  This must be the final test, as it will
	 * update security statistics.
	 */
	if (security_vm_enough_memory_mm(mm, grow))
		return -ENOMEM;

	return 0;
}

acct_stack_growth()によりスタック伸長の可否を判断する。スタック伸長が可能であればvm_stat_account()によりcurrent->mmの保持するvm_area_structの統計情報を更新し、vma->vm_startにstack_baseを代入する。上下にあるanon_vma_interval_tree_pre_update_vma()とanon_vma_interval_tree_post_update_vma()は分からん。vm_area_structの要素をどんどん確保してリストを拡大していくものだと思ったが、そうではないようだ。
すっきりしないがこれでsetup_arg_pages()の処理が終わる。次が長いんだ。

/* Now we do a little grungy work by mmapping the ELF image into
   the correct location in memory. */
for(i = 0, elf_ppnt = elf_phdata;
    i < loc->elf_ex.e_phnum; i++, elf_ppnt++) {
	int elf_prot = 0, elf_flags;
	unsigned long k, vaddr;
	unsigned long total_size = 0;

	if (elf_ppnt->p_type != PT_LOAD)
		continue;

	if (unlikely (elf_brk > elf_bss)) {
		unsigned long nbyte;
            
		/* There was a PT_LOAD segment with p_memsz > p_filesz
		   before this one. Map anonymous pages, if needed,
		   and clear the area.  */
		retval = set_brk(elf_bss + load_bias,
				 elf_brk + load_bias,
				 bss_prot);
		if (retval)
			goto out_free_dentry;
		nbyte = ELF_PAGEOFFSET(elf_bss);
		if (nbyte) {
			nbyte = ELF_MIN_ALIGN - nbyte;
			if (nbyte > elf_brk - elf_bss)
				nbyte = elf_brk - elf_bss;
			if (clear_user((void __user *)elf_bss +
						load_bias, nbyte)) {
				/*
				 * This bss-zeroing can fail if the ELF
				 * file specifies odd protections. So
				 * we don't check the return value
				 */
			}
		}
	}

	if (elf_ppnt->p_flags & PF_R)
		elf_prot |= PROT_READ;
	if (elf_ppnt->p_flags & PF_W)
		elf_prot |= PROT_WRITE;
	if (elf_ppnt->p_flags & PF_X)
		elf_prot |= PROT_EXEC;

	elf_flags = MAP_PRIVATE | MAP_DENYWRITE | MAP_EXECUTABLE;

	vaddr = elf_ppnt->p_vaddr;
	/*
	 * If we are loading ET_EXEC or we have already performed
	 * the ET_DYN load_addr calculations, proceed normally.
	 */
	if (loc->elf_ex.e_type == ET_EXEC || load_addr_set) {
		elf_flags |= MAP_FIXED;
	} else if (loc->elf_ex.e_type == ET_DYN) {
		/*
		 * This logic is run once for the first LOAD Program
		 * Header for ET_DYN binaries to calculate the
		 * randomization (load_bias) for all the LOAD
		 * Program Headers, and to calculate the entire
		 * size of the ELF mapping (total_size). (Note that
		 * load_addr_set is set to true later once the
		 * initial mapping is performed.)
		 *
		 * There are effectively two types of ET_DYN
		 * binaries: programs (i.e. PIE: ET_DYN with INTERP)
		 * and loaders (ET_DYN without INTERP, since they
		 * _are_ the ELF interpreter). The loaders must
		 * be loaded away from programs since the program
		 * may otherwise collide with the loader (especially
		 * for ET_EXEC which does not have a randomized
		 * position). For example to handle invocations of
		 * "./ld.so someprog" to test out a new version of
		 * the loader, the subsequent program that the
		 * loader loads must avoid the loader itself, so
		 * they cannot share the same load range. Sufficient
		 * room for the brk must be allocated with the
		 * loader as well, since brk must be available with
		 * the loader.
		 *
		 * Therefore, programs are loaded offset from
		 * ELF_ET_DYN_BASE and loaders are loaded into the
		 * independently randomized mmap region (0 load_bias
		 * without MAP_FIXED).
		 */
		if (elf_interpreter) {
			load_bias = ELF_ET_DYN_BASE;
			if (current->flags & PF_RANDOMIZE)
				load_bias += arch_mmap_rnd();
			elf_flags |= MAP_FIXED;
		} else
			load_bias = 0;

		/*
		 * Since load_bias is used for all subsequent loading
		 * calculations, we must lower it by the first vaddr
		 * so that the remaining calculations based on the
		 * ELF vaddrs will be correctly offset. The result
		 * is then page aligned.
		 */
		load_bias = ELF_PAGESTART(load_bias - vaddr);

		total_size = total_mapping_size(elf_phdata,
						loc->elf_ex.e_phnum);
		if (!total_size) {
			retval = -EINVAL;
			goto out_free_dentry;
		}
	}

	error = elf_map(bprm->file, load_bias + vaddr, elf_ppnt,
			elf_prot, elf_flags, total_size);
	if (BAD_ADDR(error)) {
		retval = IS_ERR((void *)error) ?
			PTR_ERR((void*)error) : -EINVAL;
		goto out_free_dentry;
	}

	if (!load_addr_set) {
		load_addr_set = 1;
		load_addr = (elf_ppnt->p_vaddr - elf_ppnt->p_offset);
		if (loc->elf_ex.e_type == ET_DYN) {
			load_bias += error -
			             ELF_PAGESTART(load_bias + vaddr);
			load_addr += load_bias;
			reloc_func_desc = load_bias;
		}
	}
	k = elf_ppnt->p_vaddr;
	if (k < start_code)
		start_code = k;
	if (start_data < k)
		start_data = k;

	/*
	 * Check to see if the section's size will overflow the
	 * allowed task size. Note that p_filesz must always be
	 * <= p_memsz so it is only necessary to check p_memsz.
	 */
	if (BAD_ADDR(k) || elf_ppnt->p_filesz > elf_ppnt->p_memsz ||
	    elf_ppnt->p_memsz > TASK_SIZE ||
	    TASK_SIZE - elf_ppnt->p_memsz < k) {
		/* set_brk can never work. Avoid overflows. */
		retval = -EINVAL;
		goto out_free_dentry;
	}

	k = elf_ppnt->p_vaddr + elf_ppnt->p_filesz;

	if (k > elf_bss)
		elf_bss = k;
	if ((elf_ppnt->p_flags & PF_X) && end_code < k)
		end_code = k;
	if (end_data < k)
		end_data = k;
	k = elf_ppnt->p_vaddr + elf_ppnt->p_memsz;
	if (k > elf_brk) {
		bss_prot = elf_prot;
		elf_brk = k;
	}
}

プログラムヘッダテーブルから実際にメモリマッピングを行い、変数を初期化していく。ここで重要となる変数はelf_bss、elf_brk、start_code、end_code、start_data、end_dataの6個である。一番外のfor文で全プログラムヘッダテーブルを舐めていく。各処理を細かく追っていこう。

if (elf_ppnt->p_type != PT_LOAD)
	continue;

まず、プログラムヘッダのタイプがPT_LOADでない場合はセグメントに関する情報を持っていないため無視する。

if (unlikely (elf_brk > elf_bss)) {
	unsigned long nbyte;

	/* There was a PT_LOAD segment with p_memsz > p_filesz
	   before this one. Map anonymous pages, if needed,
	   and clear the area.  */
	retval = set_brk(elf_bss + load_bias,
			 elf_brk + load_bias,
			 bss_prot);
	if (retval)
		goto out_free_dentry;
	nbyte = ELF_PAGEOFFSET(elf_bss);
	if (nbyte) {
		nbyte = ELF_MIN_ALIGN - nbyte;
		if (nbyte > elf_brk - elf_bss)
			nbyte = elf_brk - elf_bss;
		if (clear_user((void __user *)elf_bss +
					load_bias, nbyte)) {
			/*
			 * This bss-zeroing can fail if the ELF
			 * file specifies odd protections. So
			 * we don't check the return value
			 */
		}
	}
}

次のfor文はelf_brk、elf_bssが共に0のため無視できる。ヒープセグメントがBSSセグメントよりも上位のアドレスに位置することがあるのだろうか。

if (elf_ppnt->p_flags & PF_R)
	elf_prot |= PROT_READ;
if (elf_ppnt->p_flags & PF_W)
	elf_prot |= PROT_WRITE;
if (elf_ppnt->p_flags & PF_X)
	elf_prot |= PROT_EXEC;

elf_flags = MAP_PRIVATE | MAP_DENYWRITE | MAP_EXECUTABLE;

vaddr = elf_ppnt->p_vaddr;

各種変数の初期化を行う。

/*
 * If we are loading ET_EXEC or we have already performed
 * the ET_DYN load_addr calculations, proceed normally.
 */
if (loc->elf_ex.e_type == ET_EXEC || load_addr_set) {
	elf_flags |= MAP_FIXED;
} else if (loc->elf_ex.e_type == ET_DYN) {
	/*
	 * This logic is run once for the first LOAD Program
	 * Header for ET_DYN binaries to calculate the
	 * randomization (load_bias) for all the LOAD
	 * Program Headers, and to calculate the entire
	 * size of the ELF mapping (total_size). (Note that
	 * load_addr_set is set to true later once the
	 * initial mapping is performed.)
	 *
	 * There are effectively two types of ET_DYN
	 * binaries: programs (i.e. PIE: ET_DYN with INTERP)
	 * and loaders (ET_DYN without INTERP, since they
	 * _are_ the ELF interpreter). The loaders must
	 * be loaded away from programs since the program
	 * may otherwise collide with the loader (especially
	 * for ET_EXEC which does not have a randomized
	 * position). For example to handle invocations of
	 * "./ld.so someprog" to test out a new version of
	 * the loader, the subsequent program that the
	 * loader loads must avoid the loader itself, so
	 * they cannot share the same load range. Sufficient
	 * room for the brk must be allocated with the
	 * loader as well, since brk must be available with
	 * the loader.
	 *
	 * Therefore, programs are loaded offset from
	 * ELF_ET_DYN_BASE and loaders are loaded into the
	 * independently randomized mmap region (0 load_bias
	 * without MAP_FIXED).
	 */
	if (elf_interpreter) {
		load_bias = ELF_ET_DYN_BASE;
		if (current->flags & PF_RANDOMIZE)
			load_bias += arch_mmap_rnd();
		elf_flags |= MAP_FIXED;
	} else
		load_bias = 0;

	/*
	 * Since load_bias is used for all subsequent loading
	 * calculations, we must lower it by the first vaddr
	 * so that the remaining calculations based on the
	 * ELF vaddrs will be correctly offset. The result
	 * is then page aligned.
	 */
	load_bias = ELF_PAGESTART(load_bias - vaddr);

	total_size = total_mapping_size(elf_phdata,
					loc->elf_ex.e_phnum);
	if (!total_size) {
		retval = -EINVAL;
		goto out_free_dentry;
	}
}

ELFヘッダのタイプとフラグ変数をチェックし、ET_EXECなファイルであればelf_flagsにMAP_FIXEDを立てる。これは書いてあるアドレスをその通りに解釈することを意味する(定義元にInterpret addr exactlyって書いてあった)。タイプがET_DYNである場合、ASLR用にオフセットの乱数に用いるload_biasとELFファイルをマッピングした際の総サイズを格納するtotal_sizeを初回の1度のみ計算する。ELFヘッダのe_typeがET_DYNであるバイナリファイルは二種類に区別できる。一つはPIE、もう一つはELFインタプリタである。前者はET_DYNかつINTERPセクションを持っており、後者はET_DYNだがINTERPを持たないことで区別することができる。ローダはメモリの衝突を回避するためにプログラムと離れた位置にマッピングする必要がある(特にET_EXECではプログラムの位置を変更することができないため)。例えば自作のローダをテストするために"./ld.so someprog"といった呼び出しを行った場合、ローダは自身と被らないようsomeprogをロードしなければならない。ld.soとsomeprogは同じメモリ範囲を共有することはできないのだ。また、ローダもbrkを使用することから、brkのための十分な空間が確保されていなければならない。これらの事情からプログラムはELF_ET_DYN_BASE以降のオフセットに配置され、ローダは独立してランダマイズされたmmap領域に配置される(この場合load_biasは0でMAP_FIXEDも立たない)。みたいなことがコメントに書いてある。多分。

if (elf_interpreter) {
	load_bias = ELF_ET_DYN_BASE;
	if (current->flags & PF_RANDOMIZE)
		load_bias += arch_mmap_rnd();
	elf_flags |= MAP_FIXED;
} else
	load_bias = 0;

elf_interpreterに値が入っている場合はファイルが(PT_)INTERPセクションを持っていたと判断できる。このため、このファイルはPIEな実行バイナリであり、load_biasにELF_ET_DYN_BASEと乱数を足し合わせた数値が格納される。ASLRが無効化されている場合は乱数を足し合わせずELF_ET_DYN_BASEをそのままload_biasとして用いる。そしてelf_flagsにMAP_FIXEDを立てる。elf_interpreterがNULLの場合はインタプリタであると判断してload_biasを0にしておく。

/*
 * Since load_bias is used for all subsequent loading
 * calculations, we must lower it by the first vaddr
 * so that the remaining calculations based on the
 * ELF vaddrs will be correctly offset. The result
 * is then page aligned.
 */
load_bias = ELF_PAGESTART(load_bias - vaddr);
#define ELF_PAGESTART(_v) ((_v) & ~(unsigned long)(ELF_MIN_ALIGN-1))
#if ELF_EXEC_PAGESIZE > PAGE_SIZE
#define ELF_MIN_ALIGN	ELF_EXEC_PAGESIZE
#else
#define ELF_MIN_ALIGN	PAGE_SIZE
#endif

ここで、vaddrにはプログラムヘッダテーブルを舐めて一番最初に見つけたPT_LOADなセクションの仮想アドレスが格納されているはず(このif文はload_addr_setフラグにより二回目以降は入らない)。load_biasは以降の全てのオフセットの計算に用いるため、最初に出現したvaddrによって下げることで、ELFのvaddrに基づく残りの計算では正しいオフセットが算出されるとのこと。何のこっちゃ。手元の環境ではET_DYNなオブジェクトの一番低位のvaddrは0x0だったのであまり気にしなくていいのかも?

total_size = total_mapping_size(elf_phdata,
				loc->elf_ex.e_phnum);
if (!total_size) {
	retval = -EINVAL;
	goto out_free_dentry;
}
static unsigned long total_mapping_size(struct elf_phdr *cmds, int nr)
{
	int i, first_idx = -1, last_idx = -1;

	for (i = 0; i < nr; i++) {
		if (cmds[i].p_type == PT_LOAD) {
			last_idx = i;
			if (first_idx == -1)
				first_idx = i;
		}
	}
	if (first_idx == -1)
		return 0;

	return cmds[last_idx].p_vaddr + cmds[last_idx].p_memsz -
				ELF_PAGESTART(cmds[first_idx].p_vaddr);
}

プログラムヘッダテーブルと要素数をtotal_mapping_size()に与え、total_sizeを計算する。cmds[last_idx].p_vaddrは最後のメモリ配置、すなわち最も上位のアドレスに位置する仮想アドレスの開始位置が格納されており、cmds[last_idx].p_memszにはそのセクションのサイズが格納されている。cmds[first_idx].p_vaddrには最初のメモリ配置、すなわち最も下位のアドレスに位置する仮想アドレスの開始位置が格納されているので、メモリマップの最初から最後までの合計を計算している。ELF_PAGESTART()は単にアライメントの考慮をしている(下位数bitを0にしてるだけ)。これでload_biasとtotal_sizeの計算が終わる。

error = elf_map(bprm->file, load_bias + vaddr, elf_ppnt,
		elf_prot, elf_flags, total_size);
if (BAD_ADDR(error)) {
	retval = IS_ERR((void *)error) ?
		PTR_ERR((void*)error) : -EINVAL;
	goto out_free_dentry;
}
static unsigned long elf_map(struct file *filep, unsigned long addr,
		struct elf_phdr *eppnt, int prot, int type,
		unsigned long total_size)
{
	unsigned long map_addr;
	unsigned long size = eppnt->p_filesz + ELF_PAGEOFFSET(eppnt->p_vaddr);
	unsigned long off = eppnt->p_offset - ELF_PAGEOFFSET(eppnt->p_vaddr);
	addr = ELF_PAGESTART(addr);
	size = ELF_PAGEALIGN(size);

	/* mmap() will return -EINVAL if given a zero size, but a
	 * segment with zero filesize is perfectly valid */
	if (!size)
		return addr;

	/*
	* total_size is the size of the ELF (interpreter) image.
	* The _first_ mmap needs to know the full size, otherwise
	* randomization might put this image into an overlapping
	* position with the ELF binary image. (since size < total_size)
	* So we first map the 'big' image - and unmap the remainder at
	* the end. (which unmap is needed for ELF images with holes.)
	*/
	if (total_size) {
		total_size = ELF_PAGEALIGN(total_size);
		map_addr = vm_mmap(filep, addr, total_size, prot, type, off);
		if (!BAD_ADDR(map_addr))
			vm_munmap(map_addr+size, total_size-size);
	} else
		map_addr = vm_mmap(filep, addr, size, prot, type, off);

	return(map_addr);
}
#define ELF_PAGESTART(_v) ((_v) & ~(unsigned long)(ELF_MIN_ALIGN-1))
#define ELF_PAGEOFFSET(_v) ((_v) & (ELF_MIN_ALIGN-1))
#define ELF_PAGEALIGN(_v) (((_v) + ELF_MIN_ALIGN - 1) & ~(ELF_MIN_ALIGN - 1))

elf_map()によって実際にセクションをメモリ上にマッピングしていく。elf_ppntは現在見ているPT_LOADなプログラムヘッダが格納されている。elf_protはプログラムヘッダのフラグに応じてPROT_READ、PROT_WRITE、PROT_EXECが格納されている。elf_flagsは(MAP_PRIVATE | MAP_DENYWRITE | MAP_EXECUTABLE)もしくは(MAP_PRIVATE | MAP_DENYWRITE | MAP_EXECUTABLE | MAP_FIXED)のどちらかが格納されている。前者はELFインタプリタ、後者はそれ以外(非PIEも含む)だ。
sizeはelf_ppntの指すプログラムヘッダのサイズと仮想アドレスのELF_MIN_ALIGNからのオフセットの合計を更にELF_MIN_ALIGNでアライメントした値が格納される。なお、手元の環境ではELF_MIN_ALIGNは0x1000となっていたので、仮想アドレスの下位12bitをプログラムヘッダのサイズに足し合わせ、更にその値の下位12bitのいずれかが1であれば繰り上げて0xfffでマスクされた範囲を削除するといった形となる(説明が分かりづらい)。offはelf_ppntの指すプログラムヘッダのファイルの開始位置からのオフセットより仮想アドレスのELF_MIN_ALIGNからのオフセットを差し引いた値が格納される。ただし大抵の場合、仮想アドレスの下位数bitとオフセットは一致するのでoffは0になる(はず)。
elf_map()はtotal_sizeの有無により動作が変わる。total_sizeが0というのはタイプがET_DYNでないELFファイル、すなわち位置独立でないコードの場合となる。total_sizeが0の場合は渡されたプログラムヘッダを元にバイナリファイルをそのままメモリへマッピングvm_mmap)する。total_sizeが0でない場合、すなわち位置独立の場合はまずtotal_size分のメモリ領域を(addrの位置から)mmapし、その後実際に使用する部分だけを残してmunmapする。これは最初のプログラムヘッダを元にメモリマッピングする際、ランダマイズにより重複した位置へマッピングしてしまう可能性があるためである、とコメントに書いてある。よく分からんけど最初にガッツリ確保しておかないと後々困るといった類の話なのだろうか。
なお、返り値をerrorという名前の変数に格納しているが、実際にはマッピングした仮想アドレスの開始位置が格納されているようだ。

if (!load_addr_set) {
	load_addr_set = 1;
	load_addr = (elf_ppnt->p_vaddr - elf_ppnt->p_offset);
	if (loc->elf_ex.e_type == ET_DYN) {
		load_bias += error -
		             ELF_PAGESTART(load_bias + vaddr);
		load_addr += load_bias;
		reloc_func_desc = load_bias;
	}
}

load_addr_setがfalseの場合、すなわち最初のループの場合の処理を行う。まずload_addr_setをtrueに変更し、次回以降はif文に入らないよう変更する。上で見たif文も同じく次回以降は通過しない。仮想アドレスからファイルのオフセットを引くことでload_addrを計算し、ELFヘッダのタイプがET_DYNであった場合はload_biasを再計算してload_addrに足し合わせる。また、reloc_func_descにload_biasを保持しておく。

k = elf_ppnt->p_vaddr;
if (k < start_code)
	start_code = k;
if (start_data < k)
	start_data = k;

/*
 * Check to see if the section's size will overflow the
 * allowed task size. Note that p_filesz must always be
 * <= p_memsz so it is only necessary to check p_memsz.
 */
if (BAD_ADDR(k) || elf_ppnt->p_filesz > elf_ppnt->p_memsz ||
   elf_ppnt->p_memsz > TASK_SIZE ||
    TASK_SIZE - elf_ppnt->p_memsz < k) {
	/* set_brk can never work. Avoid overflows. */
	retval = -EINVAL;
	goto out_free_dentry;
}

for文により各elf_ppntを見ていくので、start_codeが最下位の、start_dataが最上位の仮想アドレスの開始位置を指すように変更される。
その後、elf_ppnt->p_memszのサイズをチェックする。おまけでkが不正なアドレスでないかもチェックする。

k = elf_ppnt->p_vaddr + elf_ppnt->p_filesz;

if (k > elf_bss)
	elf_bss = k;
if ((elf_ppnt->p_flags & PF_X) && end_code < k)
	end_code = k;
if (end_data < k)
	end_data = k;

次はp_vaddrにp_fileszを足すので、kは各セクションの仮想アドレスの終端位置を指す。elf_bssは最上位の仮想アドレスの終端位置を指し、end_codeは実行権限のあるセクションの終端位置を指す。条件式がひっくり返っているだけでend_dataもelf_bssと同じ位置を指す。

k = elf_ppnt->p_vaddr + elf_ppnt->p_memsz;
if (k > elf_brk) {
	bss_prot = elf_prot;
	elf_brk = k;
}

次はp_vaddrにp_memszを足す。これにより、どうやらkはBSSセクションの終端位置を指すらしい。
stackoverflow.com
BSSセクションは容量の無駄なのでp_fileszでは削除されており、p_filesz <= p_memszの関係となっているようだ。BSSセクションの下(上位アドレス)にヒープ領域が位置するため、elf_brkは最上位のkを指すようにする。

loc->elf_ex.e_entry += load_bias;
elf_bss += load_bias;
elf_brk += load_bias;
start_code += load_bias;
end_code += load_bias;
start_data += load_bias;
end_data += load_bias;

上のfor文で初期化した変数にload_biasを足していく。また、エントリーポイントにもload_biasを足しておく。

/* Calling set_brk effectively mmaps the pages that we need
 * for the bss and break sections.  We must do this before
 * mapping in the interpreter, to make sure it doesn't wind
 * up getting placed where the bss needs to go.
 */
retval = set_brk(elf_bss, elf_brk, bss_prot);
if (retval)
	goto out_free_dentry;
if (likely(elf_bss != elf_brk) && unlikely(padzero(elf_bss))) {
	retval = -EFAULT; /* Nobody gets to see this, but.. */
	goto out_free_dentry;
}
static int set_brk(unsigned long start, unsigned long end, int prot)
{
	start = ELF_PAGEALIGN(start);
	end = ELF_PAGEALIGN(end);
	if (end > start) {
		/*
		 * Map the last of the bss segment.
		 * If the header is requesting these pages to be
		 * executable, honour that (ppc32 needs this).
		 */
		int error = vm_brk_flags(start, end - start,
				prot & PROT_EXEC ? VM_EXEC : 0);
		if (error)
			return error;
	}
	current->mm->start_brk = current->mm->brk = end;
	return 0;
}

elf_bssからelf_brkまでの領域をBSSセクションとして初期化する。BSSセクションに実行権限が必要なのだろうか?
初期化が終わるとcurrent->mm->start_brkとcurrent->mm->brkにBSSセクションの終わりの位置を格納する。ヒープ領域を要求されると逐次current->mm->brkが伸びていくのだろう。

if (elf_interpreter) {
	unsigned long interp_map_addr = 0;

	elf_entry = load_elf_interp(&loc->interp_elf_ex,
				    interpreter,
				    &interp_map_addr,
				    load_bias, interp_elf_phdata);
	if (!IS_ERR((void *)elf_entry)) {
		/*
		 * load_elf_interp() returns relocation
		 * adjustment
		 */
		interp_load_addr = elf_entry;
		elf_entry += loc->interp_elf_ex.e_entry;
	}
	if (BAD_ADDR(elf_entry)) {
		retval = IS_ERR((void *)elf_entry) ?
				(int)elf_entry : -EINVAL;
		goto out_free_dentry;
	}
	reloc_func_desc = interp_load_addr;

	allow_write_access(interpreter);
	fput(interpreter);
	kfree(elf_interpreter);
} else {
	elf_entry = loc->elf_ex.e_entry;
	if (BAD_ADDR(elf_entry)) {
		retval = -EINVAL;
		goto out_free_dentry;
	}
}

kfree(interp_elf_phdata);
kfree(elf_phdata);

elf_interpreterに値が入っている場合はload_elf_interp()により読み込む。当分前に出てきたのでもう一度おさらいすると、elf_interpreterはELFインタプリタのパスが格納されたchar型のポインタであり、interpreterはelf_interpreterをopenしたstruct file型のポインタである。そしてloc->interp_elf_exにはELFインタプリタ自身のELFヘッダが格納されており、interp_elf_phdataにはELFインタプリタ自身のプログラムヘッダテーブルが格納されている。elf_interpreterに値が入っているということは実行バイナリの動的ローダをメモリ上にマッピングする必要があるということだ。これを踏まえてload_elf_interp()を見ていこう。

/* This is much more generalized than the library routine read function,
   so we keep this separate.  Technically the library read function
   is only provided so that we can read a.out libraries that have
   an ELF header */

static unsigned long load_elf_interp(struct elfhdr *interp_elf_ex,
		struct file *interpreter, unsigned long *interp_map_addr,
		unsigned long no_base, struct elf_phdr *interp_elf_phdata)
{
	struct elf_phdr *eppnt;
	unsigned long load_addr = 0;
	int load_addr_set = 0;
	unsigned long last_bss = 0, elf_bss = 0;
	int bss_prot = 0;
	unsigned long error = ~0UL;
	unsigned long total_size;
	int i;

	/* First of all, some simple consistency checks */
	if (interp_elf_ex->e_type != ET_EXEC &&
	    interp_elf_ex->e_type != ET_DYN)
		goto out;
	if (!elf_check_arch(interp_elf_ex))
		goto out;
	if (!interpreter->f_op->mmap)
		goto out;

	total_size = total_mapping_size(interp_elf_phdata,
					interp_elf_ex->e_phnum);
	if (!total_size) {
		error = -EINVAL;
		goto out;
	}

	eppnt = interp_elf_phdata;
	for (i = 0; i < interp_elf_ex->e_phnum; i++, eppnt++) {
		if (eppnt->p_type == PT_LOAD) {
			int elf_type = MAP_PRIVATE | MAP_DENYWRITE;
			int elf_prot = 0;
			unsigned long vaddr = 0;
			unsigned long k, map_addr;

			if (eppnt->p_flags & PF_R)
		    		elf_prot = PROT_READ;
			if (eppnt->p_flags & PF_W)
				elf_prot |= PROT_WRITE;
			if (eppnt->p_flags & PF_X)
				elf_prot |= PROT_EXEC;
			vaddr = eppnt->p_vaddr;
			if (interp_elf_ex->e_type == ET_EXEC || load_addr_set)
				elf_type |= MAP_FIXED;
			else if (no_base && interp_elf_ex->e_type == ET_DYN)
				load_addr = -vaddr;

			map_addr = elf_map(interpreter, load_addr + vaddr,
					eppnt, elf_prot, elf_type, total_size);
			total_size = 0;
			if (!*interp_map_addr)
				*interp_map_addr = map_addr;
			error = map_addr;
			if (BAD_ADDR(map_addr))
				goto out;

			if (!load_addr_set &&
			    interp_elf_ex->e_type == ET_DYN) {
				load_addr = map_addr - ELF_PAGESTART(vaddr);
				load_addr_set = 1;
			}

			/*
			 * Check to see if the section's size will overflow the
			 * allowed task size. Note that p_filesz must always be
			 * <= p_memsize so it's only necessary to check p_memsz.
			 */
			k = load_addr + eppnt->p_vaddr;
			if (BAD_ADDR(k) ||
			    eppnt->p_filesz > eppnt->p_memsz ||
			    eppnt->p_memsz > TASK_SIZE ||
			    TASK_SIZE - eppnt->p_memsz < k) {
				error = -ENOMEM;
				goto out;
			}

			/*
			 * Find the end of the file mapping for this phdr, and
			 * keep track of the largest address we see for this.
			 */
			k = load_addr + eppnt->p_vaddr + eppnt->p_filesz;
			if (k > elf_bss)
				elf_bss = k;

			/*
			 * Do the same thing for the memory mapping - between
			 * elf_bss and last_bss is the bss section.
			 */
			k = load_addr + eppnt->p_vaddr + eppnt->p_memsz;
			if (k > last_bss) {
				last_bss = k;
				bss_prot = elf_prot;
			}
		}
	}

	/*
	 * Now fill out the bss section: first pad the last page from
	 * the file up to the page boundary, and zero it from elf_bss
	 * up to the end of the page.
	 */
	if (padzero(elf_bss)) {
		error = -EFAULT;
		goto out;
	}
	/*
	 * Next, align both the file and mem bss up to the page size,
	 * since this is where elf_bss was just zeroed up to, and where
	 * last_bss will end after the vm_brk_flags() below.
	 */
	elf_bss = ELF_PAGEALIGN(elf_bss);
	last_bss = ELF_PAGEALIGN(last_bss);
	/* Finally, if there is still more bss to allocate, do it. */
	if (last_bss > elf_bss) {
		error = vm_brk_flags(elf_bss, last_bss - elf_bss,
				bss_prot & PROT_EXEC ? VM_EXEC : 0);
		if (error)
			goto out;
	}

	error = load_addr;
out:
	return error;
}

先程見た処理と概ね同じ処理を行っている。流れだけ追うと、まずエラーチェックを行った後にプログラムヘッダテーブルからtotal_sizeを計算する。これが終わるとプログラムヘッダテーブルを舐めていき、PT_LOADなタイプのセクションを対象にインタプリタをメモリ上にマッピングしていく。インタプリタのタイプはET_DYNであるため、no_baseが0でない(ASLRが有効)場合のみload_addrが-vaddrとなる。これはelf_map()の第二引数を0にするために使用される。elf_map()の第二引数に0を渡すと呼び出し先のdo_mmap()の第二引数addrがNULLとなり、カーネルが適当な領域が選びマッピングする。用がすんだtotal_sizeは0にしておくことで、以降のelf_map()の呼び出し時の動作をスイッチさせることができる。多分さっき分からんかったやつはここで使うんだな。その後出てくるinterp_map_addrは使用しないので無視して良い。次回以降のループで使用するため、map_addrを元にload_addrを再計算し、load_addr_setをtrueにしておく。その後セクションサイズのチェックを行い、elf_bssとlast_bssを更新する。elf_bssはプログラムヘッダのファイルサイズから得た最上位の仮想アドレスの終端を指し、last_bssは更にelf_bssからBSSセクション分進んだ位置を指す。elf_bssとlast_bssで挟まれた区間BSSセクションとして用いる(変数名を統一しろ)。for文が終わるとelf_bssのチェックを行い、elf_bssとlast_bssをアライメントした上でBSSセクションをアロケートする。load_addrは最初にmmapした領域の最下位のアドレスを指しているため、これを返し処理が終了する。
以上の処理でELFインタプリタのメモリマッピングが終了する。interp_load_addrにELFインタプリタマッピングされている仮想アドレスの先端を保持しておき、ELFインタプリタのエントリーポイントのアドレスをelf_entryに足し合わせておく。reloc_func_descにinterp_load_addrを代入する。これは後にABI依存な処理に用いられるらしい。用がすんだinterpreter及びelf_interpreterを解放し、if文が終了する。
次にelse文の方を見ていく。こちらは非常に簡単でelf_entryにELFヘッダのエントリーポイントのアドレスを格納するだけだ。elf_interpreterがNULLなのでこちらはstaticにリンクされた実行バイナリであり、実行に動的ローダは必要ないためそのままエントリーポイントから実行できる。
そしてelf_interpreterのNULL/非NULLに関係なく用済みとなったinterp_elf_phdataとelf_phdataを解放する。

set_binfmt(&elf_format);
void set_binfmt(struct linux_binfmt *new)
{
	struct mm_struct *mm = current->mm;

	if (mm->binfmt)
		module_put(mm->binfmt->module);

	mm->binfmt = new;
	if (new)
		__module_get(new->module);
}
static struct linux_binfmt elf_format = {
	.module		= THIS_MODULE,
	.load_binary	= load_elf_binary,
	.load_shlib	= load_elf_library,
	.core_dump	= elf_core_dump,
	.min_coredump	= ELF_EXEC_PAGESIZE,
};

current->mm->binfmtを差し替える。elf_formatにはELF固有のcoredump処理などが格納されており、多分scriptからELFファイルをexecした場合などに備えた差し替え処理だと思われる。module_put()は参照カウンタのデクリメント、__module_get()は参照カウンタのインクリメントを行う。

#ifdef ARCH_HAS_SETUP_ADDITIONAL_PAGES
	retval = arch_setup_additional_pages(bprm, !!elf_interpreter);
	if (retval < 0)
		goto out;
#endif /* ARCH_HAS_SETUP_ADDITIONAL_PAGES */

x86ではARCH_HAS_SETUP_ADDITIONAL_PAGESが定義されており、arch_setup_additional_pages()はarch/x86/entry/vdso/vma.cで定義されている。
用途としては多分これっぽい。
qiita.com

あとで読む(多分)。
Implementing virtual system calls [LWN.net]

retval = create_elf_tables(bprm, &loc->elf_ex,
		  load_addr, interp_load_addr);
if (retval < 0)
	goto out;
static int
create_elf_tables(struct linux_binprm *bprm, struct elfhdr *exec,
		unsigned long load_addr, unsigned long interp_load_addr)
{
	unsigned long p = bprm->p;
	int argc = bprm->argc;
	int envc = bprm->envc;
	elf_addr_t __user *sp;
	elf_addr_t __user *u_platform;
	elf_addr_t __user *u_base_platform;
	elf_addr_t __user *u_rand_bytes;
	const char *k_platform = ELF_PLATFORM;
	const char *k_base_platform = ELF_BASE_PLATFORM;
	unsigned char k_rand_bytes[16];
	int items;
	elf_addr_t *elf_info;
	int ei_index = 0;
	const struct cred *cred = current_cred();
	struct vm_area_struct *vma;

	/*
	 * In some cases (e.g. Hyper-Threading), we want to avoid L1
	 * evictions by the processes running on the same package. One
	 * thing we can do is to shuffle the initial stack for them.
	 */

	p = arch_align_stack(p);

	/*
	 * If this architecture has a platform capability string, copy it
	 * to userspace.  In some cases (Sparc), this info is impossible
	 * for userspace to get any other way, in others (i386) it is
	 * merely difficult.
	 */
	u_platform = NULL;
	if (k_platform) {
		size_t len = strlen(k_platform) + 1;

		u_platform = (elf_addr_t __user *)STACK_ALLOC(p, len);
		if (__copy_to_user(u_platform, k_platform, len))
			return -EFAULT;
	}

	/*
	 * If this architecture has a "base" platform capability
	 * string, copy it to userspace.
	 */
	u_base_platform = NULL;
	if (k_base_platform) {
		size_t len = strlen(k_base_platform) + 1;

		u_base_platform = (elf_addr_t __user *)STACK_ALLOC(p, len);
		if (__copy_to_user(u_base_platform, k_base_platform, len))
			return -EFAULT;
	}

	/*
	 * Generate 16 random bytes for userspace PRNG seeding.
	 */
	get_random_bytes(k_rand_bytes, sizeof(k_rand_bytes));
	u_rand_bytes = (elf_addr_t __user *)
		       STACK_ALLOC(p, sizeof(k_rand_bytes));
	if (__copy_to_user(u_rand_bytes, k_rand_bytes, sizeof(k_rand_bytes)))
		return -EFAULT;

	/* Create the ELF interpreter info */
	elf_info = (elf_addr_t *)current->mm->saved_auxv;
	/* update AT_VECTOR_SIZE_BASE if the number of NEW_AUX_ENT() changes */
#define NEW_AUX_ENT(id, val) \
	do { \
		elf_info[ei_index++] = id; \
		elf_info[ei_index++] = val; \
	} while (0)

#ifdef ARCH_DLINFO
	/* 
	 * ARCH_DLINFO must come first so PPC can do its special alignment of
	 * AUXV.
	 * update AT_VECTOR_SIZE_ARCH if the number of NEW_AUX_ENT() in
	 * ARCH_DLINFO changes
	 */
	ARCH_DLINFO;
#endif
	NEW_AUX_ENT(AT_HWCAP, ELF_HWCAP);
	NEW_AUX_ENT(AT_PAGESZ, ELF_EXEC_PAGESIZE);
	NEW_AUX_ENT(AT_CLKTCK, CLOCKS_PER_SEC);
	NEW_AUX_ENT(AT_PHDR, load_addr + exec->e_phoff);
	NEW_AUX_ENT(AT_PHENT, sizeof(struct elf_phdr));
	NEW_AUX_ENT(AT_PHNUM, exec->e_phnum);
	NEW_AUX_ENT(AT_BASE, interp_load_addr);
	NEW_AUX_ENT(AT_FLAGS, 0);
	NEW_AUX_ENT(AT_ENTRY, exec->e_entry);
	NEW_AUX_ENT(AT_UID, from_kuid_munged(cred->user_ns, cred->uid));
	NEW_AUX_ENT(AT_EUID, from_kuid_munged(cred->user_ns, cred->euid));
	NEW_AUX_ENT(AT_GID, from_kgid_munged(cred->user_ns, cred->gid));
	NEW_AUX_ENT(AT_EGID, from_kgid_munged(cred->user_ns, cred->egid));
	NEW_AUX_ENT(AT_SECURE, bprm->secureexec);
	NEW_AUX_ENT(AT_RANDOM, (elf_addr_t)(unsigned long)u_rand_bytes);
#ifdef ELF_HWCAP2
	NEW_AUX_ENT(AT_HWCAP2, ELF_HWCAP2);
#endif
	NEW_AUX_ENT(AT_EXECFN, bprm->exec);
	if (k_platform) {
		NEW_AUX_ENT(AT_PLATFORM,
			    (elf_addr_t)(unsigned long)u_platform);
	}
	if (k_base_platform) {
		NEW_AUX_ENT(AT_BASE_PLATFORM,
			    (elf_addr_t)(unsigned long)u_base_platform);
	}
	if (bprm->interp_flags & BINPRM_FLAGS_EXECFD) {
		NEW_AUX_ENT(AT_EXECFD, bprm->interp_data);
	}
#undef NEW_AUX_ENT
	/* AT_NULL is zero; clear the rest too */
	memset(&elf_info[ei_index], 0,
	       sizeof current->mm->saved_auxv - ei_index * sizeof elf_info[0]);

	/* And advance past the AT_NULL entry.  */
	ei_index += 2;

	sp = STACK_ADD(p, ei_index);

	items = (argc + 1) + (envc + 1) + 1;
	bprm->p = STACK_ROUND(sp, items);

	/* Point sp at the lowest address on the stack */
#ifdef CONFIG_STACK_GROWSUP
	sp = (elf_addr_t __user *)bprm->p - items - ei_index;
	bprm->exec = (unsigned long)sp; /* XXX: PARISC HACK */
#else
	sp = (elf_addr_t __user *)bprm->p;
#endif


	/*
	 * Grow the stack manually; some architectures have a limit on how
	 * far ahead a user-space access may be in order to grow the stack.
	 */
	vma = find_extend_vma(current->mm, bprm->p);
	if (!vma)
		return -EFAULT;

	/* Now, let's put argc (and argv, envp if appropriate) on the stack */
	if (__put_user(argc, sp++))
		return -EFAULT;

	/* Populate list of argv pointers back to argv strings. */
	p = current->mm->arg_end = current->mm->arg_start;
	while (argc-- > 0) {
		size_t len;
		if (__put_user((elf_addr_t)p, sp++))
			return -EFAULT;
		len = strnlen_user((void __user *)p, MAX_ARG_STRLEN);
		if (!len || len > MAX_ARG_STRLEN)
			return -EINVAL;
		p += len;
	}
	if (__put_user(0, sp++))
		return -EFAULT;
	current->mm->arg_end = p;

	/* Populate list of envp pointers back to envp strings. */
	current->mm->env_end = current->mm->env_start = p;
	while (envc-- > 0) {
		size_t len;
		if (__put_user((elf_addr_t)p, sp++))
			return -EFAULT;
		len = strnlen_user((void __user *)p, MAX_ARG_STRLEN);
		if (!len || len > MAX_ARG_STRLEN)
			return -EINVAL;
		p += len;
	}
	if (__put_user(0, sp++))
		return -EFAULT;
	current->mm->env_end = p;

	/* Put the elf_info on the stack in the right place.  */
	if (copy_to_user(sp, elf_info, ei_index * sizeof(elf_addr_t)))
		return -EFAULT;
	return 0;
}

create_elf_tables()はbprm->pを起点にユーザーランドのスタック領域を初期化していく。一つ一つ見ていく。

/*
 * In some cases (e.g. Hyper-Threading), we want to avoid L1
 * evictions by the processes running on the same package. One
 * thing we can do is to shuffle the initial stack for them.
 */

p = arch_align_stack(p);

/*
 * If this architecture has a platform capability string, copy it
 * to userspace.  In some cases (Sparc), this info is impossible
 * for userspace to get any other way, in others (i386) it is
 * merely difficult.
 */
u_platform = NULL;
if (k_platform) {
	size_t len = strlen(k_platform) + 1;
		u_platform = (elf_addr_t __user *)STACK_ALLOC(p, len);
	if (__copy_to_user(u_platform, k_platform, len))
		return -EFAULT;
}

/*
 * If this architecture has a "base" platform capability
 * string, copy it to userspace.
 */
u_base_platform = NULL;
if (k_base_platform) {
	size_t len = strlen(k_base_platform) + 1;

	u_base_platform = (elf_addr_t __user *)STACK_ALLOC(p, len);
	if (__copy_to_user(u_base_platform, k_base_platform, len))
		return -EFAULT;
}

/*
 * Generate 16 random bytes for userspace PRNG seeding.
 */
get_random_bytes(k_rand_bytes, sizeof(k_rand_bytes));
u_rand_bytes = (elf_addr_t __user *)
	       STACK_ALLOC(p, sizeof(k_rand_bytes));
if (__copy_to_user(u_rand_bytes, k_rand_bytes, sizeof(k_rand_bytes)))
	return -EFAULT;
unsigned long arch_align_stack(unsigned long sp)
{
	if (!(current->personality & ADDR_NO_RANDOMIZE) && randomize_va_space)
		sp -= get_random_int() % 8192;
	return sp & ~0xf;
}

bprm->pをアライメントし、プラットフォームの名前("x86_64")、16byteの乱数などをユーザーランドにコピーしていく。k_base_platformはPowerPC以外はNULLになるので無視して良い。また、arch_align_stack()はload_biasを使わず、新たに乱数を取得してその値でランダマイズしているようだ。

/* Create the ELF interpreter info */
elf_info = (elf_addr_t *)current->mm->saved_auxv;
/* update AT_VECTOR_SIZE_BASE if the number of NEW_AUX_ENT() changes */
#define NEW_AUX_ENT(id, val) \
do { \
	elf_info[ei_index++] = id; \
	elf_info[ei_index++] = val; \
} while (0)

auxvとはAuxiliary Vectorsの略で、カーネルからユーザーランドに渡す情報が格納されているらしい。詳しくは以下を参照されたい。
About ELF Auxiliary Vectors

以降はNEW_AUX_ENT()マクロを用いてAuxiliary Vectorsに情報を格納していく。

#ifdef ARCH_DLINFO
/* 
 * ARCH_DLINFO must come first so PPC can do its special alignment of
 * AUXV.
 * update AT_VECTOR_SIZE_ARCH if the number of NEW_AUX_ENT() in
 * ARCH_DLINFO changes
 */
ARCH_DLINFO;
#endif
NEW_AUX_ENT(AT_HWCAP, ELF_HWCAP);
NEW_AUX_ENT(AT_PAGESZ, ELF_EXEC_PAGESIZE);
NEW_AUX_ENT(AT_CLKTCK, CLOCKS_PER_SEC);
NEW_AUX_ENT(AT_PHDR, load_addr + exec->e_phoff);
NEW_AUX_ENT(AT_PHENT, sizeof(struct elf_phdr));
NEW_AUX_ENT(AT_PHNUM, exec->e_phnum);
NEW_AUX_ENT(AT_BASE, interp_load_addr);
NEW_AUX_ENT(AT_FLAGS, 0);
NEW_AUX_ENT(AT_ENTRY, exec->e_entry);
NEW_AUX_ENT(AT_UID, from_kuid_munged(cred->user_ns, cred->uid));
NEW_AUX_ENT(AT_EUID, from_kuid_munged(cred->user_ns, cred->euid));
NEW_AUX_ENT(AT_GID, from_kgid_munged(cred->user_ns, cred->gid));
NEW_AUX_ENT(AT_EGID, from_kgid_munged(cred->user_ns, cred->egid));
NEW_AUX_ENT(AT_SECURE, bprm->secureexec);
NEW_AUX_ENT(AT_RANDOM, (elf_addr_t)(unsigned long)u_rand_bytes);
#ifdef ELF_HWCAP2
NEW_AUX_ENT(AT_HWCAP2, ELF_HWCAP2);
#endif
NEW_AUX_ENT(AT_EXECFN, bprm->exec);
if (k_platform) {
	NEW_AUX_ENT(AT_PLATFORM,
		    (elf_addr_t)(unsigned long)u_platform);
}
if (k_base_platform) {
	NEW_AUX_ENT(AT_BASE_PLATFORM,
		    (elf_addr_t)(unsigned long)u_base_platform);
}
if (bprm->interp_flags & BINPRM_FLAGS_EXECFD) {
	NEW_AUX_ENT(AT_EXECFD, bprm->interp_data);
}
#undef NEW_AUX_ENT

AT_*なマクロはinclude/uapi/linux/auxvec.hで定義されている。

/* Symbolic values for the entries in the auxiliary table
   put on the initial stack */
#define AT_NULL   0	/* end of vector */
#define AT_IGNORE 1	/* entry should be ignored */
#define AT_EXECFD 2	/* file descriptor of program */
#define AT_PHDR   3	/* program headers for program */
#define AT_PHENT  4	/* size of program header entry */
#define AT_PHNUM  5	/* number of program headers */
#define AT_PAGESZ 6	/* system page size */
#define AT_BASE   7	/* base address of interpreter */
#define AT_FLAGS  8	/* flags */
#define AT_ENTRY  9	/* entry point of program */
#define AT_NOTELF 10	/* program is not ELF */
#define AT_UID    11	/* real uid */
#define AT_EUID   12	/* effective uid */
#define AT_GID    13	/* real gid */
#define AT_EGID   14	/* effective gid */
#define AT_PLATFORM 15  /* string identifying CPU for optimizations */
#define AT_HWCAP  16    /* arch dependent hints at CPU capabilities */
#define AT_CLKTCK 17	/* frequency at which times() increments */
/* AT_* values 18 through 22 are reserved */
#define AT_SECURE 23   /* secure mode boolean */
#define AT_BASE_PLATFORM 24	/* string identifying real platform, may
				 * differ from AT_PLATFORM. */
#define AT_RANDOM 25	/* address of 16 random bytes */
#define AT_HWCAP2 26	/* extension of AT_HWCAP */

#define AT_EXECFN  31	/* filename of program */

例えば一番最初のAT_HWCAPはCPUの情報に関してアーキテクチャ依存のヒントが渡される。どんな命令が使えるかといった情報が書いてある。らしい。

/* AT_NULL is zero; clear the rest too */
memset(&elf_info[ei_index], 0,
       sizeof current->mm->saved_auxv - ei_index * sizeof elf_info[0]);

/* And advance past the AT_NULL entry.  */
ei_index += 2;

sp = STACK_ADD(p, ei_index);

items = (argc + 1) + (envc + 1) + 1;
bprm->p = STACK_ROUND(sp, items);

/* Point sp at the lowest address on the stack */
#ifdef CONFIG_STACK_GROWSUP
sp = (elf_addr_t __user *)bprm->p - items - ei_index;
bprm->exec = (unsigned long)sp; /* XXX: PARISC HACK */
#else
sp = (elf_addr_t __user *)bprm->p;
#endif

終端処理としてAT_NULLを定義する。ただしやっていることは0クリアで、おまけでAT_NULL以降のメモリ領域も0クリアしている。これが終わるとei_indexを2つインクリメントする。2つインクリメントするのはidとvalで2つ使用するから。これが終わるとSTACK_ADD()を用いてスタックポインタを現在の位置まで伸長させる。その後、argcとenvcの分を考慮した上でbprm->pに書き戻し、CONFIG_STACK_GROWSUPでない場合は再びspに格納する。なんで1足してるんだろう。

/*
 * Grow the stack manually; some architectures have a limit on how
 * far ahead a user-space access may be in order to grow the stack.
 */
vma = find_extend_vma(current->mm, bprm->p);
if (!vma)
	return -EFAULT;

/* Now, let's put argc (and argv, envp if appropriate) on the stack */
if (__put_user(argc, sp++))
	return -EFAULT;
struct vm_area_struct *
find_extend_vma(struct mm_struct *mm, unsigned long addr)
{
	struct vm_area_struct *vma;
	unsigned long start;

	addr &= PAGE_MASK;
	vma = find_vma(mm, addr);
	if (!vma)
		return NULL;
	if (vma->vm_start <= addr)
		return vma;
	if (!(vma->vm_flags & VM_GROWSDOWN))
		return NULL;
	start = vma->vm_start;
	if (expand_stack(vma, addr))
		return NULL;
	if (vma->vm_flags & VM_LOCKED)
		populate_vma_page_range(vma, addr, start, NULL);
	return vma;
}
/* Look up the first VMA which satisfies  addr < vm_end,  NULL if none. */
struct vm_area_struct *find_vma(struct mm_struct *mm, unsigned long addr)
{
	struct rb_node *rb_node;
	struct vm_area_struct *vma;

	/* Check the cache first. */
	vma = vmacache_find(mm, addr);
	if (likely(vma))
		return vma;

	rb_node = mm->mm_rb.rb_node;

	while (rb_node) {
		struct vm_area_struct *tmp;

		tmp = rb_entry(rb_node, struct vm_area_struct, vm_rb);

		if (tmp->vm_end > addr) {
			vma = tmp;
			if (tmp->vm_start <= addr)
				break;
			rb_node = rb_node->rb_left;
		} else
			rb_node = rb_node->rb_right;
	}

	if (vma)
		vmacache_update(addr, vma);
	return vma;
}
struct vm_area_struct *vmacache_find(struct mm_struct *mm, unsigned long addr)
{
	int i;

	count_vm_vmacache_event(VMACACHE_FIND_CALLS);

	if (!vmacache_valid(mm))
		return NULL;

	for (i = 0; i < VMACACHE_SIZE; i++) {
		struct vm_area_struct *vma = current->vmacache.vmas[i];

		if (!vma)
			continue;
		if (WARN_ON_ONCE(vma->vm_mm != mm))
			break;
		if (vma->vm_start <= addr && vma->vm_end > addr) {
			count_vm_vmacache_event(VMACACHE_FIND_HITS);
			return vma;
		}
	}

	return NULL;
}

find_vma()はまずvmacache_find()によりキャッシュを探索する。vmacache_find()ではcurrent->vmacache.vmas[]を舐めていき、vm_start <= bprm->p < vm_endとなるキャッシュが見つかればそれを返す。見つからない場合は赤黒木のルートであるcurrent->mm->mm_rb.rb_nodeから同様にvm_start <= bprm->p < vm_endとなるvmaを探索する。見つからなかった場合はexpand_stack()で必要な分だけスタックを伸長する。多分。

/* Now, let's put argc (and argv, envp if appropriate) on the stack */
if (__put_user(argc, sp++))
	return -EFAULT;

/* Populate list of argv pointers back to argv strings. */
p = current->mm->arg_end = current->mm->arg_start;
while (argc-- > 0) {
	size_t len;
	if (__put_user((elf_addr_t)p, sp++))
		return -EFAULT;
	len = strnlen_user((void __user *)p, MAX_ARG_STRLEN);
	if (!len || len > MAX_ARG_STRLEN)
		return -EINVAL;
	p += len;
}
if (__put_user(0, sp++))
	return -EFAULT;
current->mm->arg_end = p;

argcをユーザーランドにコピーした後、current->mm->arg_startよりargvをコピーしていく。終端に0をコピーするとcurrent->mm->arg_endを更新し、argc及びargvのコピーが終了する。

/* Populate list of envp pointers back to envp strings. */
current->mm->env_end = current->mm->env_start = p;
while (envc-- > 0) {
	size_t len;
	if (__put_user((elf_addr_t)p, sp++))
		return -EFAULT;
	len = strnlen_user((void __user *)p, MAX_ARG_STRLEN);
	if (!len || len > MAX_ARG_STRLEN)
		return -EINVAL;
	p += len;
}
if (__put_user(0, sp++))
	return -EFAULT;
current->mm->env_end = p;

同様にenvpをコピーしていきcurrent->mm->env_endを更新してenvpのコピーが終了する。

/* Put the elf_info on the stack in the right place.  */
if (copy_to_user(sp, elf_info, ei_index * sizeof(elf_addr_t)))
	return -EFAULT;

最後に先程作成したAuxiliary Vectorsをコピーしてcreate_elf_tables()の処理が終了する。
load_elf_binary()に戻ろう。

/* N.B. passed_fileno might not be initialized? */
current->mm->end_code = end_code;
current->mm->start_code = start_code;
current->mm->start_data = start_data;
current->mm->end_data = end_data;
current->mm->start_stack = bprm->p;

if ((current->flags & PF_RANDOMIZE) && (randomize_va_space > 1)) {
	current->mm->brk = current->mm->start_brk =
		arch_randomize_brk(current->mm);
#ifdef compat_brk_randomized
	current->brk_randomized = 1;
#endif
}
unsigned long arch_randomize_brk(struct mm_struct *mm)
{
	return randomize_page(mm->brk, 0x02000000);
}
/**
 * randomize_page - Generate a random, page aligned address
 * @start:	The smallest acceptable address the caller will take.
 * @range:	The size of the area, starting at @start, within which the
 *		random address must fall.
 *
 * If @start + @range would overflow, @range is capped.
 *
 * NOTE: Historical use of randomize_range, which this replaces, presumed that
 * @start was already page aligned.  We now align it regardless.
 *
 * Return: A page aligned address within [start, start + range).  On error,
 * @start is returned.
 */
unsigned long
randomize_page(unsigned long start, unsigned long range)
{
	if (!PAGE_ALIGNED(start)) {
		range -= PAGE_ALIGN(start) - start;
		start = PAGE_ALIGN(start);
	}

	if (start > ULONG_MAX - range)
		range = ULONG_MAX - start;

	range >>= PAGE_SHIFT;

	if (range == 0)
		return start;

	return start + (get_random_long() % range << PAGE_SHIFT);
}

少し前のfor文で計算した各変数をcurrent->mmに反映していく。

if (current->personality & MMAP_PAGE_ZERO) {
	/* Why this, you ask???  Well SVr4 maps page 0 as read-only,
	   and some applications "depend" upon this behavior.
	   Since we do not have the power to recompile these, we
	   emulate the SVr4 behavior. Sigh. */
	error = vm_mmap(NULL, 0, PAGE_SIZE, PROT_READ | PROT_EXEC,
			MAP_FIXED | MAP_PRIVATE, 0);
}

SVr4の動作をエミュレートするための処理らしい。よく分からんので飛ばす。

#ifdef ELF_PLAT_INIT
/*
 * The ABI may specify that certain registers be set up in special
 * ways (on i386 %edx is the address of a DT_FINI function, for
 * example.  In addition, it may also specify (eg, PowerPC64 ELF)
 * that the e_entry field is the address of the function descriptor
 * for the startup routine, rather than the address of the startup
 * routine itself.  This macro performs whatever initialization to
 * the regs structure is required as well as any relocations to the
 * function descriptor entries when executing dynamically links apps.
 */
ELF_PLAT_INIT(regs, reloc_func_desc);
#endif

ELF_PLAT_INIT()は例えばi386のedxレジスタ(DT_FINI関数のアドレスを指定するために使用する)のような特別な用途でのレジスタの初期化を行う。reloc_func_descにはload_biasもしくはELFインタプリタマッピングされている仮想アドレスの先端が格納されている。

#define ELF_PLAT_INIT(_r, load_addr)			\
	elf_common_init(&current->thread, _r, 0)
static inline void elf_common_init(struct thread_struct *t,
				   struct pt_regs *regs, const u16 ds)
{
	/* ax gets execve's return value. */
	/*regs->ax = */ regs->bx = regs->cx = regs->dx = 0;
	regs->si = regs->di = regs->bp = 0;
	regs->r8 = regs->r9 = regs->r10 = regs->r11 = 0;
	regs->r12 = regs->r13 = regs->r14 = regs->r15 = 0;
	t->fsbase = t->gsbase = 0;
	t->fsindex = t->gsindex = 0;
	t->ds = t->es = ds;
}

reloc_func_desc使わんのんかい。bx、cx、dx、si、di、bp、r8、r9、r10、r11、r12、r13、r14、r15レジスタを0で初期化する。fsbase、gsbase、fsindex、gsindexはよく分からんけど0になる。ついでに引数のdsも0なのでds、esレジスタも0になる。

start_thread(regs, elf_entry, bprm->p);

start_thread()はarch/x86/kernel/process_64.cで定義されている。

void
start_thread(struct pt_regs *regs, unsigned long new_ip, unsigned long new_sp)
{
	start_thread_common(regs, new_ip, new_sp,
			    __USER_CS, __USER_DS, 0);
}
static void
start_thread_common(struct pt_regs *regs, unsigned long new_ip,
		    unsigned long new_sp,
		    unsigned int _cs, unsigned int _ss, unsigned int _ds)
{
	WARN_ON_ONCE(regs != current_pt_regs());

	if (static_cpu_has(X86_BUG_NULL_SEG)) {
		/* Loading zero below won't clear the base. */
		loadsegment(fs, __USER_DS);
		load_gs_index(__USER_DS);
	}

	loadsegment(fs, 0);
	loadsegment(es, _ds);
	loadsegment(ds, _ds);
	load_gs_index(0);

	regs->ip		= new_ip;
	regs->sp		= new_sp;
	regs->cs		= _cs;
	regs->ss		= _ss;
	regs->flags		= X86_EFLAGS_IF;
	force_iret();
}

elf_entryをregs->ip、bprm->pをregs->spとし、iret命令によりスイッチする。以上の処理により、全てのexecveの処理が完了する。長かった。
このような手順でプログラムは実行される。色々飛ばしてしまった部分は分かった時にまとめていこう。

参考資料
メモリ管理、アドレス空間、ページテーブル
How the Linux kernel runs a program · Linux Inside

PIE(position-independent executable)なオブジェクトの中身を見てみる

巷ではSpectre/Meltdownが話題になっている.

有効な対策として(K)ASLRが挙げられており,これはプロセス実行時のメモリ空間の配置をランダマイズすることで,例えば攻撃者が配置したコードのアドレスへのJMP等を防ぐことができる.
詳しくはシェルコードでググろう(解説できるほど詳しくない).

共有ライブラリは原則的にPIC(position-independent code)としてビルドされており,これらはASLRの恩恵に与ることができる.

しかし,通常のアプリケーションは絶対アドレスを用いて記述されているため,ASLRによるランダマイズが適用できない.
そこで,セキュリティ向上のために相対アドレスを用いたPIE(position-independent executable)と呼ばれる形式が通常のアプリケーションにおいても使用されている.

昔はセキュリティ上重要なファイルだけがPIEでビルドされていたらしいが,最近ではGentoo Linuxのprofile 17.0からPIEでのビルドがデフォルトオプションになったりしている.(これ全パッケージのリビルドが必要なので,まだ更新できてないです.同時にpython3.4がstableに降ってきたりして酷いことになりました…)
https://www.gentoo.org/support/news-items/2017-11-30-new-17-profiles.html

しかし,PIEでビルドした場合はパフォーマンスの低下が懸念されている.論文ではベンチマークであるSPEC CPU2006を用いて評価実験を行い,幾何平均で9.4%のパフォーマンス低下という結果を得たと述べている.
だが,実際にPIEを用いることで何がボトルネックになっているのか分からないので,今回はPIEなオブジェクトの中身を見てみる.
中身が見たくて見てるので,パフォーマンス低下の考察なら論文でしてると思うよ.

検証には前回と同じファイルを用いる.また,実行環境はGNU/Linux Fedora 25を用い,コンパイラgcc 6.4.1を用いた.

#include <cstdio>

class Base {
public:
    Base() { printf("Base::Base() has called\n"); }
    virtual void hoge() { printf("Base::hoge() has called\n"); }
    virtual void fuga() { printf("Base::fuga() has called\n"); }
};

class Derived : public Base {
public:
    Derived() { printf("Derived::Derived() has called\n"); }
    void fuga() { printf("Derived::fuga() has called\n"); }
};

void func(Base &x)
{
    x.hoge();
    x.fuga();
}

int main()
{
    Derived obj;
    func(obj);

    return 0;
}

PIEとしてビルドする場合,-fPIEもしくは-fpieオプションをつけてコンパイルし,リンク時に-pieオプションをつける.

$ g++ -c -fPIE vtable.cpp
$ g++ -o vtable_pie -pie vtable.o
$ g++ -o vtable_pie -pie -fPIE vtable.cpp # 一発でやる場合
$ ./vtable_pie
Base::Base() has called
Derived::Derived() has called
Base::hoge() has called
Derived::fuga() has called

比較用に普通にビルドしたファイルも用意しておく.

$ g++ -o vtable_normal vtable.cpp
$ ./vtable_normal
Base::Base() has called
Derived::Derived() has called
Base::hoge() has called
Derived::fuga() has called

ELFヘッダを見てみる.まずは普通にビルドした方から.

$ readelf -h vtable_normal
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:                              EXEC (Executable file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x4005b0
  Start of program headers:          64 (bytes into file)
  Start of section headers:          7288 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         9
  Size of section headers:           64 (bytes)
  Number of section headers:         30
  Section header string table index: 27

次にPIEな方.

$ readelf -h vtable_pie
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:               0x7f0
  Start of program headers:          64 (bytes into file)
  Start of section headers:          11504 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         9
  Size of section headers:           64 (bytes)
  Number of section headers:         32
  Section header string table index: 29

タイプが「EXEC (Executable file)」から「DYN (Shared object file)」になっており,またEntry point addressが小さくなっている.あとセクションヘッダの数が異なっており,そのせいかストリングテーブルのインデックスも異なっている.

セクションヘッダを見てみる.普通の方.

readelf -S vtable_normal
There are 30 section headers, starting at offset 0x1c78:

Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [ 0]                   NULL             0000000000000000  00000000
       0000000000000000  0000000000000000           0     0     0
  [ 1] .interp           PROGBITS         0000000000400238  00000238
       000000000000001c  0000000000000000   A       0     0     1
  [ 2] .note.ABI-tag     NOTE             0000000000400254  00000254
       0000000000000020  0000000000000000   A       0     0     4
  [ 3] .note.gnu.build-i NOTE             0000000000400274  00000274
       0000000000000024  0000000000000000   A       0     0     4
  [ 4] .gnu.hash         GNU_HASH         0000000000400298  00000298
       0000000000000028  0000000000000000   A       5     0     8
  [ 5] .dynsym           DYNSYM           00000000004002c0  000002c0
       00000000000000d8  0000000000000018   A       6     1     8
  [ 6] .dynstr           STRTAB           0000000000400398  00000398
       0000000000000108  0000000000000000   A       0     0     1
  [ 7] .gnu.version      VERSYM           00000000004004a0  000004a0
       0000000000000012  0000000000000002   A       5     0     2
  [ 8] .gnu.version_r    VERNEED          00000000004004b8  000004b8
       0000000000000040  0000000000000000   A       6     2     8
  [ 9] .rela.dyn         RELA             00000000004004f8  000004f8
       0000000000000060  0000000000000018   A       5     0     8
  [10] .rela.plt         RELA             0000000000400558  00000558
       0000000000000018  0000000000000018  AI       5    23     8
  [11] .init             PROGBITS         0000000000400570  00000570
       0000000000000017  0000000000000000  AX       0     0     4
  [12] .plt              PROGBITS         0000000000400590  00000590
       0000000000000020  0000000000000010  AX       0     0     16
  [13] .text             PROGBITS         00000000004005b0  000005b0
       0000000000000272  0000000000000000  AX       0     0     16
  [14] .fini             PROGBITS         0000000000400824  00000824
       0000000000000009  0000000000000000  AX       0     0     4
  [15] .rodata           PROGBITS         0000000000400830  00000830
       0000000000000116  0000000000000000   A       0     0     8
  [16] .eh_frame_hdr     PROGBITS         0000000000400948  00000948
       0000000000000064  0000000000000000   A       0     0     4
  [17] .eh_frame         PROGBITS         00000000004009b0  000009b0
       00000000000001b4  0000000000000000   A       0     0     8
  [18] .init_array       INIT_ARRAY       0000000000600dd8  00000dd8
       0000000000000008  0000000000000000  WA       0     0     8
  [19] .fini_array       FINI_ARRAY       0000000000600de0  00000de0
       0000000000000008  0000000000000000  WA       0     0     8
  [20] .jcr              PROGBITS         0000000000600de8  00000de8
       0000000000000008  0000000000000000  WA       0     0     8
  [21] .dynamic          DYNAMIC          0000000000600df0  00000df0
       0000000000000200  0000000000000010  WA       6     0     8
  [22] .got              PROGBITS         0000000000600ff0  00000ff0
       0000000000000010  0000000000000008  WA       0     0     8
  [23] .got.plt          PROGBITS         0000000000601000  00001000
       0000000000000020  0000000000000008  WA       0     0     8
  [24] .data             PROGBITS         0000000000601020  00001020
       0000000000000004  0000000000000000  WA       0     0     1
  [25] .bss              NOBITS           0000000000601028  00001024
       00000000000000b8  0000000000000000  WA       0     0     8
  [26] .comment          PROGBITS         0000000000000000  00001024
       0000000000000058  0000000000000001  MS       0     0     1
  [27] .shstrtab         STRTAB           0000000000000000  00001b6e
       0000000000000108  0000000000000000           0     0     1
  [28] .symtab           SYMTAB           0000000000000000  00001080
       00000000000007b0  0000000000000018          29    46     8
  [29] .strtab           STRTAB           0000000000000000  00001830
       000000000000033e  0000000000000000           0     0     1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), l (large)
  I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
  O (extra OS processing required) o (OS specific), p (processor specific)

PIEな方.

$ readelf -S vtable_pie
There are 32 section headers, starting at offset 0x2cf0:

Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [ 0]                   NULL             0000000000000000  00000000
       0000000000000000  0000000000000000           0     0     0
  [ 1] .interp           PROGBITS         0000000000000238  00000238
       000000000000001c  0000000000000000   A       0     0     1
  [ 2] .note.ABI-tag     NOTE             0000000000000254  00000254
       0000000000000020  0000000000000000   A       0     0     4
  [ 3] .note.gnu.build-i NOTE             0000000000000274  00000274
       0000000000000024  0000000000000000   A       0     0     4
  [ 4] .gnu.hash         GNU_HASH         0000000000000298  00000298
       0000000000000030  0000000000000000   A       5     0     8
  [ 5] .dynsym           DYNSYM           00000000000002c8  000002c8
       0000000000000150  0000000000000018   A       6     2     8
  [ 6] .dynstr           STRTAB           0000000000000418  00000418
       000000000000012f  0000000000000000   A       0     0     1
  [ 7] .gnu.version      VERSYM           0000000000000548  00000548
       000000000000001c  0000000000000002   A       5     0     2
  [ 8] .gnu.version_r    VERNEED          0000000000000568  00000568
       0000000000000040  0000000000000000   A       6     2     8
  [ 9] .rela.dyn         RELA             00000000000005a8  000005a8
       00000000000001e0  0000000000000018   A       5     0     8
  [10] .rela.plt         RELA             0000000000000788  00000788
       0000000000000018  0000000000000018  AI       5    25     8
  [11] .init             PROGBITS         00000000000007a0  000007a0
       0000000000000017  0000000000000000  AX       0     0     4
  [12] .plt              PROGBITS         00000000000007c0  000007c0
       0000000000000020  0000000000000010  AX       0     0     16
  [13] .plt.got          PROGBITS         00000000000007e0  000007e0
       0000000000000008  0000000000000000  AX       0     0     8
  [14] .text             PROGBITS         00000000000007f0  000007f0
       00000000000002c2  0000000000000000  AX       0     0     16
  [15] .fini             PROGBITS         0000000000000ab4  00000ab4
       0000000000000009  0000000000000000  AX       0     0     4
  [16] .rodata           PROGBITS         0000000000000ac0  00000ac0
       0000000000000097  0000000000000000   A       0     0     8
  [17] .eh_frame_hdr     PROGBITS         0000000000000b58  00000b58
       0000000000000064  0000000000000000   A       0     0     4
  [18] .eh_frame         PROGBITS         0000000000000bc0  00000bc0
       00000000000001b4  0000000000000000   A       0     0     8
  [19] .init_array       INIT_ARRAY       0000000000201d38  00001d38
       0000000000000008  0000000000000000  WA       0     0     8
  [20] .fini_array       FINI_ARRAY       0000000000201d40  00001d40
       0000000000000008  0000000000000000  WA       0     0     8
  [21] .jcr              PROGBITS         0000000000201d48  00001d48
       0000000000000008  0000000000000000  WA       0     0     8
  [22] .data.rel.ro      PROGBITS         0000000000201d50  00001d50
       0000000000000070  0000000000000000  WA       0     0     8
  [23] .dynamic          DYNAMIC          0000000000201dc0  00001dc0
       0000000000000210  0000000000000010  WA       6     0     8
  [24] .got              PROGBITS         0000000000201fd0  00001fd0
       0000000000000030  0000000000000008  WA       0     0     8
  [25] .got.plt          PROGBITS         0000000000202000  00002000
       0000000000000020  0000000000000008  WA       0     0     8
  [26] .data             PROGBITS         0000000000202020  00002020
       0000000000000004  0000000000000000  WA       0     0     1
  [27] .bss              NOBITS           0000000000202024  00002024
       0000000000000004  0000000000000000  WA       0     0     1
  [28] .comment          PROGBITS         0000000000000000  00002024
       0000000000000058  0000000000000001  MS       0     0     1
  [29] .shstrtab         STRTAB           0000000000000000  00002bd2
       0000000000000119  0000000000000000           0     0     1
  [30] .symtab           SYMTAB           0000000000000000  00002080
       00000000000007f8  0000000000000018          31    48     8
  [31] .strtab           STRTAB           0000000000000000  00002878
       000000000000035a  0000000000000000           0     0     1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), l (large)
  I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
  O (extra OS processing required) o (OS specific), p (processor specific)

.plt.gotと.data.rel.roというセクションが増えているようだ.ググったらRELRO (RELocation Read-Only)という別?の技術が出てきたが何も分からんので宿題にしたい.

アセンブルしてみる.

 objdump -d vtable_pie |c++filt

vtable_pie:     file format elf64-x86-64


Disassembly of section .init:

00000000000007a0 <_init>:
 7a0:	48 83 ec 08          	sub    $0x8,%rsp
 7a4:	48 8b 05 25 18 20 00 	mov    0x201825(%rip),%rax        # 201fd0 <_DYNAMIC+0x210>
 7ab:	48 85 c0             	test   %rax,%rax
 7ae:	74 02                	je     7b2 <_init+0x12>
 7b0:	ff d0                	callq  *%rax
 7b2:	48 83 c4 08          	add    $0x8,%rsp
 7b6:	c3                   	retq

Disassembly of section .plt:

00000000000007c0 <puts@plt-0x10>:
 7c0:	ff 35 42 18 20 00    	pushq  0x201842(%rip)        # 202008 <_GLOBAL_OFFSET_TABLE_+0x8>
 7c6:	ff 25 44 18 20 00    	jmpq   *0x201844(%rip)        # 202010 <_GLOBAL_OFFSET_TABLE_+0x10>
 7cc:	0f 1f 40 00          	nopl   0x0(%rax)

00000000000007d0 <puts@plt>:
 7d0:	ff 25 42 18 20 00    	jmpq   *0x201842(%rip)        # 202018 <_GLOBAL_OFFSET_TABLE_+0x18>
 7d6:	68 00 00 00 00       	pushq  $0x0
 7db:	e9 e0 ff ff ff       	jmpq   7c0 <_init+0x20>

Disassembly of section .plt.got:

00000000000007e0 <.plt.got>:
 7e0:	ff 25 12 18 20 00    	jmpq   *0x201812(%rip)        # 201ff8 <_DYNAMIC+0x238>
 7e6:	66 90                	xchg   %ax,%ax

Disassembly of section .text:

00000000000007f0 <_start>:
 7f0:	31 ed                	xor    %ebp,%ebp
 7f2:	49 89 d1             	mov    %rdx,%r9
 7f5:	5e                   	pop    %rsi
 7f6:	48 89 e2             	mov    %rsp,%rdx
 7f9:	48 83 e4 f0          	and    $0xfffffffffffffff0,%rsp
 7fd:	50                   	push   %rax
 7fe:	54                   	push   %rsp
 7ff:	4c 8d 05 aa 02 00 00 	lea    0x2aa(%rip),%r8        # ab0 <__libc_csu_fini>
 806:	48 8d 0d 33 02 00 00 	lea    0x233(%rip),%rcx        # a40 <__libc_csu_init>
 80d:	48 8d 3d 45 01 00 00 	lea    0x145(%rip),%rdi        # 959 <main>
 814:	ff 15 c6 17 20 00    	callq  *0x2017c6(%rip)        # 201fe0 <_DYNAMIC+0x220>
 81a:	f4                   	hlt
 81b:	0f 1f 44 00 00       	nopl   0x0(%rax,%rax,1)

0000000000000820 <deregister_tm_clones>:
 820:	48 8d 3d 01 18 20 00 	lea    0x201801(%rip),%rdi        # 202028 <__TMC_END__>
 827:	48 8d 05 01 18 20 00 	lea    0x201801(%rip),%rax        # 20202f <__TMC_END__+0x7>
 82e:	55                   	push   %rbp
 82f:	48 29 f8             	sub    %rdi,%rax
 832:	48 89 e5             	mov    %rsp,%rbp
 835:	48 83 f8 0e          	cmp    $0xe,%rax
 839:	76 15                	jbe    850 <deregister_tm_clones+0x30>
 83b:	48 8b 05 a6 17 20 00 	mov    0x2017a6(%rip),%rax        # 201fe8 <_DYNAMIC+0x228>
 842:	48 85 c0             	test   %rax,%rax
 845:	74 09                	je     850 <deregister_tm_clones+0x30>
 847:	5d                   	pop    %rbp
 848:	ff e0                	jmpq   *%rax
 84a:	66 0f 1f 44 00 00    	nopw   0x0(%rax,%rax,1)
 850:	5d                   	pop    %rbp
 851:	c3                   	retq
 852:	0f 1f 40 00          	nopl   0x0(%rax)
 856:	66 2e 0f 1f 84 00 00 	nopw   %cs:0x0(%rax,%rax,1)
 85d:	00 00 00

0000000000000860 <register_tm_clones>:
 860:	48 8d 3d c1 17 20 00 	lea    0x2017c1(%rip),%rdi        # 202028 <__TMC_END__>
 867:	48 8d 35 ba 17 20 00 	lea    0x2017ba(%rip),%rsi        # 202028 <__TMC_END__>
 86e:	55                   	push   %rbp
 86f:	48 29 fe             	sub    %rdi,%rsi
 872:	48 89 e5             	mov    %rsp,%rbp
 875:	48 c1 fe 03          	sar    $0x3,%rsi
 879:	48 89 f0             	mov    %rsi,%rax
 87c:	48 c1 e8 3f          	shr    $0x3f,%rax
 880:	48 01 c6             	add    %rax,%rsi
 883:	48 d1 fe             	sar    %rsi
 886:	74 18                	je     8a0 <register_tm_clones+0x40>
 888:	48 8b 05 61 17 20 00 	mov    0x201761(%rip),%rax        # 201ff0 <_DYNAMIC+0x230>
 88f:	48 85 c0             	test   %rax,%rax
 892:	74 0c                	je     8a0 <register_tm_clones+0x40>
 894:	5d                   	pop    %rbp
 895:	ff e0                	jmpq   *%rax
 897:	66 0f 1f 84 00 00 00 	nopw   0x0(%rax,%rax,1)
 89e:	00 00
 8a0:	5d                   	pop    %rbp
 8a1:	c3                   	retq
 8a2:	0f 1f 40 00          	nopl   0x0(%rax)
 8a6:	66 2e 0f 1f 84 00 00 	nopw   %cs:0x0(%rax,%rax,1)
 8ad:	00 00 00

00000000000008b0 <__do_global_dtors_aux>:
 8b0:	80 3d 6d 17 20 00 00 	cmpb   $0x0,0x20176d(%rip)        # 202024 <_edata>
 8b7:	75 27                	jne    8e0 <__do_global_dtors_aux+0x30>
 8b9:	48 83 3d 37 17 20 00 	cmpq   $0x0,0x201737(%rip)        # 201ff8 <_DYNAMIC+0x238>
 8c0:	00
 8c1:	55                   	push   %rbp
 8c2:	48 89 e5             	mov    %rsp,%rbp
 8c5:	74 0c                	je     8d3 <__do_global_dtors_aux+0x23>
 8c7:	48 8d 3d 82 14 20 00 	lea    0x201482(%rip),%rdi        # 201d50 <__dso_handle>
 8ce:	e8 0d ff ff ff       	callq  7e0 <puts@plt+0x10>
 8d3:	e8 48 ff ff ff       	callq  820 <deregister_tm_clones>
 8d8:	5d                   	pop    %rbp
 8d9:	c6 05 44 17 20 00 01 	movb   $0x1,0x201744(%rip)        # 202024 <_edata>
 8e0:	f3 c3                	repz retq
 8e2:	0f 1f 40 00          	nopl   0x0(%rax)
 8e6:	66 2e 0f 1f 84 00 00 	nopw   %cs:0x0(%rax,%rax,1)
 8ed:	00 00 00

00000000000008f0 <frame_dummy>:
 8f0:	48 8d 3d 51 14 20 00 	lea    0x201451(%rip),%rdi        # 201d48 <__JCR_END__>
 8f7:	48 83 3f 00          	cmpq   $0x0,(%rdi)
 8fb:	75 0b                	jne    908 <frame_dummy+0x18>
 8fd:	e9 5e ff ff ff       	jmpq   860 <register_tm_clones>
 902:	66 0f 1f 44 00 00    	nopw   0x0(%rax,%rax,1)
 908:	48 8b 05 c9 16 20 00 	mov    0x2016c9(%rip),%rax        # 201fd8 <_DYNAMIC+0x218>
 90f:	48 85 c0             	test   %rax,%rax
 912:	74 e9                	je     8fd <frame_dummy+0xd>
 914:	55                   	push   %rbp
 915:	48 89 e5             	mov    %rsp,%rbp
 918:	ff d0                	callq  *%rax
 91a:	5d                   	pop    %rbp
 91b:	e9 40 ff ff ff       	jmpq   860 <register_tm_clones>

0000000000000920 <func(Base&)>:
 920:	55                   	push   %rbp
 921:	48 89 e5             	mov    %rsp,%rbp
 924:	48 83 ec 10          	sub    $0x10,%rsp
 928:	48 89 7d f8          	mov    %rdi,-0x8(%rbp)
 92c:	48 8b 45 f8          	mov    -0x8(%rbp),%rax
 930:	48 8b 00             	mov    (%rax),%rax
 933:	48 8b 00             	mov    (%rax),%rax
 936:	48 8b 55 f8          	mov    -0x8(%rbp),%rdx
 93a:	48 89 d7             	mov    %rdx,%rdi
 93d:	ff d0                	callq  *%rax
 93f:	48 8b 45 f8          	mov    -0x8(%rbp),%rax
 943:	48 8b 00             	mov    (%rax),%rax
 946:	48 83 c0 08          	add    $0x8,%rax
 94a:	48 8b 00             	mov    (%rax),%rax
 94d:	48 8b 55 f8          	mov    -0x8(%rbp),%rdx
 951:	48 89 d7             	mov    %rdx,%rdi
 954:	ff d0                	callq  *%rax
 956:	90                   	nop
 957:	c9                   	leaveq
 958:	c3                   	retq

0000000000000959 <main>:
 959:	55                   	push   %rbp
 95a:	48 89 e5             	mov    %rsp,%rbp
 95d:	48 83 ec 10          	sub    $0x10,%rsp
 961:	48 8d 45 f8          	lea    -0x8(%rbp),%rax
 965:	48 89 c7             	mov    %rax,%rdi
 968:	e8 75 00 00 00       	callq  9e2 <Derived::Derived()>
 96d:	48 8d 45 f8          	lea    -0x8(%rbp),%rax
 971:	48 89 c7             	mov    %rax,%rdi
 974:	e8 a7 ff ff ff       	callq  920 <func(Base&)>
 979:	b8 00 00 00 00       	mov    $0x0,%eax
 97e:	c9                   	leaveq
 97f:	c3                   	retq

0000000000000980 <Base::Base()>:
 980:	55                   	push   %rbp
 981:	48 89 e5             	mov    %rsp,%rbp
 984:	48 83 ec 10          	sub    $0x10,%rsp
 988:	48 89 7d f8          	mov    %rdi,-0x8(%rbp)
 98c:	48 8d 15 f5 13 20 00 	lea    0x2013f5(%rip),%rdx        # 201d88 <vtable for Base+0x10>
 993:	48 8b 45 f8          	mov    -0x8(%rbp),%rax
 997:	48 89 10             	mov    %rdx,(%rax)
 99a:	48 8d 3d 23 01 00 00 	lea    0x123(%rip),%rdi        # ac4 <_IO_stdin_used+0x4>
 9a1:	e8 2a fe ff ff       	callq  7d0 <puts@plt>
 9a6:	90                   	nop
 9a7:	c9                   	leaveq
 9a8:	c3                   	retq
 9a9:	90                   	nop

00000000000009aa <Base::hoge()>:
 9aa:	55                   	push   %rbp
 9ab:	48 89 e5             	mov    %rsp,%rbp
 9ae:	48 83 ec 10          	sub    $0x10,%rsp
 9b2:	48 89 7d f8          	mov    %rdi,-0x8(%rbp)
 9b6:	48 8d 3d 1f 01 00 00 	lea    0x11f(%rip),%rdi        # adc <_IO_stdin_used+0x1c>
 9bd:	e8 0e fe ff ff       	callq  7d0 <puts@plt>
 9c2:	90                   	nop
 9c3:	c9                   	leaveq
 9c4:	c3                   	retq
 9c5:	90                   	nop

00000000000009c6 <Base::fuga()>:
 9c6:	55                   	push   %rbp
 9c7:	48 89 e5             	mov    %rsp,%rbp
 9ca:	48 83 ec 10          	sub    $0x10,%rsp
 9ce:	48 89 7d f8          	mov    %rdi,-0x8(%rbp)
 9d2:	48 8d 3d 1b 01 00 00 	lea    0x11b(%rip),%rdi        # af4 <_IO_stdin_used+0x34>
 9d9:	e8 f2 fd ff ff       	callq  7d0 <puts@plt>
 9de:	90                   	nop
 9df:	c9                   	leaveq
 9e0:	c3                   	retq
 9e1:	90                   	nop

00000000000009e2 <Derived::Derived()>:
 9e2:	55                   	push   %rbp
 9e3:	48 89 e5             	mov    %rsp,%rbp
 9e6:	48 83 ec 10          	sub    $0x10,%rsp
 9ea:	48 89 7d f8          	mov    %rdi,-0x8(%rbp)
 9ee:	48 8b 45 f8          	mov    -0x8(%rbp),%rax
 9f2:	48 89 c7             	mov    %rax,%rdi
 9f5:	e8 86 ff ff ff       	callq  980 <Base::Base()>
 9fa:	48 8d 15 67 13 20 00 	lea    0x201367(%rip),%rdx        # 201d68 <vtable for Derived+0x10>
 a01:	48 8b 45 f8          	mov    -0x8(%rbp),%rax
 a05:	48 89 10             	mov    %rdx,(%rax)
 a08:	48 8d 3d fd 00 00 00 	lea    0xfd(%rip),%rdi        # b0c <_IO_stdin_used+0x4c>
 a0f:	e8 bc fd ff ff       	callq  7d0 <puts@plt>
 a14:	90                   	nop
 a15:	c9                   	leaveq
 a16:	c3                   	retq
 a17:	90                   	nop

0000000000000a18 <Derived::fuga()>:
 a18:	55                   	push   %rbp
 a19:	48 89 e5             	mov    %rsp,%rbp
 a1c:	48 83 ec 10          	sub    $0x10,%rsp
 a20:	48 89 7d f8          	mov    %rdi,-0x8(%rbp)
 a24:	48 8d 3d ff 00 00 00 	lea    0xff(%rip),%rdi        # b2a <_IO_stdin_used+0x6a>
 a2b:	e8 a0 fd ff ff       	callq  7d0 <puts@plt>
 a30:	90                   	nop
 a31:	c9                   	leaveq
 a32:	c3                   	retq
 a33:	66 2e 0f 1f 84 00 00 	nopw   %cs:0x0(%rax,%rax,1)
 a3a:	00 00 00
 a3d:	0f 1f 00             	nopl   (%rax)

0000000000000a40 <__libc_csu_init>:
 a40:	41 57                	push   %r15
 a42:	41 56                	push   %r14
 a44:	41 89 ff             	mov    %edi,%r15d
 a47:	41 55                	push   %r13
 a49:	41 54                	push   %r12
 a4b:	4c 8d 25 e6 12 20 00 	lea    0x2012e6(%rip),%r12        # 201d38 <__frame_dummy_init_array_entry>
 a52:	55                   	push   %rbp
 a53:	48 8d 2d e6 12 20 00 	lea    0x2012e6(%rip),%rbp        # 201d40 <__init_array_end>
 a5a:	53                   	push   %rbx
 a5b:	49 89 f6             	mov    %rsi,%r14
 a5e:	49 89 d5             	mov    %rdx,%r13
 a61:	4c 29 e5             	sub    %r12,%rbp
 a64:	48 83 ec 08          	sub    $0x8,%rsp
 a68:	48 c1 fd 03          	sar    $0x3,%rbp
 a6c:	e8 2f fd ff ff       	callq  7a0 <_init>
 a71:	48 85 ed             	test   %rbp,%rbp
 a74:	74 20                	je     a96 <__libc_csu_init+0x56>
 a76:	31 db                	xor    %ebx,%ebx
 a78:	0f 1f 84 00 00 00 00 	nopl   0x0(%rax,%rax,1)
 a7f:	00
 a80:	4c 89 ea             	mov    %r13,%rdx
 a83:	4c 89 f6             	mov    %r14,%rsi
 a86:	44 89 ff             	mov    %r15d,%edi
 a89:	41 ff 14 dc          	callq  *(%r12,%rbx,8)
 a8d:	48 83 c3 01          	add    $0x1,%rbx
 a91:	48 39 dd             	cmp    %rbx,%rbp
 a94:	75 ea                	jne    a80 <__libc_csu_init+0x40>
 a96:	48 83 c4 08          	add    $0x8,%rsp
 a9a:	5b                   	pop    %rbx
 a9b:	5d                   	pop    %rbp
 a9c:	41 5c                	pop    %r12
 a9e:	41 5d                	pop    %r13
 aa0:	41 5e                	pop    %r14
 aa2:	41 5f                	pop    %r15
 aa4:	c3                   	retq
 aa5:	90                   	nop
 aa6:	66 2e 0f 1f 84 00 00 	nopw   %cs:0x0(%rax,%rax,1)
 aad:	00 00 00

0000000000000ab0 <__libc_csu_fini>:
 ab0:	f3 c3                	repz retq

Disassembly of section .fini:

0000000000000ab4 <_fini>:
 ab4:	48 83 ec 08          	sub    $0x8,%rsp
 ab8:	48 83 c4 08          	add    $0x8,%rsp
 abc:	c3                   	retq

先程宿題にした.plt.gotが逆アセンブルされている.xchg %ax,%axはNOPなので,*(%rip + 0x201812)にJMPするだけのコードのようだ.
以下,普通にビルドしたプログラムの逆アセンブル結果と比較してみる.量が多いのでDerived::Derived()だけ抜き出す.

普通にビルドした方.

0000000000400760 <Derived::Derived()>:
  400760:	55                   	push   %rbp
  400761:	48 89 e5             	mov    %rsp,%rbp
  400764:	48 83 ec 10          	sub    $0x10,%rsp
  400768:	48 89 7d f8          	mov    %rdi,-0x8(%rbp)
  40076c:	48 8b 45 f8          	mov    -0x8(%rbp),%rax
  400770:	48 89 c7             	mov    %rax,%rdi
  400773:	e8 8e ff ff ff       	callq  400706 <Base::Base()>
  400778:	ba d8 08 40 00       	mov    $0x4008d8,%edx
  40077d:	48 8b 45 f8          	mov    -0x8(%rbp),%rax
  400781:	48 89 10             	mov    %rdx,(%rax)
  400784:	bf 88 08 40 00       	mov    $0x400888,%edi
  400789:	e8 12 fe ff ff       	callq  4005a0 <puts@plt>
  40078e:	90                   	nop
  40078f:	c9                   	leaveq
  400790:	c3                   	retq
  400791:	90                   	nop

PIEの方.

00000000000009e2 <Derived::Derived()>:
 9e2:	55                   	push   %rbp
 9e3:	48 89 e5             	mov    %rsp,%rbp
 9e6:	48 83 ec 10          	sub    $0x10,%rsp
 9ea:	48 89 7d f8          	mov    %rdi,-0x8(%rbp)
 9ee:	48 8b 45 f8          	mov    -0x8(%rbp),%rax
 9f2:	48 89 c7             	mov    %rax,%rdi
 9f5:	e8 86 ff ff ff       	callq  980 <Base::Base()>
 9fa:	48 8d 15 67 13 20 00 	lea    0x201367(%rip),%rdx        # 201d68 <vtable for Derived+0x10>
 a01:	48 8b 45 f8          	mov    -0x8(%rbp),%rax
 a05:	48 89 10             	mov    %rdx,(%rax)
 a08:	48 8d 3d fd 00 00 00 	lea    0xfd(%rip),%rdi        # b0c <_IO_stdin_used+0x4c>
 a0f:	e8 bc fd ff ff       	callq  7d0 <puts@plt>
 a14:	90                   	nop
 a15:	c9                   	leaveq
 a16:	c3                   	retq
 a17:	90                   	nop

見比べてみると,call命令はそのままアドレスが格納されているが,vtableのアドレスやputs()の引数(ここでputsが呼ばれているのはprintfが最適化されているため)は%ripから計算していることが分かる.普通にビルドした方はともかく,PIEの方もcall先のアドレスが固定(e.g. Base::Base() -> 0x980)になっているように見えるが,これはASLRが有効であれば実行時にランダムな値が足し合わさるようだ.
gdbで実行時のアドレスを確認してみる.gdbではASLRが無効化されるので,disable-randomizationオプションをoffにすることで有効化する.

$ gdb -ex 'set print asm-demangle on' -ex 'set disable-randomization off' vtable_pie
(gdb) break Derived::Derived()
Breakpoint 1 at 0x9e6
(gdb) run
Starting program: /home/vagrant/vtable_pie

Breakpoint 1, 0x000055e54d3719e6 in Derived::Derived() ()
(gdb) disassemble
Dump of assembler code for function _ZN7DerivedC2Ev:
   0x000055e54d3719e2 <+0>:	push   %rbp
   0x000055e54d3719e3 <+1>:	mov    %rsp,%rbp
=> 0x000055e54d3719e6 <+4>:	sub    $0x10,%rsp
   0x000055e54d3719ea <+8>:	mov    %rdi,-0x8(%rbp)
   0x000055e54d3719ee <+12>:	mov    -0x8(%rbp),%rax
   0x000055e54d3719f2 <+16>:	mov    %rax,%rdi
   0x000055e54d3719f5 <+19>:	callq  0x55e54d371980 <Base::Base()>
   0x000055e54d3719fa <+24>:	lea    0x201367(%rip),%rdx        # 0x55e54d572d68 <vtable for Derived+16>
   0x000055e54d371a01 <+31>:	mov    -0x8(%rbp),%rax
   0x000055e54d371a05 <+35>:	mov    %rdx,(%rax)
   0x000055e54d371a08 <+38>:	lea    0xfd(%rip),%rdi        # 0x55e54d371b0c
   0x000055e54d371a0f <+45>:	callq  0x55e54d3717d0 <puts@plt>
   0x000055e54d371a14 <+50>:	nop
   0x000055e54d371a15 <+51>:	leaveq
   0x000055e54d371a16 <+52>:	retq
End of assembler dump.
(gdb)

再度起動し,アドレス空間が変わっていることを確認する.

(gdb) run
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/vagrant/vtable_pie

Breakpoint 1, 0x000055dda61629e6 in Derived::Derived() ()
(gdb) disassemble
Dump of assembler code for function _ZN7DerivedC2Ev:
   0x000055dda61629e2 <+0>:	push   %rbp
   0x000055dda61629e3 <+1>:	mov    %rsp,%rbp
=> 0x000055dda61629e6 <+4>:	sub    $0x10,%rsp
   0x000055dda61629ea <+8>:	mov    %rdi,-0x8(%rbp)
   0x000055dda61629ee <+12>:	mov    -0x8(%rbp),%rax
   0x000055dda61629f2 <+16>:	mov    %rax,%rdi
   0x000055dda61629f5 <+19>:	callq  0x55dda6162980 <Base::Base()>
   0x000055dda61629fa <+24>:	lea    0x201367(%rip),%rdx        # 0x55dda6363d68 <vtable for Derived+16>
   0x000055dda6162a01 <+31>:	mov    -0x8(%rbp),%rax
   0x000055dda6162a05 <+35>:	mov    %rdx,(%rax)
   0x000055dda6162a08 <+38>:	lea    0xfd(%rip),%rdi        # 0x55dda6162b0c
   0x000055dda6162a0f <+45>:	callq  0x55dda61627d0 <puts@plt>
   0x000055dda6162a14 <+50>:	nop
   0x000055dda6162a15 <+51>:	leaveq
   0x000055dda6162a16 <+52>:	retq
End of assembler dump.
(gdb)

呼び出す関数(Base::Base(),puts@plt)のアドレスが変わっていることが分かる.
実行時にアドレスが足し合わされるといった説明を見たが,call命令のオペランドが実行時に機械語のレベルで書き換えられるのだろうか.それなら確かにパフォーマンス低下もうなずける.というかそれ無茶では…?
実際にこの時の機械語をチェックしてみよう.gdbのdisassemble命令に/rというオプションを付けるとマシン語の対応が分かる.

(gdb) disassemble /r
Dump of assembler code for function _ZN7DerivedC2Ev:
   0x000056083c3fc9e2 <+0>:	55	push   %rbp
   0x000056083c3fc9e3 <+1>:	48 89 e5	mov    %rsp,%rbp
=> 0x000056083c3fc9e6 <+4>:	48 83 ec 10	sub    $0x10,%rsp
   0x000056083c3fc9ea <+8>:	48 89 7d f8	mov    %rdi,-0x8(%rbp)
   0x000056083c3fc9ee <+12>:	48 8b 45 f8	mov    -0x8(%rbp),%rax
   0x000056083c3fc9f2 <+16>:	48 89 c7	mov    %rax,%rdi
   0x000056083c3fc9f5 <+19>:	e8 86 ff ff ff	callq  0x56083c3fc980 <Base::Base()>
   0x000056083c3fc9fa <+24>:	48 8d 15 67 13 20 00	lea    0x201367(%rip),%rdx        # 0x56083c5fdd68 <vtable for Derived+16>
   0x000056083c3fca01 <+31>:	48 8b 45 f8	mov    -0x8(%rbp),%rax
   0x000056083c3fca05 <+35>:	48 89 10	mov    %rdx,(%rax)
   0x000056083c3fca08 <+38>:	48 8d 3d fd 00 00 00	lea    0xfd(%rip),%rdi        # 0x56083c3fcb0c
   0x000056083c3fca0f <+45>:	e8 bc fd ff ff	callq  0x56083c3fc7d0 <puts@plt>
   0x000056083c3fca14 <+50>:	90	nop
   0x000056083c3fca15 <+51>:	c9	leaveq
   0x000056083c3fca16 <+52>:	c3	retq
End of assembler dump.

call命令だけ抜き出して,objdumpの逆アセンブル結果と見比べてみる.
gdb

0x000056083c3fc9f5 <+19>:	e8 86 ff ff ff	callq  0x56083c3fc980 <Base::Base()>

objdump

9f5:	e8 86 ff ff ff       	callq  980 <Base::Base()>

変わってない…?と思ったが,callqのOpcodeを見ると「Call near, relative, displacement relative to next instruction」とのことなので,%ripからの相対位置で呼び出しているようだ.
Liberation: x86 Instruction Set Reference
普通にビルドした方も見てみる.

400773:	e8 8e ff ff ff       	callq  400706 <Base::Base()>

同じくOpcodeがE8なので,おそらくここでパフォーマンス低下は招いていないだろう.

これらの結果より,PIEでない場合は即値で扱っていたアドレスがlea命令に置き換わったことで,ある程度のパフォーマンス低下が起きていると考えられる.また,ローダの処理が追加されていることもパフォーマンス低下を招いていると予想できる.これ以上はちゃんとサーベイしないと分からないので,もっと論文読んで出直します.

今回の内容マサカリビリティに溢れているので怖いっすね

参考資料:
OSセキュリティチュートリアル
PIE (位置独立実行形式) を作成する - bkブログ
Linux の共有ライブラリを作るとき PIC でコンパイルするのはなぜか - bkブログ
革命の日々! PIE(位置独立実行ファイル)で遊んでみる

vtableの中身を見てみる

C++ポリモーフィズムを実現するためにvtableと呼ばれる機構が用いられている.
だいたいの入門本でvtableという言葉は出てくるものの,実装については特に触れられていないので中身を見てみた.

検証用プログラムには以下を用いた.また,実行環境はGNU/Linux Fedora 25を用い,コンパイラgcc 6.4.1を用いた.

#include <cstdio>

class Base {
public:
    Base() { printf("Base::Base() has called\n"); }
    virtual void hoge() { printf("Base::hoge() has called\n"); }
    virtual void fuga() { printf("Base::fuga() has called\n"); }
};

class Derived : public Base {
public:
    Derived() { printf("Derived::Derived() has called\n"); }
    void fuga() { printf("Derived::fuga() has called\n"); }
};

void func(Base &x)
{
    x.hoge();
    x.fuga();
}

int main()
{
    Derived obj;
    func(obj);

    return 0;
}

コンパイルして実行すると以下のようになる.

$ g++ vtable.cpp
$ ./a.out
Base::Base() has called
Derived::Derived() has called
Base::hoge() has called
Derived::fuga() has called

ではobjdumpで逆アセンブルしてみて中身を見てみよう.

$ objdump -d a.out | c++filt

a.out:     file format elf64-x86-64

〜
Disassembly of section .text:
〜
00000000004006a6 <func(Base&)>:
  4006a6:	55                   	push   %rbp
  4006a7:	48 89 e5             	mov    %rsp,%rbp
  4006aa:	48 83 ec 10          	sub    $0x10,%rsp
  4006ae:	48 89 7d f8          	mov    %rdi,-0x8(%rbp)
  4006b2:	48 8b 45 f8          	mov    -0x8(%rbp),%rax
  4006b6:	48 8b 00             	mov    (%rax),%rax
  4006b9:	48 8b 00             	mov    (%rax),%rax
  4006bc:	48 8b 55 f8          	mov    -0x8(%rbp),%rdx
  4006c0:	48 89 d7             	mov    %rdx,%rdi
  4006c3:	ff d0                	callq  *%rax
  4006c5:	48 8b 45 f8          	mov    -0x8(%rbp),%rax
  4006c9:	48 8b 00             	mov    (%rax),%rax
  4006cc:	48 83 c0 08          	add    $0x8,%rax
  4006d0:	48 8b 00             	mov    (%rax),%rax
  4006d3:	48 8b 55 f8          	mov    -0x8(%rbp),%rdx
  4006d7:	48 89 d7             	mov    %rdx,%rdi
  4006da:	ff d0                	callq  *%rax
  4006dc:	90                   	nop
  4006dd:	c9                   	leaveq
  4006de:	c3                   	retq

00000000004006df <main>:
  4006df:	55                   	push   %rbp
  4006e0:	48 89 e5             	mov    %rsp,%rbp
  4006e3:	48 83 ec 10          	sub    $0x10,%rsp
  4006e7:	48 8d 45 f8          	lea    -0x8(%rbp),%rax
  4006eb:	48 89 c7             	mov    %rax,%rdi
  4006ee:	e8 6d 00 00 00       	callq  400760 <Derived::Derived()>
  4006f3:	48 8d 45 f8          	lea    -0x8(%rbp),%rax
  4006f7:	48 89 c7             	mov    %rax,%rdi
  4006fa:	e8 a7 ff ff ff       	callq  4006a6 <func(Base&)>
  4006ff:	b8 00 00 00 00       	mov    $0x0,%eax
  400704:	c9                   	leaveq
  400705:	c3                   	retq

0000000000400706 <Base::Base()>:
  400706:	55                   	push   %rbp
  400707:	48 89 e5             	mov    %rsp,%rbp
  40070a:	48 83 ec 10          	sub    $0x10,%rsp
  40070e:	48 89 7d f8          	mov    %rdi,-0x8(%rbp)
  400712:	ba f8 08 40 00       	mov    $0x4008f8,%edx
  400717:	48 8b 45 f8          	mov    -0x8(%rbp),%rax
  40071b:	48 89 10             	mov    %rdx,(%rax)
  40071e:	bf 40 08 40 00       	mov    $0x400840,%edi
  400723:	e8 78 fe ff ff       	callq  4005a0 <puts@plt>
  400728:	90                   	nop
  400729:	c9                   	leaveq
  40072a:	c3                   	retq
  40072b:	90                   	nop

000000000040072c <Base::hoge()>:
  40072c:	55                   	push   %rbp
  40072d:	48 89 e5             	mov    %rsp,%rbp
  400730:	48 83 ec 10          	sub    $0x10,%rsp
  400734:	48 89 7d f8          	mov    %rdi,-0x8(%rbp)
  400738:	bf 58 08 40 00       	mov    $0x400858,%edi
  40073d:	e8 5e fe ff ff       	callq  4005a0 <puts@plt>
  400742:	90                   	nop
  400743:	c9                   	leaveq
  400744:	c3                   	retq
  400745:	90                   	nop

0000000000400746 <Base::fuga()>:
  400746:	55                   	push   %rbp
  400747:	48 89 e5             	mov    %rsp,%rbp
  40074a:	48 83 ec 10          	sub    $0x10,%rsp
  40074e:	48 89 7d f8          	mov    %rdi,-0x8(%rbp)
  400752:	bf 70 08 40 00       	mov    $0x400870,%edi
  400757:	e8 44 fe ff ff       	callq  4005a0 <puts@plt>
  40075c:	90                   	nop
  40075d:	c9                   	leaveq
  40075e:	c3                   	retq
  40075f:	90                   	nop

0000000000400760 <Derived::Derived()>:
  400760:	55                   	push   %rbp
  400761:	48 89 e5             	mov    %rsp,%rbp
  400764:	48 83 ec 10          	sub    $0x10,%rsp
  400768:	48 89 7d f8          	mov    %rdi,-0x8(%rbp)
  40076c:	48 8b 45 f8          	mov    -0x8(%rbp),%rax
  400770:	48 89 c7             	mov    %rax,%rdi
  400773:	e8 8e ff ff ff       	callq  400706 <Base::Base()>
  400778:	ba d8 08 40 00       	mov    $0x4008d8,%edx
  40077d:	48 8b 45 f8          	mov    -0x8(%rbp),%rax
  400781:	48 89 10             	mov    %rdx,(%rax)
  400784:	bf 88 08 40 00       	mov    $0x400888,%edi
  400789:	e8 12 fe ff ff       	callq  4005a0 <puts@plt>
  40078e:	90                   	nop
  40078f:	c9                   	leaveq
  400790:	c3                   	retq
  400791:	90                   	nop

0000000000400792 <Derived::fuga()>:
  400792:	55                   	push   %rbp
  400793:	48 89 e5             	mov    %rsp,%rbp
  400796:	48 83 ec 10          	sub    $0x10,%rsp
  40079a:	48 89 7d f8          	mov    %rdi,-0x8(%rbp)
  40079e:	bf a6 08 40 00       	mov    $0x4008a6,%edi
  4007a3:	e8 f8 fd ff ff       	callq  4005a0 <puts@plt>
  4007a8:	90                   	nop
  4007a9:	c9                   	leaveq
  4007aa:	c3                   	retq
  4007ab:	0f 1f 44 00 00       	nopl   0x0(%rax,%rax,1)

まずはfunc()から見てみる.

00000000004006a6 <func(Base&)>:
# スタックを掘る
  4006a6:	55                   	push   %rbp
  4006a7:	48 89 e5             	mov    %rsp,%rbp
  4006aa:	48 83 ec 10          	sub    $0x10,%rsp

# Linux x86_64なのでrdiには1番目の引数が入る
# メモリの(rbp-0x8)番地に1番目の引数(つまりBase &x)を格納
  4006ae:	48 89 7d f8          	mov    %rdi,-0x8(%rbp)
# メモリからraxにコピー
  4006b2:	48 8b 45 f8          	mov    -0x8(%rbp),%rax
# raxの指すメモリを2回デリファレンス(rax=*rax)
  4006b6:	48 8b 00             	mov    (%rax),%rax
  4006b9:	48 8b 00             	mov    (%rax),%rax
# rdxにメモリから&xをコピーし,更にrdiにコピー
  4006bc:	48 8b 55 f8          	mov    -0x8(%rbp),%rdx
  4006c0:	48 89 d7             	mov    %rdx,%rdi
# raxの指す関数をcall
  4006c3:	ff d0                	callq  *%rax
# もう一度メモリから&xをコピーし,raxに格納
  4006c5:	48 8b 45 f8          	mov    -0x8(%rbp),%rax
# 今度はraxの指すメモリをデリファレンスし,そのアドレスに0x8を加える
  4006c9:	48 8b 00             	mov    (%rax),%rax
  4006cc:	48 83 c0 08          	add    $0x8,%rax
# そしてそのアドレスをデリファレンス
  4006d0:	48 8b 00             	mov    (%rax),%rax
# rdxにメモリから&xをコピーし,更にrdiにコピー
  4006d3:	48 8b 55 f8          	mov    -0x8(%rbp),%rdx
  4006d7:	48 89 d7             	mov    %rdx,%rdi
# raxの指す関数をcall
  4006da:	ff d0                	callq  *%rax
# 終了処理
  4006dc:	90                   	nop
  4006dd:	c9                   	leaveq
  4006de:	c3                   	retq

func()にはオブジェクトのアドレスが格納されているので,オブジェクトの先頭位置を一度デリファレンスし,その情報を元に呼び出す関数を決定している.
では,オブジェクトの先頭に何が格納されているかを確認する.オブジェクトの初期化部分から見ていこう.

00000000004006df <main>:
# スタックを掘る
  4006df:	55                   	push   %rbp
  4006e0:	48 89 e5             	mov    %rsp,%rbp
  4006e3:	48 83 ec 10          	sub    $0x10,%rsp
# スタックの(rbp-0x8)のアドレスをraxに格納し,それをrdiにコピー
  4006e7:	48 8d 45 f8          	lea    -0x8(%rbp),%rax
  4006eb:	48 89 c7             	mov    %rax,%rdi
# コンストラクタの呼び出し
  4006ee:	e8 6d 00 00 00       	callq  400760 <Derived::Derived()>
〜

スタック上にメモリを確保し,それをrdi経由でDerived::Derived()に渡していることが分かる.
では,Derived::Derived()を見ていこう.

0000000000400760 <Derived::Derived()>:
# スタックを掘る
  400760:	55                   	push   %rbp
  400761:	48 89 e5             	mov    %rsp,%rbp
  400764:	48 83 ec 10          	sub    $0x10,%rsp
# 引数をメモリにコピー
  400768:	48 89 7d f8          	mov    %rdi,-0x8(%rbp)
# メモリから引数をraxにコピーし,rdiにコピー
  40076c:	48 8b 45 f8          	mov    -0x8(%rbp),%rax
  400770:	48 89 c7             	mov    %rax,%rdi
# Base::Base()の呼び出し
  400773:	e8 8e ff ff ff       	callq  400706 <Base::Base()>
# edxに即値を代入
  400778:	ba d8 08 40 00       	mov    $0x4008d8,%edx
# メモリから引数をraxにコピー
  40077d:	48 8b 45 f8          	mov    -0x8(%rbp),%rax
# 先程の即値をraxの参照先にコピー
  400781:	48 89 10             	mov    %rdx,(%rax)
# ediに即値を代入(おそらく文字列のアドレスだろう)
  400784:	bf 88 08 40 00       	mov    $0x400888,%edi
# puts@pltを呼び出し
  400789:	e8 12 fe ff ff       	callq  4005a0 <puts@plt>
# 終了処理
  40078e:	90                   	nop
  40078f:	c9                   	leaveq
  400790:	c3                   	retq
  400791:	90                   	nop

突然出てきた即値も気になるが,まずはBase::Base()を見ていこう.

0000000000400706 <Base::Base()>:
# スタックを掘る
  400706:	55                   	push   %rbp
  400707:	48 89 e5             	mov    %rsp,%rbp
  40070a:	48 83 ec 10          	sub    $0x10,%rsp
# 引数をメモリにコピー
  40070e:	48 89 7d f8          	mov    %rdi,-0x8(%rbp)
# 即値をedxに代入
  400712:	ba f8 08 40 00       	mov    $0x4008f8,%edx
# メモリから引数をraxにコピー
  400717:	48 8b 45 f8          	mov    -0x8(%rbp),%rax
# 先程の即値をraxの参照先にコピー
  40071b:	48 89 10             	mov    %rdx,(%rax)
# ediに即値を代入(おそらく文字列のアドレスだろう)
  40071e:	bf 40 08 40 00       	mov    $0x400840,%edi
# puts@pltを呼び出し
  400723:	e8 78 fe ff ff       	callq  4005a0 <puts@plt>
# 終了処理
  400728:	90                   	nop
  400729:	c9                   	leaveq
  40072a:	c3                   	retq
  40072b:	90                   	nop

こちらでも同じく即値を引数の参照先にコピーしている.コンストラクタに渡される引数は未初期化オブジェクトのアドレスである.main()でスタックから確保したアドレスが格納され,その先頭位置に即値が代入されている.そして,Base::Base()で設定した$0x4008f8という値は,Derived::Derived()の以降の処理で$0x4008d8に上書きされている.

くどいようだが,もう一度C++ソースコードからコンストラクタの定義を見てみよう.

class Base {
public:
    Base() { printf("Base::Base() has called\n"); }
    virtual void hoge() { printf("Base::hoge() has called\n"); }
    virtual void fuga() { printf("Base::fuga() has called\n"); }
};

class Derived : public Base {
public:
    Derived() { printf("Derived::Derived() has called\n"); }
    void fuga() { printf("Derived::fuga() has called\n"); }
};

中では特に代入処理はしていない.では,Derived::Derived()の$0x4008d8とBase::Base()の$0x4008f8とは何なのだろうか.
この実行時のアドレスが位置するセクションが知りたいので,readelfコマンドでセクションヘッダを見てみよう.
(追記)今回は単一のリンク済み実行ファイルを見ているのでセクションヘッダの位置が*たまたま*実行時のアドレスに対応しています.実行時のアドレスに対応するファイル位置を確認したいのであれば,本来はプログラムヘッダを見なければなりません.(誰もこんな記事見ないと思いますが)以下の文章は間違っているので気をつけてください.

$ readelf -S a.out
There are 30 section headers, starting at offset 0x1c48:

Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
〜
  [15] .rodata           PROGBITS         0000000000400830  00000830
       0000000000000116  0000000000000000   A       0     0     8
〜
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), l (large)
  I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
  O (extra OS processing required) o (OS specific), p (processor specific)

0x400830+0x116の範囲は.rodataであることが分かった.つまり,$0x4008d8と$0x4008f8は(ついでにputs@pltの前にあった$0x400888と$0x400840も).rodataに存在する.
readelfコマンドで.rodataをダンプして中身を見てみよう.

$ readelf -x .rodata a.out

Hex dump of section '.rodata':
  0x00400830 01000200 00000000 00000000 00000000 ................
  0x00400840 42617365 3a3a4261 73652829 20686173 Base::Base() has
  0x00400850 2063616c 6c656400 42617365 3a3a686f  called.Base::ho
  0x00400860 67652829 20686173 2063616c 6c656400 ge() has called.
  0x00400870 42617365 3a3a6675 67612829 20686173 Base::fuga() has
  0x00400880 2063616c 6c656400 44657269 7665643a  called.Derived:
  0x00400890 3a446572 69766564 28292068 61732063 :Derived() has c
  0x004008a0 616c6c65 64004465 72697665 643a3a66 alled.Derived::f
  0x004008b0 75676128 29206861 73206361 6c6c6564 uga() has called
  0x004008c0 00000000 00000000 00000000 00000000 ................
  0x004008d0 08094000 00000000 2c074000 00000000 ..@.....,.@.....
  0x004008e0 92074000 00000000 00000000 00000000 ..@.............
  0x004008f0 30094000 00000000 2c074000 00000000 0.@.....,.@.....
  0x00400900 46074000 00000000 90106000 00000000 F.@.......`.....
  0x00400910 20094000 00000000 30094000 00000000  .@.....0.@.....
  0x00400920 37446572 69766564 00000000 00000000 7Derived........
  0x00400930 38106000 00000000 40094000 00000000 8.`.....@.@.....
  0x00400940 34426173 6500                       4Base.

とりあえず文字列が存在することは分かったが,見づらいので先程のreadelf -Sのアドレス情報とオフセット情報を元に,odで$0x4008d8と$0x4008f8に該当するファイル位置をダンプしてみる..rodataの開始アドレスである0x400830のオフセットは0x830であるため,実行時に0x4008d8に展開される部分は単純にファイルの0x8d8バイト目でよさそうだ.

$ od -j 0x8d8 -N 16 -tx8 -Ax a.out
0008d8 000000000040072c 0000000000400792
0008e8
$ od -j 0x8f8 -N 16 -tx8 -Ax a.out
0008f8 000000000040072c 0000000000400746
000908

何かのアドレスらしき値が格納されていることが分かる.ここで,再度objdumpの逆アセンブル結果を見てみよう.

000000000040072c <Base::hoge()>:
〜

0000000000400746 <Base::fuga()>:
〜

0000000000400792 <Derived::fuga()>:
〜

関数アドレスがodのダンプ結果と一致することが分かる.以上より,Base::Base()で格納した0x4008f8という値を元に,*0x4008f8にBase::hoge(),*(0x4008f8+0x8)にBase::fuga()のアドレスが格納されていることが分かった.また,Derived::Derived()で格納した0x4008d8という値を元に,*0x4008d8の位置にBase::hoge(),*(0x4008d8+0x8)ではオーバーライドしたDerived::fuga()のアドレスが格納されていることが分かった.

以上の機構により,C++ではポリモーフィズムを実現している.そして.rodataに存在する関数のアドレスのリストをvtableと呼んでいる.
.rodataにvtableを設置し各オブジェクトで二回デリファレンスするより,各オブジェクトの先頭に直接関数のアドレスのリストを格納すればデリファレンスの回数が減ってよいのでは?と思ったが,おそらくオブジェクトの数が多くなった場合にプログラムが無駄に肥大化するためこのような形になっているのだと思う.ちなみに,コンパイラの最適化レベルを上げるとまた異なった結果が得られるため,その辺りは注意されたい.

以下,参考サイト等.
以下のサイトではC++擬似コードで説明してくれているのでより分かりやすい.
qiita.com
stackoverflow.com

また,ヒープオーバーフローを用いてvtableを上書きする攻撃も存在するらしい.
inaz2.hatenablog.com

プログラミング言語が違うと実行バイナリも違うのか検証した話

こんな話が流れてきたので検証してみる.

とりあえず実行バイナリを直接吐き出す言語として,C,C++,Go,Haskellを試してみる.チョイスの理由は単に今の環境に入っていたことと,ネイティブ実行できるバイナリを吐き出す言語をこれくらいしか知らなかったため.
実行環境はGentoo Linux(amd64),C・C++コンパイラgcc 5.4.0-r3,Goコンパイラgc 1.9.1,Haskellコンパイラghc 8.0.2を用いる.

まずはコードから.
C言語

#include <stdio.h>

int main()
{
	printf("Hello, World!\n");
}

C++

#include <cstdio>

int main()
{
	printf("Hello, World!\n");
	return 0;
}

Go(wikipediaから拝借)

package main

import "fmt"

func main() {
	fmt.Print("Hello, World!\n")
}

Haskell

main = putStrLn "Hello, World!"

コンパイルしていく.

% gcc -o hello_c hello.c
% g++ -o hello_cpp hello.cpp
% go build -o hello_go hello.go
% ghc -o hello_hs hello.hs

コンパイルして出来たバイナリのファイルサイズをまず確認してみる.

% ls -lh
total 3.0M
-rwxr-xr-x 1 vicco vicco 7.8K Dec  2 22:21 hello_c*
-rw-r--r-- 1 vicco vicco   63 Dec  2 01:01 hello.c
-rwxr-xr-x 1 vicco vicco 7.8K Dec  2 22:21 hello_cpp*
-rw-r--r-- 1 vicco vicco   73 Dec  2 00:56 hello.cpp
-rwxr-xr-x 1 vicco vicco 1.8M Dec  2 22:21 hello_go*
-rw-r--r-- 1 vicco vicco   74 Dec  2 22:16 hello.go
-rw-r--r-- 1 vicco vicco  753 Dec  2 22:22 hello.hi
-rwxr-xr-x 1 vicco vicco 1.2M Dec  2 22:22 hello_hs*
-rw-r--r-- 1 vicco vicco   32 Dec  2 22:19 hello.hs
-rw-r--r-- 1 vicco vicco 3.5K Dec  2 22:22 hello.o

hello_cがC,hello_cppがC++,hello_goがGo,hello_hsがHaskellの出力ファイルである.CとC++のファイルサイズが7.8KBと同じであり,対してHaskellは1.2MB,Goは1.8MBと割とでかい.
次にreadelfコマンドでELFヘッダを見てみる.ELFヘッダとは実行バイナリに何が含まれているかを記述するためにファイルの先頭に置かれている64バイトのヘッダである(詳しくはman elfを読もう).大体の部分はどのファイルも同じなので,プログラムヘッダの数とセクションヘッダの数だけ見てみよう.

% for hello in hello_c hello_cpp hello_go hello_hs; do echo $hello; readelf -h $hello|grep Number; done
hello_c
  Number of program headers:         9
  Number of section headers:         29
hello_cpp
  Number of program headers:         9
  Number of section headers:         29
hello_go
  Number of program headers:         7
  Number of section headers:         23
hello_hs
  Number of program headers:         9
  Number of section headers:         31

CとC++のプログラムヘッダおよびセクションヘッダの数は一致している.一方,GoはプログラムヘッダもセクションヘッダもC(C++)よりも少ない.Haskellはセクションヘッダの数がC(C++)より多い.これはC(C++)とGoおよびHaskellではバイナリのフォーマットが異なることを意味する.

次にlddコマンドを用いて依存している共有ライブラリを見てみる.

% for hello in hello_c hello_cpp hello_go hello_hs; do echo $hello; ldd $hello; done
hello_c
	linux-vdso.so.1 (0x00007fff2d5f2000)
	libc.so.6 => /lib64/libc.so.6 (0x00007f8e3b208000)
	/lib64/ld-linux-x86-64.so.2 (0x00007f8e3b5b9000)
hello_cpp
	linux-vdso.so.1 (0x00007ffee2316000)
	libstdc++.so.6 => /usr/lib/gcc/x86_64-pc-linux-gnu/5.4.0/libstdc++.so.6 (0x00007fd4f1253000)
	libm.so.6 => /lib64/libm.so.6 (0x00007fd4f0f43000)
	libgcc_s.so.1 => /usr/lib/gcc/x86_64-pc-linux-gnu/5.4.0/libgcc_s.so.1 (0x00007fd4f0d2c000)
	libc.so.6 => /lib64/libc.so.6 (0x00007fd4f097b000)
	/lib64/ld-linux-x86-64.so.2 (0x00007fd4f164c000)
hello_go
	not a dynamic executable
hello_hs
	linux-vdso.so.1 (0x00007fffe59a9000)
	libgmp.so.10 => /usr/lib64/libgmp.so.10 (0x00007f17177fb000)
	libm.so.6 => /lib64/libm.so.6 (0x00007f17174eb000)
	librt.so.1 => /lib64/librt.so.1 (0x00007f17172e3000)
	libdl.so.2 => /lib64/libdl.so.2 (0x00007f17170df000)
	libffi.so.6 => /usr/lib64/libffi.so.6 (0x00007f1716ed6000)
	libpthread.so.0 => /lib64/libpthread.so.0 (0x00007f1716cb6000)
	libc.so.6 => /lib64/libc.so.6 (0x00007f1716905000)
	/lib64/ld-linux-x86-64.so.2 (0x00007f1717a73000)

CとC++はファイルサイズやELFヘッダは同じだったが,C++の方が使用する共有ライブラリの数が多い.使用する共有ライブラリの情報は.dynamicセクションに格納されており,実行時に.interpセクションに格納された動的リンカ(ローダ?)がリンクしてくれる.
7shi.hateblo.jp
qiita.com

注目すべきはGoであり,なんと一切共有ライブラリを必要としていない.つまり実行に必要なライブラリは全て静的リンクされている.実はGoはツールチェインを自前で実装しているらしく,このためlibc相当の機能(printfやscanfのような関数)も自前で賄っている.
本の虫: goのgcコンパイラーがC実装を除去

では,コンパイラの一番の仕事だと思われる,高級言語から実際にどのようなマシン語を吐き出しているのかを見ていきたい.まずは.textセクションのサイズを見ていこう.

% for hello in hello_c hello_cpp hello_go hello_hs; do echo $hello; readelf -S $hello|grep .text -A1; done # 一部編集
Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
hello_c
  [12] .text             PROGBITS         00000000004003e0  000003e0
       0000000000000171  0000000000000000  AX       0     0     16
hello_cpp
  [12] .text             PROGBITS         00000000004004a0  000004a0
       0000000000000171  0000000000000000  AX       0     0     16
hello_go
  [ 1] .text             PROGBITS         0000000000401000  00001000
       0000000000086b5b  0000000000000000  AX       0     0     16
hello_hs
  [12] .text             PROGBITS         0000000000402cf0  00002cf0
       000000000008e2d9  0000000000000000  AX       0     0     16

C,C++が0x171(=369)バイトであるのに対し,Goは0x86b5b(=551771)バイト,0x8e2d9(=582361)バイトとかなり大きくなっている.
Go,Haskellは流石にでかすぎて読めないのでCとC++を逆アセンブルしていく.CとC++で先頭のアドレス部分が全て異なっていたので,awkでざっくりと取り除いてdiffコマンドで差を見ていく.

% objdump -d hello_c|awk '$1 ~ /^[0-9a-f]+:/{$1=""; print; next}; //{print}' > c.dump
% objdump -d hello_cpp|awk '$1 ~ /^[0-9a-f]+:/{$1=""; print; next}; //{print}' > cpp.dump
% diff -u c.dump cpp.dump
--- c.dump	2017-12-03 00:09:39.872448835 +0900
+++ cpp.dump	2017-12-03 00:09:45.972448843 +0900
@@ -1,33 +1,33 @@

-hello_c:     file format elf64-x86-64
+hello_cpp:     file format elf64-x86-64


 Disassembly of section .init:

-00000000004003a8 <_init>:
+0000000000400468 <_init>:
  48 83 ec 08 sub $0x8,%rsp
- 48 8b 05 45 0c 20 00 mov 0x200c45(%rip),%rax # 600ff8 <_DYNAMIC+0x1d8>
+ 48 8b 05 7d 0b 20 00 mov 0x200b7d(%rip),%rax # 600ff0 <_DYNAMIC+0x200>
  48 85 c0 test %rax,%rax
- 74 02 je 4003ba <_init+0x12>
+ 74 02 je 40047a <_init+0x12>
  ff d0 callq *%rax
  48 83 c4 08 add $0x8,%rsp
  c3 retq

 Disassembly of section .plt:

-00000000004003c0 <puts@plt-0x10>:
- ff 35 42 0c 20 00 pushq 0x200c42(%rip) # 601008 <_GLOBAL_OFFSET_TABLE_+0x8>
- ff 25 44 0c 20 00 jmpq *0x200c44(%rip) # 601010 <_GLOBAL_OFFSET_TABLE_+0x10>
+0000000000400480 <puts@plt-0x10>:
+ ff 35 82 0b 20 00 pushq 0x200b82(%rip) # 601008 <_GLOBAL_OFFSET_TABLE_+0x8>
+ ff 25 84 0b 20 00 jmpq *0x200b84(%rip) # 601010 <_GLOBAL_OFFSET_TABLE_+0x10>
  0f 1f 40 00 nopl 0x0(%rax)

-00000000004003d0 <puts@plt>:
- ff 25 42 0c 20 00 jmpq *0x200c42(%rip) # 601018 <_GLOBAL_OFFSET_TABLE_+0x18>
+0000000000400490 <puts@plt>:
+ ff 25 82 0b 20 00 jmpq *0x200b82(%rip) # 601018 <_GLOBAL_OFFSET_TABLE_+0x18>
  68 00 00 00 00 pushq $0x0
- e9 e0 ff ff ff jmpq 4003c0 <_init+0x18>
+ e9 e0 ff ff ff jmpq 400480 <_init+0x18>

 Disassembly of section .text:

-00000000004003e0 <_start>:
+00000000004004a0 <_start>:
  31 ed xor %ebp,%ebp
  49 89 d1 mov %rdx,%r9
  5e pop %rsi
@@ -35,10 +35,10 @@
  48 83 e4 f0 and $0xfffffffffffffff0,%rsp
  50 push %rax
  54 push %rsp
- 49 c7 c0 50 05 40 00 mov $0x400550,%r8
- 48 c7 c1 f0 04 40 00 mov $0x4004f0,%rcx
- 48 c7 c7 d6 04 40 00 mov $0x4004d6,%rdi
- ff 15 e6 0b 20 00 callq *0x200be6(%rip) # 600ff0 <_DYNAMIC+0x1d0>
+ 49 c7 c0 10 06 40 00 mov $0x400610,%r8
+ 48 c7 c1 b0 05 40 00 mov $0x4005b0,%rcx
+ 48 c7 c7 96 05 40 00 mov $0x400596,%rdi
+ ff 15 2e 0b 20 00 callq *0x200b2e(%rip) # 600ff8 <_DYNAMIC+0x208>
  f4 hlt
  0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)
  b8 37 10 60 00 mov $0x601037,%eax
@@ -46,10 +46,10 @@
  48 2d 30 10 60 00 sub $0x601030,%rax
  48 89 e5 mov %rsp,%rbp
  48 83 f8 0e cmp $0xe,%rax
- 76 1b jbe 400440 <_start+0x60>
+ 76 1b jbe 400500 <_start+0x60>
  b8 00 00 00 00 mov $0x0,%eax
  48 85 c0 test %rax,%rax
- 74 11 je 400440 <_start+0x60>
+ 74 11 je 400500 <_start+0x60>
  bf 30 10 60 00 mov $0x601030,%edi
  5d pop %rbp
  ff e0 jmpq *%rax
@@ -69,10 +69,10 @@
  48 c1 e8 3f shr $0x3f,%rax
  48 01 c6 add %rax,%rsi
  48 d1 fe sar %rsi
- 74 15 je 400488 <_start+0xa8>
+ 74 15 je 400548 <_start+0xa8>
  b8 00 00 00 00 mov $0x0,%eax
  48 85 c0 test %rax,%rax
- 74 0b je 400488 <_start+0xa8>
+ 74 0b je 400548 <_start+0xa8>
  bf 30 10 60 00 mov $0x601030,%edi
  5d pop %rbp
  ff e0 jmpq *%rax
@@ -80,40 +80,40 @@
  5d pop %rbp
  c3 retq
  66 0f 1f 44 00 00 nopw 0x0(%rax,%rax,1)
- 80 3d 99 0b 20 00 00 cmpb $0x0,0x200b99(%rip) # 601030 <__TMC_END__>
- 75 11 jne 4004aa <_start+0xca>
+ 80 3d d9 0a 20 00 00 cmpb $0x0,0x200ad9(%rip) # 601030 <__TMC_END__>
+ 75 11 jne 40056a <_start+0xca>
  55 push %rbp
  48 89 e5 mov %rsp,%rbp
- e8 6e ff ff ff callq 400410 <_start+0x30>
+ e8 6e ff ff ff callq 4004d0 <_start+0x30>
  5d pop %rbp
- c6 05 86 0b 20 00 01 movb $0x1,0x200b86(%rip) # 601030 <__TMC_END__>
+ c6 05 c6 0a 20 00 01 movb $0x1,0x200ac6(%rip) # 601030 <__TMC_END__>
  c3 retq
  0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)
- bf 18 0e 60 00 mov $0x600e18,%edi
+ bf e8 0d 60 00 mov $0x600de8,%edi
  48 83 3f 00 cmpq $0x0,(%rdi)
- 75 05 jne 4004c0 <_start+0xe0>
- eb 93 jmp 400450 <_start+0x70>
+ 75 05 jne 400580 <_start+0xe0>
+ eb 93 jmp 400510 <_start+0x70>
  0f 1f 00 nopl (%rax)
  b8 00 00 00 00 mov $0x0,%eax
  48 85 c0 test %rax,%rax
- 74 f1 je 4004bb <_start+0xdb>
+ 74 f1 je 40057b <_start+0xdb>
  55 push %rbp
  48 89 e5 mov %rsp,%rbp
  ff d0 callq *%rax
  5d pop %rbp
- e9 7a ff ff ff jmpq 400450 <_start+0x70>
+ e9 7a ff ff ff jmpq 400510 <_start+0x70>

-00000000004004d6 <main>:
+0000000000400596 <main>:
  55 push %rbp
  48 89 e5 mov %rsp,%rbp
- bf 64 05 40 00 mov $0x400564,%edi
- e8 ec fe ff ff callq 4003d0 <puts@plt>
+ bf 24 06 40 00 mov $0x400624,%edi
+ e8 ec fe ff ff callq 400490 <puts@plt>
  b8 00 00 00 00 mov $0x0,%eax
  5d pop %rbp
  c3 retq
  0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)

-00000000004004f0 <__libc_csu_init>:
+00000000004005b0 <__libc_csu_init>:
  41 57 push %r15
  41 89 ff mov %edi,%r15d
  41 56 push %r14
@@ -121,15 +121,15 @@
  41 55 push %r13
  49 89 d5 mov %rdx,%r13
  41 54 push %r12
- 4c 8d 25 00 09 20 00 lea 0x200900(%rip),%r12 # 600e08 <__init_array_start>
+ 4c 8d 25 10 08 20 00 lea 0x200810(%rip),%r12 # 600dd8 <__init_array_start>
  55 push %rbp
- 48 8d 2d 00 09 20 00 lea 0x200900(%rip),%rbp # 600e10 <__init_array_end>
+ 48 8d 2d 10 08 20 00 lea 0x200810(%rip),%rbp # 600de0 <__init_array_end>
  53 push %rbx
  4c 29 e5 sub %r12,%rbp
  48 83 ec 08 sub $0x8,%rsp
- e8 8b fe ff ff callq 4003a8 <_init>
+ e8 8b fe ff ff callq 400468 <_init>
  48 c1 fd 03 sar $0x3,%rbp
- 74 1b je 40053e <__libc_csu_init+0x4e>
+ 74 1b je 4005fe <__libc_csu_init+0x4e>
  31 db xor %ebx,%ebx
  0f 1f 00 nopl (%rax)
  4c 89 ea mov %r13,%rdx
@@ -138,7 +138,7 @@
  41 ff 14 dc callq *(%r12,%rbx,8)
  48 83 c3 01 add $0x1,%rbx
  48 39 eb cmp %rbp,%rbx
- 75 ea jne 400528 <__libc_csu_init+0x38>
+ 75 ea jne 4005e8 <__libc_csu_init+0x38>
  48 83 c4 08 add $0x8,%rsp
  5b pop %rbx
  5d pop %rbp
@@ -149,12 +149,12 @@
  c3 retq
  0f 1f 00 nopl (%rax)

-0000000000400550 <__libc_csu_fini>:
+0000000000400610 <__libc_csu_fini>:
  c3 retq

 Disassembly of section .fini:

-0000000000400554 <_fini>:
+0000000000400614 <_fini>:
  48 83 ec 08 sub $0x8,%rsp
  48 83 c4 08 add $0x8,%rsp
  c3 retq

printfはコンパイラによる最適化の結果putsに置き換わっている.見比べてみると概ねjmpやcall先のアドレスが異なる程度で,後は全く同じであることが分かる.つまり,CとC++ではHello, World!程度なら同じマシン語で動くことが分かった.

以上から,同じHello, World!を出力するプログラムであっても実行バイナリは異なることが分かった,特にCやC++のような速度や最適化が売りの言語等と比較すると,GoやHaskellだとバイナリのサイズが200倍近く大きかったりする.また,実行バイナリにはマシン語だけが記述されているわけではないので,同じマシン語のプログラムであっても違う共有ライブラリに依存していたりすると実行バイナリの中身は違ってくる.今回は29個あったセクションのうち半分も比較していないので,興味がある人は他のセクションも比較してみたり,中に何が入っているのかを調べてみてほしい.バイナリの触り方を学びたい場合はBINARY HACKSがおすすめです.
Binary Hacks ―ハッカー秘伝のテクニック100選


ということで,CとC++のHello, World!は大体同じということにして終わりたいが,このままだと「printf?なんでstd::coutを使わないんだ!」って言われそうなので,ざっくりと検証してみる.以下余談なのでオチはない.
コードは以下のような感じ.

#include <iostream>

int main()
{
	std::cout << "Hello, World!" << std::endl;
	return 0;
}
% gcc -o hello2_cpp hello2.cpp
% ls -lh hello{,2}_cpp
-rwxr-xr-x 1 vicco vicco 8.4K Dec  3 00:13 hello2_cpp*
-rwxr-xr-x 1 vicco vicco 7.8K Dec  2 22:21 hello_cpp*

ファイルサイズが違う.

% for hello in hello_cpp hello2_cpp; do echo $hello; readelf -h $hello|grep Number; done
hello_cpp
  Number of program headers:         9
  Number of section headers:         29
hello2_cpp
  Number of program headers:         9
  Number of section headers:         29

ヘッダの数は同じ.

% for hello in hello_cpp hello2_cpp; do echo $hello; ldd $hello; done
hello_cpp
	linux-vdso.so.1 (0x00007ffcc98fc000)
	libstdc++.so.6 => /usr/lib/gcc/x86_64-pc-linux-gnu/5.4.0/libstdc++.so.6 (0x00007fa14b001000)
	libm.so.6 => /lib64/libm.so.6 (0x00007fa14acf1000)
	libgcc_s.so.1 => /usr/lib/gcc/x86_64-pc-linux-gnu/5.4.0/libgcc_s.so.1 (0x00007fa14aada000)
	libc.so.6 => /lib64/libc.so.6 (0x00007fa14a729000)
	/lib64/ld-linux-x86-64.so.2 (0x00007fa14b3fa000)
hello2_cpp
	linux-vdso.so.1 (0x00007fff0d59e000)
	libstdc++.so.6 => /usr/lib/gcc/x86_64-pc-linux-gnu/5.4.0/libstdc++.so.6 (0x00007f96e4996000)
	libm.so.6 => /lib64/libm.so.6 (0x00007f96e4686000)
	libgcc_s.so.1 => /usr/lib/gcc/x86_64-pc-linux-gnu/5.4.0/libgcc_s.so.1 (0x00007f96e446f000)
	libc.so.6 => /lib64/libc.so.6 (0x00007f96e40be000)
	/lib64/ld-linux-x86-64.so.2 (0x00007f96e4d8f000)

共有ライブラリは同じ(意外だった).

% for hello in hello_cpp hello2_cpp; do echo $hello; readelf -S $hello|grep .text -A1; done
hello_cpp #一部編集
Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [12] .text             PROGBITS         00000000004004a0  000004a0
       0000000000000171  0000000000000000  AX       0     0     16
hello2_cpp
  [12] .text             PROGBITS         0000000000400720  00000720
       00000000000001d1  0000000000000000  AX       0     0     16

.textセクションのサイズは違う.

% objdump -d hello2_cpp |awk '$1 ~ /^[0-9a-f]+:/{$1=""; print; next}; //{print}' > cpp2.dump
% diff -u cpp.dump cpp2.dump
--- cpp.dump	2017-12-03 00:09:45.972448843 +0900
+++ cpp2.dump	2017-12-03 00:25:56.002450091 +0900
@@ -1,33 +1,58 @@

-hello_cpp:     file format elf64-x86-64
+hello2_cpp:     file format elf64-x86-64


 Disassembly of section .init:

-0000000000400468 <_init>:
+0000000000400698 <_init>:
  48 83 ec 08 sub $0x8,%rsp
- 48 8b 05 7d 0b 20 00 mov 0x200b7d(%rip),%rax # 600ff0 <_DYNAMIC+0x200>
+ 48 8b 05 4d 09 20 00 mov 0x20094d(%rip),%rax # 600ff0 <_DYNAMIC+0x200>
  48 85 c0 test %rax,%rax
- 74 02 je 40047a <_init+0x12>
+ 74 02 je 4006aa <_init+0x12>
  ff d0 callq *%rax
  48 83 c4 08 add $0x8,%rsp
  c3 retq

 Disassembly of section .plt:

-0000000000400480 <puts@plt-0x10>:
- ff 35 82 0b 20 00 pushq 0x200b82(%rip) # 601008 <_GLOBAL_OFFSET_TABLE_+0x8>
- ff 25 84 0b 20 00 jmpq *0x200b84(%rip) # 601010 <_GLOBAL_OFFSET_TABLE_+0x10>
+00000000004006b0 <_ZNSt8ios_base4InitC1Ev@plt-0x10>:
+ ff 35 52 09 20 00 pushq 0x200952(%rip) # 601008 <_GLOBAL_OFFSET_TABLE_+0x8>
+ ff 25 54 09 20 00 jmpq *0x200954(%rip) # 601010 <_GLOBAL_OFFSET_TABLE_+0x10>
  0f 1f 40 00 nopl 0x0(%rax)

-0000000000400490 <puts@plt>:
- ff 25 82 0b 20 00 jmpq *0x200b82(%rip) # 601018 <_GLOBAL_OFFSET_TABLE_+0x18>
+00000000004006c0 <_ZNSt8ios_base4InitC1Ev@plt>:
+ ff 25 52 09 20 00 jmpq *0x200952(%rip) # 601018 <_GLOBAL_OFFSET_TABLE_+0x18>
  68 00 00 00 00 pushq $0x0
- e9 e0 ff ff ff jmpq 400480 <_init+0x18>
+ e9 e0 ff ff ff jmpq 4006b0 <_init+0x18>
+
+00000000004006d0 <__cxa_atexit@plt>:
+ ff 25 4a 09 20 00 jmpq *0x20094a(%rip) # 601020 <_GLOBAL_OFFSET_TABLE_+0x20>
+ 68 01 00 00 00 pushq $0x1
+ e9 d0 ff ff ff jmpq 4006b0 <_init+0x18>
+
+00000000004006e0 <_ZNSt8ios_base4InitD1Ev@plt>:
+ ff 25 42 09 20 00 jmpq *0x200942(%rip) # 601028 <_GLOBAL_OFFSET_TABLE_+0x28>
+ 68 02 00 00 00 pushq $0x2
+ e9 c0 ff ff ff jmpq 4006b0 <_init+0x18>
+
+00000000004006f0 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@plt>:
+ ff 25 3a 09 20 00 jmpq *0x20093a(%rip) # 601030 <_GLOBAL_OFFSET_TABLE_+0x30>
+ 68 03 00 00 00 pushq $0x3
+ e9 b0 ff ff ff jmpq 4006b0 <_init+0x18>
+
+0000000000400700 <_ZNSolsEPFRSoS_E@plt>:
+ ff 25 32 09 20 00 jmpq *0x200932(%rip) # 601038 <_GLOBAL_OFFSET_TABLE_+0x38>
+ 68 04 00 00 00 pushq $0x4
+ e9 a0 ff ff ff jmpq 4006b0 <_init+0x18>
+
+0000000000400710 <_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_@plt>:
+ ff 25 2a 09 20 00 jmpq *0x20092a(%rip) # 601040 <_GLOBAL_OFFSET_TABLE_+0x40>
+ 68 05 00 00 00 pushq $0x5
+ e9 90 ff ff ff jmpq 4006b0 <_init+0x18>

 Disassembly of section .text:

-00000000004004a0 <_start>:
+0000000000400720 <_start>:
  31 ed xor %ebp,%ebp
  49 89 d1 mov %rdx,%r9
  5e pop %rsi
@@ -35,22 +60,22 @@
  48 83 e4 f0 and $0xfffffffffffffff0,%rsp
  50 push %rax
  54 push %rsp
- 49 c7 c0 10 06 40 00 mov $0x400610,%r8
- 48 c7 c1 b0 05 40 00 mov $0x4005b0,%rcx
- 48 c7 c7 96 05 40 00 mov $0x400596,%rdi
- ff 15 2e 0b 20 00 callq *0x200b2e(%rip) # 600ff8 <_DYNAMIC+0x208>
+ 49 c7 c0 f0 08 40 00 mov $0x4008f0,%r8
+ 48 c7 c1 90 08 40 00 mov $0x400890,%rcx
+ 48 c7 c7 16 08 40 00 mov $0x400816,%rdi
+ ff 15 ae 08 20 00 callq *0x2008ae(%rip) # 600ff8 <_DYNAMIC+0x208>
  f4 hlt
  0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)
- b8 37 10 60 00 mov $0x601037,%eax
+ b8 5f 10 60 00 mov $0x60105f,%eax
  55 push %rbp
- 48 2d 30 10 60 00 sub $0x601030,%rax
+ 48 2d 58 10 60 00 sub $0x601058,%rax
  48 89 e5 mov %rsp,%rbp
  48 83 f8 0e cmp $0xe,%rax
- 76 1b jbe 400500 <_start+0x60>
+ 76 1b jbe 400780 <_start+0x60>
  b8 00 00 00 00 mov $0x0,%eax
  48 85 c0 test %rax,%rax
- 74 11 je 400500 <_start+0x60>
- bf 30 10 60 00 mov $0x601030,%edi
+ 74 11 je 400780 <_start+0x60>
+ bf 58 10 60 00 mov $0x601058,%edi
  5d pop %rbp
  ff e0 jmpq *%rax
  66 0f 1f 84 00 00 00 nopw 0x0(%rax,%rax,1)
@@ -60,60 +85,92 @@
  0f 1f 40 00 nopl 0x0(%rax)
  66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1)
  00 00 00
- be 30 10 60 00 mov $0x601030,%esi
+ be 58 10 60 00 mov $0x601058,%esi
  55 push %rbp
- 48 81 ee 30 10 60 00 sub $0x601030,%rsi
+ 48 81 ee 58 10 60 00 sub $0x601058,%rsi
  48 89 e5 mov %rsp,%rbp
  48 c1 fe 03 sar $0x3,%rsi
  48 89 f0 mov %rsi,%rax
  48 c1 e8 3f shr $0x3f,%rax
  48 01 c6 add %rax,%rsi
  48 d1 fe sar %rsi
- 74 15 je 400548 <_start+0xa8>
+ 74 15 je 4007c8 <_start+0xa8>
  b8 00 00 00 00 mov $0x0,%eax
  48 85 c0 test %rax,%rax
- 74 0b je 400548 <_start+0xa8>
- bf 30 10 60 00 mov $0x601030,%edi
+ 74 0b je 4007c8 <_start+0xa8>
+ bf 58 10 60 00 mov $0x601058,%edi
  5d pop %rbp
  ff e0 jmpq *%rax
  0f 1f 00 nopl (%rax)
  5d pop %rbp
  c3 retq
  66 0f 1f 44 00 00 nopw 0x0(%rax,%rax,1)
- 80 3d d9 0a 20 00 00 cmpb $0x0,0x200ad9(%rip) # 601030 <__TMC_END__>
- 75 11 jne 40056a <_start+0xca>
+ 80 3d 99 09 20 00 00 cmpb $0x0,0x200999(%rip) # 601170 <_ZSt4cout@@GLIBCXX_3.4+0x110>
+ 75 11 jne 4007ea <_start+0xca>
  55 push %rbp
  48 89 e5 mov %rsp,%rbp
- e8 6e ff ff ff callq 4004d0 <_start+0x30>
+ e8 6e ff ff ff callq 400750 <_start+0x30>
  5d pop %rbp
- c6 05 c6 0a 20 00 01 movb $0x1,0x200ac6(%rip) # 601030 <__TMC_END__>
+ c6 05 86 09 20 00 01 movb $0x1,0x200986(%rip) # 601170 <_ZSt4cout@@GLIBCXX_3.4+0x110>
  c3 retq
  0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)
  bf e8 0d 60 00 mov $0x600de8,%edi
  48 83 3f 00 cmpq $0x0,(%rdi)
- 75 05 jne 400580 <_start+0xe0>
- eb 93 jmp 400510 <_start+0x70>
+ 75 05 jne 400800 <_start+0xe0>
+ eb 93 jmp 400790 <_start+0x70>
  0f 1f 00 nopl (%rax)
  b8 00 00 00 00 mov $0x0,%eax
  48 85 c0 test %rax,%rax
- 74 f1 je 40057b <_start+0xdb>
+ 74 f1 je 4007fb <_start+0xdb>
  55 push %rbp
  48 89 e5 mov %rsp,%rbp
  ff d0 callq *%rax
  5d pop %rbp
- e9 7a ff ff ff jmpq 400510 <_start+0x70>
+ e9 7a ff ff ff jmpq 400790 <_start+0x70>

-0000000000400596 <main>:
+0000000000400816 <main>:
  55 push %rbp
  48 89 e5 mov %rsp,%rbp
- bf 24 06 40 00 mov $0x400624,%edi
- e8 ec fe ff ff callq 400490 <puts@plt>
+ be 04 09 40 00 mov $0x400904,%esi
+ bf 60 10 60 00 mov $0x601060,%edi
+ e8 c7 fe ff ff callq 4006f0 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@plt>
+ be 10 07 40 00 mov $0x400710,%esi
+ 48 89 c7 mov %rax,%rdi
+ e8 ca fe ff ff callq 400700 <_ZNSolsEPFRSoS_E@plt>
  b8 00 00 00 00 mov $0x0,%eax
  5d pop %rbp
  c3 retq
- 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)

-00000000004005b0 <__libc_csu_init>:
+000000000040083d <_Z41__static_initialization_and_destruction_0ii>:
+ 55 push %rbp
+ 48 89 e5 mov %rsp,%rbp
+ 48 83 ec 10 sub $0x10,%rsp
+ 89 7d fc mov %edi,-0x4(%rbp)
+ 89 75 f8 mov %esi,-0x8(%rbp)
+ 83 7d fc 01 cmpl $0x1,-0x4(%rbp)
+ 75 27 jne 400878 <_Z41__static_initialization_and_destruction_0ii+0x3b>
+ 81 7d f8 ff ff 00 00 cmpl $0xffff,-0x8(%rbp)
+ 75 1e jne 400878 <_Z41__static_initialization_and_destruction_0ii+0x3b>
+ bf 71 11 60 00 mov $0x601171,%edi
+ e8 5c fe ff ff callq 4006c0 <_ZNSt8ios_base4InitC1Ev@plt>
+ ba 50 10 60 00 mov $0x601050,%edx
+ be 71 11 60 00 mov $0x601171,%esi
+ bf e0 06 40 00 mov $0x4006e0,%edi
+ e8 58 fe ff ff callq 4006d0 <__cxa_atexit@plt>
+ 90 nop
+ c9 leaveq
+ c3 retq
+
+000000000040087b <_GLOBAL__sub_I_main>:
+ 55 push %rbp
+ 48 89 e5 mov %rsp,%rbp
+ be ff ff 00 00 mov $0xffff,%esi
+ bf 01 00 00 00 mov $0x1,%edi
+ e8 af ff ff ff callq 40083d <_Z41__static_initialization_and_destruction_0ii>
+ 5d pop %rbp
+ c3 retq
+
+0000000000400890 <__libc_csu_init>:
  41 57 push %r15
  41 89 ff mov %edi,%r15d
  41 56 push %r14
@@ -121,15 +178,15 @@
  41 55 push %r13
  49 89 d5 mov %rdx,%r13
  41 54 push %r12
- 4c 8d 25 10 08 20 00 lea 0x200810(%rip),%r12 # 600dd8 <__init_array_start>
+ 4c 8d 25 28 05 20 00 lea 0x200528(%rip),%r12 # 600dd0 <__init_array_start>
  55 push %rbp
- 48 8d 2d 10 08 20 00 lea 0x200810(%rip),%rbp # 600de0 <__init_array_end>
+ 48 8d 2d 30 05 20 00 lea 0x200530(%rip),%rbp # 600de0 <__init_array_end>
  53 push %rbx
  4c 29 e5 sub %r12,%rbp
  48 83 ec 08 sub $0x8,%rsp
- e8 8b fe ff ff callq 400468 <_init>
+ e8 db fd ff ff callq 400698 <_init>
  48 c1 fd 03 sar $0x3,%rbp
- 74 1b je 4005fe <__libc_csu_init+0x4e>
+ 74 1b je 4008de <__libc_csu_init+0x4e>
  31 db xor %ebx,%ebx
  0f 1f 00 nopl (%rax)
  4c 89 ea mov %r13,%rdx
@@ -138,7 +195,7 @@
  41 ff 14 dc callq *(%r12,%rbx,8)
  48 83 c3 01 add $0x1,%rbx
  48 39 eb cmp %rbp,%rbx
- 75 ea jne 4005e8 <__libc_csu_init+0x38>
+ 75 ea jne 4008c8 <__libc_csu_init+0x38>
  48 83 c4 08 add $0x8,%rsp
  5b pop %rbx
  5d pop %rbp
@@ -149,12 +206,12 @@
  c3 retq
  0f 1f 00 nopl (%rax)

-0000000000400610 <__libc_csu_fini>:
+00000000004008f0 <__libc_csu_fini>:
  c3 retq

 Disassembly of section .fini:

-0000000000400614 <_fini>:
+00000000004008f4 <_fini>:
  48 83 ec 08 sub $0x8,%rsp
  48 83 c4 08 add $0x8,%rsp
  c3 retq

diffを見ていくと,printfを使うhello_cppとstd::coutを使うhello2_cppではシンボル名が大きく異なることに気がつく.これはC++の名前マングリングによるものであり,例えばmain関数で呼んでいる_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_をc++filtコマンドでデマングルしてみると以下のような結果が得られる.

% c++filt
_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_
std::basic_ostream<char, std::char_traits<char> >& std::endl<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&)

シンボル名自体に名前空間やクラス,引数の情報が含まれていることが分かる.この辺は全く知らないのでマサカリが怖い.
シンボル名の違いの他,いくつかのコードが追加されている.大きな違いは.pltセクションにいくつかの関数が追加されていること,mainで呼んでいる関数がputsではないこと,__static_initialization_and_destruction_0と_GLOBAL__sub_I_mainという関数が追加されていることだろうか.__static_initialization_and_destruction_0はstd::ios_base::Init::Init()といった初期化処理を行っているが,これを呼び出している_GLOBAL__sub_I_mainはどこから呼び出しているのかが分からなかった.何か分かったらいつかまとめよう.
stackoverflow.com