gccでlibc抜きでコンパイルを通す

Hello from a libc-free world! (Part 1) (Ksplice Blog) がおもしろかったのでやっていく。

libcありのとき

コード

よくあるhello worldのコード

$ cat hello_with_libc.c
# include <stdio.h>

int main()
{
  printf("Hello World\n");
  return 0;
}

コンパイル

$ gcc hello_with_libc.c -o hello_with_libc
$ ./hello_with_libc 
Hello World
$ wc -c ./hello_with_libc 
8561 ./hello_with_libc

もちろん問題なくコンパイルできる

libcなしのとき

コード

libcを使わないコードを書いた。

$ cat hello_no_libc.c
int main()
{
  char *str = "Hello World";
  return 0;
}

コンパイル

$ gcc -nostdlib hello_no_libc.c -o hello_no_libc
/usr/bin/ld: 警告: エントリシンボル _start が見つかりません。デフォルトとして 0000000000400144 を使用します
$ ./hello_no_libc
Segmentation fault (コアダンプ)

-nostdlibをつけてlibcを含まずに、コンパイルするとエントリポイントが見つからないと怒られる。 ELF形式のエントリポイントはcrt1.oで定義されているのでリンクしてみる。

$ gcc -Os -c hello_no_libc.c
$ ld /usr/lib/x86_64-linux-gnu/crt1.o -o hello_no_libc hello_no_libc.o
ld: /usr/lib/debug/usr/lib/x86_64-linux-gnu/crt1.o(.debug_info): 再配置 0 が無効なシンボル索引 11 を持っています
ld: /usr/lib/debug/usr/lib/x86_64-linux-gnu/crt1.o(.debug_info): 再配置 1 が無効なシンボル索引 12 を持っています
...
ld: /usr/lib/debug/usr/lib/x86_64-linux-gnu/crt1.o(.debug_info): 再配置 18 が無効なシンボル索引 13 を持っています
ld: /usr/lib/debug/usr/lib/x86_64-linux-gnu/crt1.o(.debug_info): 再配置 19 が無効なシンボル索引 21 を持っています
ld: /usr/lib/debug/usr/lib/x86_64-linux-gnu/crt1.o(.debug_line): 再配置 0 が無効なシンボル索引 2 を持っています
/usr/lib/x86_64-linux-gnu/crt1.o: 関数 `_start' 内:
(.text+0x12): `__libc_csu_fini' に対する定義されていない参照です
/usr/lib/x86_64-linux-gnu/crt1.o: 関数 `_start' 内:
(.text+0x19): `__libc_csu_init' に対する定義されていない参照です
/usr/lib/x86_64-linux-gnu/crt1.o: 関数 `_start' 内:
(.text+0x25): `__libc_start_main' に対する定義されていない参照です

libc_csu_fini、libc_csu_init、libc_start_mainがないと怒られた。これらはmain関数が呼ばれる前に呼ばれる関数でlibcのセットアップを行う。crt1.oに頼らず、main関数を呼ぶエントリポイントを書く必要がある。

$ cat stubstart.s 
.globl _start

_start:
    call main
    mov  $60, %eax # exit
    xor  %rdi, %rdi   
    syscall

stubstart.sはmain関数を呼んだ後、exitのシステムコールを呼び出し終了する_start関数を書いたものである。 これがエントリポイントとなる。

$ gcc -nostdlib stubstart.s hello_no_libc.c -o hello_no_libc
$ ./hello_no_libc
$ wc -c ./hello_no_libc 
1613 ./hello_no_libc

無事にコンパイルが通り実行できるようになった。冒頭でコンパイルしたHello worldの8561byteに比べ、libcがない分、1613byteとサイズが小さくなったことも確認できる。