「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
...