情弱ログ

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

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

Linux Kernelのパケット送信を追う(ソケット作成編)

実績解除のためにsendto(2)のシステムコール発行からe1000ドライバの送信処理までを追いかけてみた.取り急ぎソケット作成までをまとめてみる.
なお,Linux Kernelは4.14.0-rc8を対象としている.

初めに断っておくとアホほど長い上に備忘録なのでほとんど解説はしていない.もし間違えていたらごめんなさい.

続きを読む

Hello, World!するアセンブリ

x86_64向けに動作するHello, World!を書いた.長いので続きからにする.

続きを読む

MacでLinux Kernelをcloneするといじってないファイルがmodifiedされていると言われる現象

閲覧用にMac上にLinux Kernelをcloneしたところ、いじってないファイルがmodifiedであると言われて何もできなくなる事態が発生した。
具体的には以下のようなエラーメッセージが出力される。

$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

	modified:   include/uapi/linux/netfilter/xt_CONNMARK.h
	modified:   include/uapi/linux/netfilter/xt_DSCP.h
	modified:   include/uapi/linux/netfilter/xt_MARK.h
	modified:   include/uapi/linux/netfilter/xt_RATEEST.h
	modified:   include/uapi/linux/netfilter/xt_TCPMSS.h
	modified:   include/uapi/linux/netfilter_ipv4/ipt_ECN.h
	modified:   include/uapi/linux/netfilter_ipv4/ipt_TTL.h
	modified:   include/uapi/linux/netfilter_ipv6/ip6t_HL.h
	modified:   net/netfilter/xt_DSCP.c
	modified:   net/netfilter/xt_HL.c
	modified:   net/netfilter/xt_RATEEST.c
	modified:   net/netfilter/xt_TCPMSS.c

検索したところ、以下のようなページが見つかった。
git - Linux Kernel sources modified on OSX right after clone - Stack Overflow

要するにHFS+ではファイル名の大文字と小文字の区別がつかないので、上のファイルは衝突してgitが混乱するというのが原因らしい。
実際に確認してみたところ、確かにinclude/uapi/linux/netfilter/xt_CONNMARK.hとinclude/uapi/linux/netfilter/xt_connmark.hというファイルが見つかった。

解決策は大文字と小文字が区別される専用のディスクイメージを作成し、その中でcloneするのが良いとのこと。ディスクユーティリティを開いて、ファイル→新規イメージ→空のイメージを作成から、フォーマットの欄で大文字/小文字を区別にすればcase-sensitiveなディスクイメージが作成できる。
結構回りくどいような…
回答者も言及しているが、Virtual Box等でVMを立ててLinux環境を作り、ssh越しに閲覧するのが早いと思う。emacs派ならTRAMPを使おう。

よく使うEmacs拡張

弊校では学部1年生からEmacsの使用が強制されており、習得度の低い学生が素のEmacsを使わされています。その結果、非常に残念なことにEmacsはただただ不便なだけのエディタとしてその名が知られています。そこで、弊校におけるEmacsの悪印象を払拭し、Emacsは便利なエディタであると布教するために僕がよく使っているEmacs拡張とその設定を挙げていきたいと思います。

続きを読む

ELF入門

C言語が書ける、アセンブラが吐き出すアセンブリコードが分かる、でもHello, worldするバイナリは何が書かれているか分からない…。というか、バイナリの実行って何?っていう疑問を解決したいバイナリ初心者のメモです。
今回はreadelfコマンドは知っていても、どんな意味がある出力なのかがいまいち分かっていなかったので、車輪の再開発で理解していこうという試みです。とりあえずreadelfに-h、-l、-Sオプションを指定した時と同じような出力ができるプログラムの作成を目指します。

続きを読む