chouett0's note

掃きだめ的なsomething

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にまで縮小され明らかにサイズが変化していることがわかります。また、共有ライブラリを一切利用していないこともわかりました。

まとめ

今回の検証でライブラリの便利さや、リンカがどのようにバイナリを生成していくのかを少し理解できたと思います。また、ライブラリの冗長さを改めて理解でき大規模なソフトウェアであれば変わりはないものの今回のような小さなバイナリではその冗長さが速度の低下につながり、そういった部分がパフォーマンスチューニングにつながるのだろうか、と感じました。

参考文献

nopipi.hatenablog.com

tkmr.hatenablog.com

softwaretechnique.jp

nopipi.hatenablog.com