「GNU開発ツール」を読んだのでビルド工程をマニュアル操作で進めてみる
書籍に従いつつ手を動かしてみた時のメモ書き。gcc のバージョンの違いに注意して進めて行きます。
$ gcc -dumpversion 4.8
プリプロセス
C 言語ソース (.c) → 前処理済み C 言語ソース (.i)
$ cat hello.c #include <stdio.h> int main() { printf("Hello world\n"); return 0; } $ cpp hello.c > hello.i $ head hello.i # 1 "hello.c" # 1 "<built-in>" # 1 "<command-line>" # 1 "/usr/include/stdc-predef.h" 1 3 4 # 1 "<command-line>" 2 # 1 "hello.c" # 1 "/usr/include/stdio.h" 1 3 4 # 27 "/usr/include/stdio.h" 3 4 # 1 "/usr/include/features.h" 1 3 4 # 374 "/usr/include/features.h" 3 4 $ tail hello.i extern void funlockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__)); # 943 "/usr/include/stdio.h" 3 4 # 2 "hello.c" 2 int main() { printf("Hello world\n"); return 0; }
コンパイル
前処理済み C 言語ソースファイル (.i) → アセンブリ言語ソースファイル (.s)
$ `gcc -print-prog-name=cc1` hello.i main Analyzing compilation unit Performing interprocedural optimizations <*free_lang_data> <visibility> <early_local_cleanups> <*free_inline_summary> <whole-program>Assembling functions: main Execution times (seconds) phase setup : 0.00 ( 0%) usr 0.00 ( 0%) sys 0.00 ( 0%) wall 1094 kB (74%) ggc phase parsing : 0.01 (100%) usr 0.00 ( 0%) sys 0.01 (33%) wall 329 kB (22%) ggc phase finalize : 0.00 ( 0%) usr 0.00 ( 0%) sys 0.01 (33%) wall 0 kB ( 0%) ggc preprocessing : 0.01 (100%) usr 0.00 ( 0%) sys 0.01 (33%) wall 25 kB ( 2%) ggc TOTAL : 0.01 0.00 0.03 1472 kB $ cat hello.s .file "hello.i" .section .rodata .LC0: .string "Hello world" .text .globl main .type main, @function main: .LFB0: .cfi_startproc pushq %rbp .cfi_def_cfa_offset 16 .cfi_offset 6, -16 movq %rsp, %rbp .cfi_def_cfa_register 6 movl $.LC0, %edi call puts movl $0, %eax popq %rbp .cfi_def_cfa 7, 8 ret .cfi_endproc .LFE0: .size main, .-main .ident "GCC: (Ubuntu 4.8.4-2ubuntu1~14.04.3) 4.8.4" .section .note.GNU-stack,"",@progbits
アセンブル
アセンブリ言語ソースファイル → オブジェクトファイル
$ as -o hello.o hello.s $ file hello.o hello.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped $ readelf -S hello.o There are 13 section headers, starting at offset 0x130: Section Headers: [Nr] Name Type Address Offset Size EntSize Flags Link Info Align [ 0] NULL 0000000000000000 00000000 0000000000000000 0000000000000000 0 0 0 [ 1] .text PROGBITS 0000000000000000 00000040 0000000000000015 0000000000000000 AX 0 0 1 [ 2] .rela.text RELA 0000000000000000 00000590 0000000000000030 0000000000000018 11 1 8 [ 3] .data PROGBITS 0000000000000000 00000055 0000000000000000 0000000000000000 WA 0 0 1 [ 4] .bss NOBITS 0000000000000000 00000055 0000000000000000 0000000000000000 WA 0 0 1 [ 5] .rodata PROGBITS 0000000000000000 00000055 000000000000000c 0000000000000000 A 0 0 1 [ 6] .comment PROGBITS 0000000000000000 00000061 000000000000002c 0000000000000001 MS 0 0 1 [ 7] .note.GNU-stack PROGBITS 0000000000000000 0000008d 0000000000000000 0000000000000000 0 0 1 [ 8] .eh_frame PROGBITS 0000000000000000 00000090 0000000000000038 0000000000000000 A 0 0 8 [ 9] .rela.eh_frame RELA 0000000000000000 000005c0 0000000000000018 0000000000000018 11 8 8 [10] .shstrtab STRTAB 0000000000000000 000000c8 0000000000000061 0000000000000000 0 0 1 [11] .symtab SYMTAB 0000000000000000 00000470 0000000000000108 0000000000000018 12 9 8 [12] .strtab STRTAB 0000000000000000 00000578 0000000000000013 0000000000000000 0 0 1 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) $ objdump -j .rodata -s hello.o hello.o: file format elf64-x86-64 Contents of section .rodata: 0000 48656c6c 6f20776f 726c6400 Hello world. $ objdump -d hello.o hello.o: file format elf64-x86-64 Disassembly of section .text: 0000000000000000 <main>: 0: 55 push %rbp 1: 48 89 e5 mov %rsp,%rbp 4: bf 00 00 00 00 mov $0x0,%edi 9: e8 00 00 00 00 callq e <main+0xe> e: b8 00 00 00 00 mov $0x0,%eax 13: 5d pop %rbp 14: c3 retq $ nm hello.o 0000000000000000 T main U puts
- nm の T は .text セクションに含まれてることを意味し、U は Undefined の略で外部参照。
静的リンク
$ gcc -print-file-name=libc.a /usr/lib/gcc/x86_64-linux-gnu/4.8/../../../x86_64-linux-gnu/libc.a $ ar t `gcc -print-file-name=libc.a` | head init-first.o libc-start.o sysdep.o version.o check_fds.o libc-tls.o elf-init.o dso_handle.o errno.o init-arch.o $ nm `gcc -print-file-name=libc.a` 2>/dev/null | grep -C 5 "T printf$" U vfprintf printf.o: 0000000000000000 T _IO_printf 0000000000000000 T __printf 0000000000000000 T printf U stdout U vfprintf snprintf.o: U _IO_vsnprintf
- .a は ar コマンドで作成されたアーカイブファイルであり複数のオブジェクトファイルを連結したもの
$ ld -o hello_static `gcc -print-file-name=crt1.o` `gcc -print-file-name=crti.o` hello.o `gcc -print-file-name=libc.a` `gcc -print-file-name=libgcc_eh.a` `gcc -print-libgcc-file-name` `gcc -print-file-name=libc.a` `gcc -print-file-name=crtn.o` $ ./hello_static Hello world $ ll hello_static -rwxrwxr-x 1 872525 Jun 3 23:59 hello_static* $ file hello_static hello_static: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, for GNU/Linux 2.6.24, not stripped
- 日記/2009/11/28/gccでstaticリンクさせようと手動でldコマンド動かしたメモ - Glamenv-Septzen.net・・・こちらを参考にしたらリンクできた。
- crt = C RunTime start up。_start から argc, argv, envp の設定をして main を呼んだり、終了時には atexit で登録された処理を実行して exit システムコールで終了したり。
動的リンク
$ file /lib/x86_64-linux-gnu/libc-2.19.so /lib/x86_64-linux-gnu/libc-2.19.so: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), BuildID[sha1]=cf699a15caae64f50311fc4655b86dc39a479789, for GNU/Linux 2.6.24, stripped $ nm /lib/x86_64-linux-gnu/libc-2.19.so nm: /lib/x86_64-linux-gnu/libc-2.19.so: no symbols $ readelf -s /lib/x86_64-linux-gnu/libc-2.19.so | grep " printf@@" 596: 0000000000054340 161 FUNC GLOBAL DEFAULT 12 printf@@GLIBC_2.2.5
- strip コマンドで付加情報が削除されてる(= stripped) 場合、nm コマンドでは解析できない。
$ ld -o hello_dynamic `gcc -print-file-name=crt1.o` `gcc -print-file-name=crti.o` hello.o `gcc -print-libgcc-file-name` `gcc -print-file-name=crtn.o` -lc -dynamic-linker /lib64/ld-linux-x86-64.so.2 $ ./hello_dynamic Hello world $ ll hello_dynamic -rwxrwxr-x 1 4907 Jun 4 00:06 hello_dynamic* $ file hello_dynamic hello_dynamic: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, not stripped $ readelf -l hello_dynamic Elf file type is EXEC (Executable file) Entry point 0x4003c0 There are 7 program headers, starting at offset 64 Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flags Align PHDR 0x0000000000000040 0x0000000000400040 0x0000000000400040 0x0000000000000188 0x0000000000000188 R E 8 INTERP 0x00000000000001c8 0x00000000004001c8 0x00000000004001c8 0x000000000000001c 0x000000000000001c R 1 [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2] LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000 0x0000000000000580 0x0000000000000580 R E 200000 LOAD 0x0000000000000580 0x0000000000600580 0x0000000000600580 0x00000000000001cc 0x00000000000001cc RW 200000 DYNAMIC 0x0000000000000580 0x0000000000600580 0x0000000000600580 0x0000000000000190 0x0000000000000190 RW 8 NOTE 0x00000000000001e4 0x00000000004001e4 0x00000000004001e4 0x0000000000000020 0x0000000000000020 R 4 GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 RW 10 Section to Segment mapping: Segment Sections... 00 01 .interp 02 .interp .note.ABI-tag .hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt .init .plt .text .fini .rodata .eh_frame 03 .dynamic .got .got.plt .data 04 .dynamic 05 .note.ABI-tag 06
- 64 bit 環境なので ld-linux のファイル名が違った。
$ ls a.out $ ./a.out bash: ./a.out: No such file or directory $ ./b.out bash: ./b.out: No such file or directory
- ELF ローダが正しく指定されてないプログラムを起動しようとすると、ファイル自体は有るのにまるでファイルが無いかのようなメッセージが出るの、面白すぎる。
日々のデバッグに役立ちそうな情報
$ ldd /bin/pwd linux-vdso.so.1 => (0x00007ffd4b9e3000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f5376114000) /lib64/ld-linux-x86-64.so.2 (0x00007f53764d9000) $ LD_DEBUG=help /bin/pwd Valid options for the LD_DEBUG environment variable are: libs display library search paths reloc display relocation processing files display progress for input file symbols display symbol table processing bindings display information about symbol binding versions display version dependencies scopes display scope information all all previous options combined statistics display relocation statistics unused determined unused DSOs help display this help message and exit To direct the debugging output into a file instead of standard output a filename can be specified using the LD_DEBUG_OUTPUT environment variable. $ LD_DEBUG=libs /bin/pwd ...