「GNU開発ツール」を読んだのでビルド工程をマニュアル操作で進めてみる

www.oversea-pub.com

書籍に従いつつ手を動かしてみた時のメモ書き。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

動的リンク

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