Binary2.0 Conference 2006 発表資料

さとう ゆうすけ <ads01002 ＠ nifty.com>

自己紹介
さとう ゆうすけ (d:id:yupo5656)
 ソフトウェア エンジニア
 Hello World愛好家
 Binary Hacks の執筆に参加
 主な担当ハック:#25 「glibcを使わないでHello Worldを書く」

自己紹介 好きな休日の過ごし方
 コードを読む、書く 読書○ The Single UNIX Specification○ ISO/IEC 9899:1999 (C)○ ISO/IEC 14882:2003 (C++)

 SICPの問題をC++で解く
 Z80マイコン製作

 Hello World

自己紹介 + 本日の話のレベル
user-space バイナリアン
kernel-space バイナリアン

リアルバイナリアン

本日の内容 バイナリアンの分類
 Hello World愛好家の分類
 GCC拡張入門
 GCC拡張を使ったHello World ５連発

 まとめ目標：
いつのまにかGCC拡張機能をマスター

Hello world 愛好家の分類

GCC拡張すごいよ派
ELF Golf派

1. ELF Golf派
 “Hello, world!” を出力する、世界最小最軽量のELFバイナリを追求 深追い対象:
 ELFフォーマット
 ローダ（linux-2.6.xx/fs/binfmt_elf.c） 会話例:「アリアリで58Bですか。それはすごい」
 アリアリ: Hello, World!
 アリナシ: Hello, World ナシアリ: Hello World!
 ナシナシ: Hello World

58B

2. GCC拡張すごいよ派 （私） 本日のメインテーマ

 無駄に華麗なCのコードを追及
 GCC拡張機能を無意味に活用 一見意味のわからないコード
 実はHello world [これはすごい] [これは便利] [lifehack] 1024 users

 実はELF Golfについていけないだけ予習: GCC拡張入門__attribute__((constructor))void before_main(){puts("mainの前に呼ばれるよ");}

GCC拡張機能

int main(){ puts("mainだよ"); }__attribute__((destructor))void after_main(){puts("mainの後に呼ばれるよ");}constructor
destructor
sectioncleanupctor/dtor関数が呼ばれるしくみ（概要）_start()_libc_start_main()_do_global_ctors_aux()
コンストラクタ関数1()…
libcが呼んでくれる！
main()
exit()
mainからreturnで戻った場合は、_libc_start_main()がexit()を呼ぶ_do_global_dtors_aux()
デストラクタ関数1()…
_exitシステムコール

本日のハローワールド ５種類
二度呼ばれるmain（によるハローワールド）

蹂躙されるmain呼ばれるのに関数にしてもらえないmain

スルー力の高いmain
何もしないmain 二度呼ばれるmain 蹂躙されるmain 呼ばれるのに関数にしてもらえないmain スルー力の高いmain 何もしないmain（ネタ１/5） 二度呼ばれるmain__attribute__((constructor)) int main() {static int i = 0;
if (i) puts("world!");else i = printf("hello, ");} mainの前に好きな関数を呼べる じゃあ、mainの前にmainを呼んでみるのはどう？
d:id:shinichiro_h:20060809より、一部改変

