情弱ログ

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

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

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

とりあえず実行バイナリを直接吐き出す言語として,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