glibcを利用しないコンパイル方法
はじめに
今回の検証を行うに至った動機としてリンカやローダといった、一般的にコンパイラとして一括りにされているソフトウェアについて勉強する過程でライブラリを利用せずにコンパイルを行うにはどうすればいいのか、という疑問が生まれました。
そこで、glibcを用いずにコンパイルを行ってみることにしました。
glibcを利用したコンパイル
ライブラリを用いないコンパイルを行う前に、比較を行えるようglibcを用いた一般的なコンパイルを行ったバイナリを生成しました。
そのソースコードは以下になります。
#include <stdio.h> int main() { printf("Hello World!\n"); return 0; }
このソースコードをコンパイルし実行すると以下のようになります。
> gcc -o hello hello.c > ./hello Hello World! >
特に問題なく動作しています。
次に、このバイナリのもつセクション情報と逆アセンブル、サイズを調査しました。
> objdump -h hello hello: file format elf64-x86-64 Sections: Idx Name Size VMA LMA File off Algn 0 .interp 0000001c 0000000000400238 0000000000400238 00000238 2**0 CONTENTS, ALLOC, LOAD, READONLY, DATA 1 .note.ABI-tag 00000020 0000000000400254 0000000000400254 00000254 2**2 CONTENTS, ALLOC, LOAD, READONLY, DATA 2 .note.gnu.build-id 00000024 0000000000400274 0000000000400274 00000274 2**2 CONTENTS, ALLOC, LOAD, READONLY, DATA 3 .gnu.hash 0000001c 0000000000400298 0000000000400298 00000298 2**3 CONTENTS, ALLOC, LOAD, READONLY, DATA 4 .dynsym 00000060 00000000004002b8 00000000004002b8 000002b8 2**3 CONTENTS, ALLOC, LOAD, READONLY, DATA 5 .dynstr 0000003d 0000000000400318 0000000000400318 00000318 2**0 CONTENTS, ALLOC, LOAD, READONLY, DATA 6 .gnu.version 00000008 0000000000400356 0000000000400356 00000356 2**1 CONTENTS, ALLOC, LOAD, READONLY, DATA 7 .gnu.version_r 00000020 0000000000400360 0000000000400360 00000360 2**3 CONTENTS, ALLOC, LOAD, READONLY, DATA 8 .rela.dyn 00000018 0000000000400380 0000000000400380 00000380 2**3 CONTENTS, ALLOC, LOAD, READONLY, DATA 9 .rela.plt 00000030 0000000000400398 0000000000400398 00000398 2**3 CONTENTS, ALLOC, LOAD, READONLY, DATA 10 .init 0000001a 00000000004003c8 00000000004003c8 000003c8 2**2 CONTENTS, ALLOC, LOAD, READONLY, CODE 11 .plt 00000030 00000000004003f0 00000000004003f0 000003f0 2**4 CONTENTS, ALLOC, LOAD, READONLY, CODE 12 .plt.got 00000008 0000000000400420 0000000000400420 00000420 2**3 CONTENTS, ALLOC, LOAD, READONLY, CODE 13 .text 00000182 0000000000400430 0000000000400430 00000430 2**4 CONTENTS, ALLOC, LOAD, READONLY, CODE 14 .fini 00000009 00000000004005b4 00000000004005b4 000005b4 2**2 CONTENTS, ALLOC, LOAD, READONLY, CODE 15 .rodata 00000011 00000000004005c0 00000000004005c0 000005c0 2**2 CONTENTS, ALLOC, LOAD, READONLY, DATA 16 .eh_frame_hdr 00000034 00000000004005d4 00000000004005d4 000005d4 2**2 CONTENTS, ALLOC, LOAD, READONLY, DATA 17 .eh_frame 000000f4 0000000000400608 0000000000400608 00000608 2**3 CONTENTS, ALLOC, LOAD, READONLY, DATA 18 .init_array 00000008 0000000000600e10 0000000000600e10 00000e10 2**3 CONTENTS, ALLOC, LOAD, DATA 19 .fini_array 00000008 0000000000600e18 0000000000600e18 00000e18 2**3 CONTENTS, ALLOC, LOAD, DATA 20 .jcr 00000008 0000000000600e20 0000000000600e20 00000e20 2**3 CONTENTS, ALLOC, LOAD, DATA 21 .dynamic 000001d0 0000000000600e28 0000000000600e28 00000e28 2**3 CONTENTS, ALLOC, LOAD, DATA 22 .got 00000008 0000000000600ff8 0000000000600ff8 00000ff8 2**3 CONTENTS, ALLOC, LOAD, DATA 23 .got.plt 00000028 0000000000601000 0000000000601000 00001000 2**3 CONTENTS, ALLOC, LOAD, DATA 24 .data 00000010 0000000000601028 0000000000601028 00001028 2**3 CONTENTS, ALLOC, LOAD, DATA 25 .bss 00000008 0000000000601038 0000000000601038 00001038 2**0 ALLOC 26 .comment 00000034 0000000000000000 0000000000000000 00001038 2**0 CONTENTS, READONLY > objdump -d hello hello: file format elf64-x86-64 Disassembly of section .init: 00000000004003c8 <_init>: 4003c8: 48 83 ec 08 sub $0x8,%rsp 4003cc: 48 8b 05 25 0c 20 00 mov 0x200c25(%rip),%rax # 600ff8 <_DYNAMIC+0x1d0> 4003d3: 48 85 c0 test %rax,%rax 4003d6: 74 05 je 4003dd <_init+0x15> 4003d8: e8 43 00 00 00 callq 400420 <__libc_start_main@plt+0x10> 4003dd: 48 83 c4 08 add $0x8,%rsp 4003e1: c3 retq Disassembly of section .plt: 00000000004003f0 <puts@plt-0x10>: 4003f0: ff 35 12 0c 20 00 pushq 0x200c12(%rip) # 601008 <_GLOBAL_OFFSET_TABLE_+0x8> 4003f6: ff 25 14 0c 20 00 jmpq *0x200c14(%rip) # 601010 <_GLOBAL_OFFSET_TABLE_+0x10> 4003fc: 0f 1f 40 00 nopl 0x0(%rax) 0000000000400400 <puts@plt>: 400400: ff 25 12 0c 20 00 jmpq *0x200c12(%rip) # 601018 <_GLOBAL_OFFSET_TABLE_+0x18> 400406: 68 00 00 00 00 pushq $0x0 40040b: e9 e0 ff ff ff jmpq 4003f0 <_init+0x28> 0000000000400410 <__libc_start_main@plt>: 400410: ff 25 0a 0c 20 00 jmpq *0x200c0a(%rip) # 601020 <_GLOBAL_OFFSET_TABLE_+0x20> 400416: 68 01 00 00 00 pushq $0x1 40041b: e9 d0 ff ff ff jmpq 4003f0 <_init+0x28> Disassembly of section .plt.got: 0000000000400420 <.plt.got>: 400420: ff 25 d2 0b 20 00 jmpq *0x200bd2(%rip) # 600ff8 <_DYNAMIC+0x1d0> 400426: 66 90 xchg %ax,%ax Disassembly of section .text: 0000000000400430 <_start>: 400430: 31 ed xor %ebp,%ebp 400432: 49 89 d1 mov %rdx,%r9 400435: 5e pop %rsi 400436: 48 89 e2 mov %rsp,%rdx 400439: 48 83 e4 f0 and $0xfffffffffffffff0,%rsp 40043d: 50 push %rax 40043e: 54 push %rsp 40043f: 49 c7 c0 b0 05 40 00 mov $0x4005b0,%r8 400446: 48 c7 c1 40 05 40 00 mov $0x400540,%rcx 40044d: 48 c7 c7 26 05 40 00 mov $0x400526,%rdi 400454: e8 b7 ff ff ff callq 400410 <__libc_start_main@plt> 400459: f4 hlt 40045a: 66 0f 1f 44 00 00 nopw 0x0(%rax,%rax,1) 0000000000400460 <deregister_tm_clones>: 400460: b8 3f 10 60 00 mov $0x60103f,%eax 400465: 55 push %rbp 400466: 48 2d 38 10 60 00 sub $0x601038,%rax 40046c: 48 83 f8 0e cmp $0xe,%rax 400470: 48 89 e5 mov %rsp,%rbp 400473: 76 1b jbe 400490 <deregister_tm_clones+0x30> 400475: b8 00 00 00 00 mov $0x0,%eax 40047a: 48 85 c0 test %rax,%rax 40047d: 74 11 je 400490 <deregister_tm_clones+0x30> 40047f: 5d pop %rbp 400480: bf 38 10 60 00 mov $0x601038,%edi 400485: ff e0 jmpq *%rax 400487: 66 0f 1f 84 00 00 00 nopw 0x0(%rax,%rax,1) 40048e: 00 00 400490: 5d pop %rbp 400491: c3 retq 400492: 0f 1f 40 00 nopl 0x0(%rax) 400496: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1) 40049d: 00 00 00 00000000004004a0 <register_tm_clones>: 4004a0: be 38 10 60 00 mov $0x601038,%esi 4004a5: 55 push %rbp 4004a6: 48 81 ee 38 10 60 00 sub $0x601038,%rsi 4004ad: 48 c1 fe 03 sar $0x3,%rsi 4004b1: 48 89 e5 mov %rsp,%rbp 4004b4: 48 89 f0 mov %rsi,%rax 4004b7: 48 c1 e8 3f shr $0x3f,%rax 4004bb: 48 01 c6 add %rax,%rsi 4004be: 48 d1 fe sar %rsi 4004c1: 74 15 je 4004d8 <register_tm_clones+0x38> 4004c3: b8 00 00 00 00 mov $0x0,%eax 4004c8: 48 85 c0 test %rax,%rax 4004cb: 74 0b je 4004d8 <register_tm_clones+0x38> 4004cd: 5d pop %rbp 4004ce: bf 38 10 60 00 mov $0x601038,%edi 4004d3: ff e0 jmpq *%rax 4004d5: 0f 1f 00 nopl (%rax) 4004d8: 5d pop %rbp 4004d9: c3 retq 4004da: 66 0f 1f 44 00 00 nopw 0x0(%rax,%rax,1) 00000000004004e0 <__do_global_dtors_aux>: 4004e0: 80 3d 51 0b 20 00 00 cmpb $0x0,0x200b51(%rip) # 601038 <__TMC_END__> 4004e7: 75 11 jne 4004fa <__do_global_dtors_aux+0x1a> 4004e9: 55 push %rbp 4004ea: 48 89 e5 mov %rsp,%rbp 4004ed: e8 6e ff ff ff callq 400460 <deregister_tm_clones> 4004f2: 5d pop %rbp 4004f3: c6 05 3e 0b 20 00 01 movb $0x1,0x200b3e(%rip) # 601038 <__TMC_END__> 4004fa: f3 c3 repz retq 4004fc: 0f 1f 40 00 nopl 0x0(%rax) 0000000000400500 <frame_dummy>: 400500: bf 20 0e 60 00 mov $0x600e20,%edi 400505: 48 83 3f 00 cmpq $0x0,(%rdi) 400509: 75 05 jne 400510 <frame_dummy+0x10> 40050b: eb 93 jmp 4004a0 <register_tm_clones> 40050d: 0f 1f 00 nopl (%rax) 400510: b8 00 00 00 00 mov $0x0,%eax 400515: 48 85 c0 test %rax,%rax 400518: 74 f1 je 40050b <frame_dummy+0xb> 40051a: 55 push %rbp 40051b: 48 89 e5 mov %rsp,%rbp 40051e: ff d0 callq *%rax 400520: 5d pop %rbp 400521: e9 7a ff ff ff jmpq 4004a0 <register_tm_clones> 0000000000400526 <main>: 400526: 55 push %rbp 400527: 48 89 e5 mov %rsp,%rbp 40052a: bf c4 05 40 00 mov $0x4005c4,%edi 40052f: e8 cc fe ff ff callq 400400 <puts@plt> 400534: b8 00 00 00 00 mov $0x0,%eax 400539: 5d pop %rbp 40053a: c3 retq 40053b: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1) 0000000000400540 <__libc_csu_init>: 400540: 41 57 push %r15 400542: 41 56 push %r14 400544: 41 89 ff mov %edi,%r15d 400547: 41 55 push %r13 400549: 41 54 push %r12 40054b: 4c 8d 25 be 08 20 00 lea 0x2008be(%rip),%r12 # 600e10 <__frame_dummy_init_array_entry> 400552: 55 push %rbp 400553: 48 8d 2d be 08 20 00 lea 0x2008be(%rip),%rbp # 600e18 <__init_array_end> 40055a: 53 push %rbx 40055b: 49 89 f6 mov %rsi,%r14 40055e: 49 89 d5 mov %rdx,%r13 400561: 4c 29 e5 sub %r12,%rbp 400564: 48 83 ec 08 sub $0x8,%rsp 400568: 48 c1 fd 03 sar $0x3,%rbp 40056c: e8 57 fe ff ff callq 4003c8 <_init> 400571: 48 85 ed test %rbp,%rbp 400574: 74 20 je 400596 <__libc_csu_init+0x56> 400576: 31 db xor %ebx,%ebx 400578: 0f 1f 84 00 00 00 00 nopl 0x0(%rax,%rax,1) 40057f: 00 400580: 4c 89 ea mov %r13,%rdx 400583: 4c 89 f6 mov %r14,%rsi 400586: 44 89 ff mov %r15d,%edi 400589: 41 ff 14 dc callq *(%r12,%rbx,8) 40058d: 48 83 c3 01 add $0x1,%rbx 400591: 48 39 eb cmp %rbp,%rbx 400594: 75 ea jne 400580 <__libc_csu_init+0x40> 400596: 48 83 c4 08 add $0x8,%rsp 40059a: 5b pop %rbx 40059b: 5d pop %rbp 40059c: 41 5c pop %r12 40059e: 41 5d pop %r13 4005a0: 41 5e pop %r14 4005a2: 41 5f pop %r15 4005a4: c3 retq 4005a5: 90 nop 4005a6: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1) 4005ad: 00 00 00 00000000004005b0 <__libc_csu_fini>: 4005b0: f3 c3 repz retq Disassembly of section .fini: 00000000004005b4 <_fini>: 4005b4: 48 83 ec 08 sub $0x8,%rsp 4005b8: 48 83 c4 08 add $0x8,%rsp 4005bc: c3 retq > size hello text data bss dec hex filename 1183 552 8 1743 6cf hello > ldd hello linux-vdso.so.1 => (0x00007fffe999f000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007ff8480c0000) /lib64/ld-linux-x86-64.so.2 (0x00007ff848600000) >
この時点ではこのようになっているのだな、程度の調査のみを行い次に行うglibcを利用せずに生成したバイナリを調査する際に比較を行います。
glibcを利用しないコンパイル
次に、glibcを利用せずにコンパイルを行います。まず初めに、コンパイルが正常にできるかを確認するために以下のソースコードを用います。
int main() { char *text = "Hello World!\n"; return 0; }
最初にコンパイルを行ったソースコードではライブラリを利用することができたので「stdio.h」というヘッダファイルをインクルードしていましたが、今回は利用しないので文字列を「text」という変数に初期値設定しています。
また、glibcを利用しない場合は「 -nostdlib 」というオプションを追加します。
ubnt@ubuntu:~$ gcc -nostdlib -o hello_no_libc hello.c /usr/bin/ld: 警告: エントリシンボル _start が見つかりません。デフォルトとして 0000000000400144 を使用します
どうやらそのままではコンパイルができず、エラー内容を読んでみると「_start」というエントリシンボルが存在していないようでした。
このエントリシンボルとはエントリポイントのことであり、このシンボルが存在していないため実行できないということでした。
ということで、エントリポイントを記述します。
.global _start _start: call main mov $60, %eax xor %rdi, %rdi syscall
これでコンパイルを行うために必要な材料はそろったので実際にコンパイルを行ってみます。
ubnt@ubuntu:~$ gcc -nostdlib stubstart.s -o hello_no_libc hello.c ubnt@ubuntu:~$ ./hello_no_libc ubnt@ubuntu:~$
問題なく動作することがわかりました。しかし、これでは比較を行えないのでインクルードファイルからprintf関数の基となるwriteシステムコールラッパを探そうと思ったのですが、システムコールに関する処理はあらかじめコンパイルされてアセンブラとなっているのではないか、という結論に至りインラインアセンブラを用いてwriteシステムコールを呼び出す処理を追加しました。以下はそのソースコードです。
#define WRITE_SYS_NUM 4 #define STDOUT_FILENO 1 int main() { char *str = "Hello World, I am chouett0.\nnice to meet you!!1!\n"; int len=0, err; while (str[len] != '\0') { len++; } asm volatile("mov %0, %%eax" : : "i"(WRITE_SYS_NUM)); asm volatile("mov %0, %%ebx" : : "i"(STDOUT_FILENO)); asm volatile("mov %0, %%ecx" : : "m"(str)); asm volatile("mov %0, %%edx" : : "m"(len)); asm volatile("int $0x80" : "=a"(err)); return 0; }
特に複雑な部分もなく、インラインアセンブラにてwriteシステムコールを呼び出すシステムコール番号とstdoutを意味する「1」,表示する文字列、文字列の長さを各レジスタに格納してint 0x80でシステムコールを呼び出します。その結果は以下のようになり正常に動作することがわかります。
ubnt@ubuntu:~$ gcc -nostdlib stubstart.s -o hello_nolibc hello_nolibc.c ubnt@ubuntu:~$ ./hello_nolibc Hello World, I am chouett0. nice to meet you!!1! ubntubuntu:~$
次に、このバイナリの各セクション情報、逆アセンブラ、サイズを確認します。
ubnt@ubuntu:~$ objdump -h hello_nolibc hello_nolibc: ファイル形式 elf64-x86-64 セクション: 索引名 サイズ VMA LMA File off Algn 0 .note.gnu.build-id 00000024 0000000000400120 0000000000400120 00000120 2**2 CONTENTS, ALLOC, LOAD, READONLY, DATA 1 .text 0000005c 0000000000400144 0000000000400144 00000144 2**0 CONTENTS, ALLOC, LOAD, READONLY, CODE 2 .rodata 00000032 00000000004001a0 00000000004001a0 000001a0 2**3 CONTENTS, ALLOC, LOAD, READONLY, DATA 3 .eh_frame_hdr 00000014 00000000004001d4 00000000004001d4 000001d4 2**2 CONTENTS, ALLOC, LOAD, READONLY, DATA 4 .eh_frame 00000038 00000000004001e8 00000000004001e8 000001e8 2**3 CONTENTS, ALLOC, LOAD, READONLY, DATA 5 .comment 00000034 0000000000000000 0000000000000000 00000220 2**0 CONTENTS, READONLY ubnt@ubuntu:~$ objdump -d hello_nolibc hello_nolibc: ファイル形式 elf64-x86-64 セクション .text の逆アセンブル: 0000000000400144 <_start>: 400144: e8 0a 00 00 00 callq 400153 <main> 400149: b8 3c 00 00 00 mov $0x3c,%eax 40014e: 48 31 ff xor %rdi,%rdi 400151: 0f 05 syscall 0000000000400153 <main>: 400153: 55 push %rbp 400154: 48 89 e5 mov %rsp,%rbp 400157: 48 c7 45 f0 a0 01 40 movq $0x4001a0,-0x10(%rbp) 40015e: 00 40015f: c7 45 ec 00 00 00 00 movl $0x0,-0x14(%rbp) 400166: eb 09 jmp 400171 <main+0x1e> 400168: 8b 45 ec mov -0x14(%rbp),%eax 40016b: 83 c0 01 add $0x1,%eax 40016e: 89 45 ec mov %eax,-0x14(%rbp) 400171: 48 8b 55 f0 mov -0x10(%rbp),%rdx 400175: 8b 45 ec mov -0x14(%rbp),%eax 400178: 48 98 cltq 40017a: 48 01 d0 add %rdx,%rax 40017d: 0f b6 00 movzbl (%rax),%eax 400180: 84 c0 test %al,%al 400182: 75 e4 jne 400168 <main+0x15> 400184: b8 04 00 00 00 mov $0x4,%eax 400189: bb 01 00 00 00 mov $0x1,%ebx 40018e: 8b 4d f0 mov -0x10(%rbp),%ecx 400191: 8b 55 ec mov -0x14(%rbp),%edx 400194: cd 80 int $0x80 400196: 89 45 fc mov %eax,-0x4(%rbp) 400199: b8 00 00 00 00 mov $0x0,%eax 40019e: 5d pop %rbp 40019f: c3 retq ubnt@ubuntu:~$ size hello_nolibc text data bss dec hex filename 254 0 0 254 fe hello_nolibc an3m0ne@an3m0ne:~$ ldd hello_nolibc 動的実行ファイルではありません ubnt@ubuntu:~$
比較するまでもなく、各項目で明らかに違いがあることがわかります。
まず、セクション情報ではglibcを利用していないので無駄なものが無く変数の値が変化することもないのでtextセクション内で完結することによりbssセクションも必要がなくなります。(文字列をconstで宣言することでさらに無駄が省ける?)また、関数を利用せず直接システムコールを実行しているのでPLT領域も必要なくなります。
次に、逆アセンブラ情報では先ほども述べたPLTに関係する関数やGOTなどが存在せず_startとmainでのみ構成されていることがわかります。その結果、glibcを利用している時には1743byteあったバイナリがglibcを利用していない場合では254byteにまで縮小され明らかにサイズが変化していることがわかります。また、共有ライブラリを一切利用していないこともわかりました。
まとめ
今回の検証でライブラリの便利さや、リンカがどのようにバイナリを生成していくのかを少し理解できたと思います。また、ライブラリの冗長さを改めて理解でき大規模なソフトウェアであれば変わりはないものの今回のような小さなバイナリではその冗長さが速度の低下につながり、そういった部分がパフォーマンスチューニングにつながるのだろうか、と感じました。