デモ大切なお知らせ 以降のネタにつきましては、出力がいつも同じであるため、デモを省略させていただきますmain2度呼びのコールツリー_start()_libc_start_main()_do_global_ctors_aux()main()
main()
exit()
 mainを二度呼んでも全く問題なし！
 どう見ても単なる関数です。本当に (ry 次ネタ以降、偉そうなmainの地位をどんどん危うくしていきますよ（main蹂躙芸）

 二度呼ばれるmain 蹂躙されるmain 呼ばれるのに関数にしてもらえないmain スルー力の高いmain 何もしないmain（ネタ2/5）蹂躙されるmain__attribute__((constructor, destructor))void x(){static int i = 0;
if (i) puts("world!");else i = printf("hello, "), exit(0);} mainを（二度呼ぶどころか）一度も呼ばないハローワールド
 constructor関数内でexitコールツリー_start()_libc_start_main()_do_global_ctors_aux()x() ←コンストラクタ
exit()_do_global_dtors_aux()x() ←デストラクタ_exitシステムコール

リンク失敗..
 (.text+0x18): undefined reference to `main„ main関数がないと実行ファイルが作成できない
 どうせ呼ばれないのに…

[Q] カラのmain関数を書けばよいのでは？
[A] だが断る。

ソリューション: main変数
int main = 0;__attribute__((constructor, destructor))void x(){
if (main) puts("world!");else main = printf("hello, "), exit(0);} リンカ的には関数も変数もただの「シンボル」

 「main変数」を書いとけばよい
 変数にしたからには働いてもらう！
 main様を、x()の動作を変えるためのフラグ扱い

コンパイル風景% gcc iyana.c
iyana.c:5:warning: ’ main ’ is usually a function% ./a.out
Hello, World!

知ってます 二度呼ばれるmain 蹂躙されるmain 呼ばれるのに関数にしてもらえないmain スルー力の高いmain 何もしないmain（ネタ3/5）呼ばれるのに関数にしてもらえないmain mainをフラグとしてのみ使うのは失礼。

存在を無視しすぎ。ゆとり教育の弊害。
 ちゃんとmainを呼んであげる呼ばれるのに関数にしてもらえないmain
main; // 変数（フラグ）兼 関数__attribute__((constructor, destructor))void x(){
if (main) puts("world!");else printf("hello, "), main = 195;}
 exitする代わりに、mainに謎の数値を代入
 195 = 0xC3 = x86のRET命令

 このmain変数は「RET命令だけの関数」と同じ
d:id:shinichiro_h:20060829より、一部改変

コールツリー_start()_libc_start_main()_do_global_ctors_aux()x() ←コンストラクタ, mainを195にmain変数() ← 一瞬でRET
exit()_do_global_dtors_aux()x() ←デストラクタ_exitシステムコール

実行% ./a.out
Hello, World!

微妙に使いみちがあったりする データ実行許可（NXなし）環境% ./a.out
hello, world!
 データ実行禁止（NXあり）環境% ./a.out
zsh: segmentation fault./a.out

 /proc/sys/kernel/exec-shield を見るまでもなくOSの設定状況がわかる!（見たほうが早いけど） 二度呼ばれるmain 蹂躙されるmain 呼ばれるのに関数にしてもらえないmain スルー力の高いmain 何もしないmain（ネタ4/5）スルー力の高いmain__attribute__((section(".text"))) main = 2425393296;_() {__attribute__((destructor)) _(){ puts("world!"); }printf("hello, ");}

 今回はちゃんと(?) main変数から実行開始
 constructor関数なし
 関数名が _ だけなのは、Haskellへの対抗心

 関数の中に関数を書けるのもGCC拡張
 今回はオシャレで使っただけ、深い意味はなし

見やすく整形// 同じ動作をするコード__attribute__((section(".text")))int main = 0x90909090;

void f() { printf("hello, "); }__attribute__((destructor))void g() { puts("world!"); } 0x90 = NOP命令

ELF実行ファイル.text/.plt
section.got/.ctors/.dtorssecion…
.data section.bss section

実行コード
（関数）

雑多な色々

グローバル変数

 2425393296 = 0x90909090 = NOP命令４つ main変数のELF上の位置を変更 （.data → .text）
 ELFバイナリ上、関数fのすぐ上にmainが配置される

新概念： 関数フォールスルー

NOP４つだけ• main変数• エントリポイント

Hello出力• 関数fWorld出力• 関数g• デストラクタ属性

実行% ./a.out
Hello, World!

逆アセンブルするとわかりやすい% objdump -D a.out -j .text
08048384 <main>:
8048384:908048385:908048386:90
8048387:
90

nop
nop
nop
nop

① main変数実行開始② 関数fにフォールスルー

隙間なし

08048388 <f>:8048388:8048389:804838b:804838e:8048395:804839a:804839b:55
89 e5
83 ec 08
c7 04 24 80 84 04 08e8 1e ff ff ff
c9
c3

push %ebp
mov%esp,%ebp
sub$0x8,%esp
movl $0x8048480,(%esp)
call 80482b8 <printf@plt> ③ Hello 出力
leave
ret ④ main変数からリターン

0804839c <g>:804839c:55

push%ebp 二度呼ばれるmain 蹂躙されるmain 呼ばれるのに関数にしてもらえないmain スルー力の高いmain 何もしないmain（ネタ5/5）何もしないmain 自動変数にcleanup属性付与
 自動変数がスコープから外れたとき
 指定した１引数関数を呼べる 関数には、その自動変数へのポインタが渡る 脊髄反射的にハローワールドに応用// 変数しかないように見えるが・・
int main() {__attribute__((cleanup(puts)))
const char hoge[] = "hello, world!";}

解説
ソースコードint main() {__attribute__((cleanup(puts)))
const char hoge[] = "hello, world!";
}

コンパイル

GCCが出力するコードのイメージ
int main() {const char hoge[] = "hello, world!";puts(&hoge); // GCCが自動で追加}

何もしないmain その２（エキスパート・ハローワールダ向け）
main;__attribute((constructor, destructor)) _() {if (main++) {__attribute((cleanup(puts))) char _[] = "world!";} else {__attribute((cleanup(exit)))int __;__attribute((cleanup(printf))) char _[] = "hello,";}} 積極exit型main蹂躙スタイル

 _() → printf() → exit() → _() → puts()

応用: C言語でRAIIイディオム
 cleanup属性を使うと、CでC++のRAIIイディオムの模倣が可能
 RAIIイディオムとは！
// C++
void bar() {scoped_lock lk(mutex);...
return; // ロック自動解放...
return; // ロック自動解放}普通のCだと手動解放、超面倒// C
void bar() {mutex_lock(mutex);...
mutex_unlock(mutex); // 手動解放return;...
mutex_unlock(mutex); // 手動解放
return;}

もの作るってレベルじゃねーぞ！

GCCならラクラク
 ほぼC++相当、RAIIもどき マクロの中身は d:id:yupo5656:20060609

 GCCのcleanup属性を使ってます// GCC
void bar() {SCOPED_LOCK(mutex);...
return; // ロック自動解放...
return; // ロック自動解放}

まとめ
GCCの拡張機能便利！

ご清聴ありがとうございました参考になる資料
1.

Binary Hacks （宣伝）2.

ELF: プログラマの視点から
http://www.globe.to/~oka326/archive/elf_doc_sgml_ja/elf_doc.html

3.

GCCのオンラインマニュアル Function Attributes
http://gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html Variable Attributes
http://gcc.gnu.org/onlinedocs/gcc/Variable-Attributes.html