はしくれエンジニアもどきのメモ

情報系技術・哲学・デザインなどの勉強メモ・備忘録です。

gccのコンパイルの流れとオプションのメモ

gccコンパイルの流れとオプションのメモ

docker利用によりWindowsでもMinGWなど使わずとも手軽にUnixベースのgccが利用できるようになった.

gccでの中間コード(Intermediate code; アセンブリコード), オブジェクトコード(Object code), 実行ファイル(Executable file)の流れとオプションを使った生成のメモ.

環境

  • Windows10

コンパイラの流れ(特にgcc

インタプリタ処理系でない)一般的なコンパイラの流れとして, ソースコード.c)があったときにgccに投げて実行ファイルができるまでは以下の流れになる.

  1. 字句解析
  2. 構文解析
  3. 中間コード(Intermediate code; アセンブリコード)が生成される
  4. アセンブラ(assembler):アセンブラに通すとオブジェクトコード(Object code)が生成される
  5. リンカ(linker):オブジェクトコードと標準ライブラリなどのオブジェクトコードをリンクしてひとまとめにすると実行ファイル(Executable file)になる.

各ファイル形式をまとめると

  • ソースコード高級言語で書かれていて今回はC言語でCのコード.拡張子は.c
  • 中間コード,アセンブリコード:テキスト形式.中身はアセンブリコード(低級言語でマイコンで使われるようなコード).gccでは.sファイルとして出力される.
  • オブジェクトコード: バイナリ形式.ただしリンクされていないので実行はできない.開くにはバイナリエディタodコマンドが必要.拡張子は.oファイル.
  • 実行ファイル:オブジェクトコードとほぼ同じだがライブラリとリンクして実行可能になったファイル.
    • Windows: PE形式,拡張子は.exeファイル
    • Unix: ELF形式,拡張子は.outかなし

オブジェクトファイルと実行ファイルの中身は,いわゆるマシンコード(machine code)と呼ばれる.

gccでの例

以下のcで書かれたhellowoldのソースがあったとする.

#include<stdio.h>

int main(void){
     printf("hello world\n");
     return 0;
}

中間(アセンブリ)コードの生成(-Sオプション)

-Sオプション(大文字)でアセンブリコード(.sファイル)を生成できる.

https://gcc.gnu.org/onlinedocs/gcc/Overall-Options.html#index-S

ソース -> アセンブリコード:

gcc -S hello.c

Dockerのgccイメージなら

docker run --rm -v ${PWD}:/usr/src/myapp -w /usr/src/myapp gcc:latest gcc -S hello.c

これでアセンブリコードhello.sが生成される.

    .file   "hello.c"
    .text
    .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: (GNU) 10.1.0"
    .section    .note.GNU-stack,"",@progbits

アセンブラからオブジェクトコードの生成(-cオプション)

-cオプションでオブジェクトコード.oを生成できる.

https://gcc.gnu.org/onlinedocs/gcc/Overall-Options.html#index-c

アセンブリコード -> オブジェクトコード:

gcc -c hello.s

ソース -> オブジェクトコード:

こっちのほうが一般的.

gcc -c hello.c

dockerのgccイメージでも同様に

docker run --rm -v ${PWD}:/usr/src/myapp -w /usr/src/myapp gcc:latest gcc -c hello.c

hello.oが生成される.

WSL上のUbuntuodコマンドで中を見る.

  • -A x: 値の表示を16進数に変更
  • -t x1z: x(16進数)で1byteずつ,zで行末に出力可能文字の出力
:/mnt/c/Users/<user>/Desktop/c$ od -t x1z -A x hello/hello.o

000000 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00  >.ELF............<
000010 01 00 3e 00 01 00 00 00 00 00 00 00 00 00 00 00  >..>.............<
000020 00 00 00 00 00 00 00 00 80 02 00 00 00 00 00 00  >................<
000030 00 00 00 00 40 00 00 00 00 00 40 00 0d 00 0c 00  >....@.....@.....<
000040 55 48 89 e5 bf 00 00 00 00 e8 00 00 00 00 b8 00  >UH..............<
000050 00 00 00 5d c3 68 65 6c 6c 6f 20 77 6f 72 6c 64  >...].hello world<
000060 00 00 47 43 43 3a 20 28 47 4e 55 29 20 31 30 2e  >..GCC: (GNU) 10.<
000070 31 2e 30 00 00 00 00 00 14 00 00 00 00 00 00 00  >1.0.............<
000080 01 7a 52 00 01 78 10 01 1b 0c 07 08 90 01 00 00  >.zR..x..........<
000090 1c 00 00 00 1c 00 00 00 00 00 00 00 15 00 00 00  >................<
0000a0 00 41 0e 10 86 02 43 0d 06 50 0c 07 08 00 00 00  >.A....C..P......<
0000b0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  >................<
0000c0 00 00 00 00 00 00 00 00 01 00 00 00 04 00 f1 ff  >................<
0000d0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  >................<
0000e0 00 00 00 00 03 00 01 00 00 00 00 00 00 00 00 00  >................<
0000f0 00 00 00 00 00 00 00 00 00 00 00 00 03 00 03 00  >................<
000100 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  >................<
000110 00 00 00 00 03 00 04 00 00 00 00 00 00 00 00 00  >................<
000120 00 00 00 00 00 00 00 00 00 00 00 00 03 00 05 00  >................<
000130 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  >................<
000140 00 00 00 00 03 00 07 00 00 00 00 00 00 00 00 00  >................<
000150 00 00 00 00 00 00 00 00 00 00 00 00 03 00 08 00  >................<
000160 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  >................<
000170 00 00 00 00 03 00 06 00 00 00 00 00 00 00 00 00  >................<
000180 00 00 00 00 00 00 00 00 09 00 00 00 12 00 01 00  >................<
000190 00 00 00 00 00 00 00 00 15 00 00 00 00 00 00 00  >................<
0001a0 0e 00 00 00 10 00 00 00 00 00 00 00 00 00 00 00  >................<
0001b0 00 00 00 00 00 00 00 00 00 68 65 6c 6c 6f 2e 63  >.........hello.c<
0001c0 00 6d 61 69 6e 00 70 75 74 73 00 00 00 00 00 00  >.main.puts......<
0001d0 05 00 00 00 00 00 00 00 0a 00 00 00 05 00 00 00  >................<
0001e0 00 00 00 00 00 00 00 00 0a 00 00 00 00 00 00 00  >................<
0001f0 04 00 00 00 0a 00 00 00 fc ff ff ff ff ff ff ff  >................<
000200 20 00 00 00 00 00 00 00 02 00 00 00 02 00 00 00  > ...............<
000210 00 00 00 00 00 00 00 00 00 2e 73 79 6d 74 61 62  >..........symtab<
000220 00 2e 73 74 72 74 61 62 00 2e 73 68 73 74 72 74  >..strtab..shstrt<
000230 61 62 00 2e 72 65 6c 61 2e 74 65 78 74 00 2e 64  >ab..rela.text..d<
000240 61 74 61 00 2e 62 73 73 00 2e 72 6f 64 61 74 61  >ata..bss..rodata<
000250 00 2e 63 6f 6d 6d 65 6e 74 00 2e 6e 6f 74 65 2e  >..comment..note.<
000260 47 4e 55 2d 73 74 61 63 6b 00 2e 72 65 6c 61 2e  >GNU-stack..rela.<
000270 65 68 5f 66 72 61 6d 65 00 00 00 00 00 00 00 00  >eh_frame........<
000280 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  >................<
*
0002c0 20 00 00 00 01 00 00 00 06 00 00 00 00 00 00 00  > ...............<
0002d0 00 00 00 00 00 00 00 00 40 00 00 00 00 00 00 00  >........@.......<
0002e0 15 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  >................<
0002f0 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  >................<
000300 1b 00 00 00 04 00 00 00 40 00 00 00 00 00 00 00  >........@.......<
000310 00 00 00 00 00 00 00 00 d0 01 00 00 00 00 00 00  >................<
000320 30 00 00 00 00 00 00 00 0a 00 00 00 01 00 00 00  >0...............<
000330 08 00 00 00 00 00 00 00 18 00 00 00 00 00 00 00  >................<
000340 26 00 00 00 01 00 00 00 03 00 00 00 00 00 00 00  >&...............<
000350 00 00 00 00 00 00 00 00 55 00 00 00 00 00 00 00  >........U.......<
000360 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  >................<
000370 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  >................<
000380 2c 00 00 00 08 00 00 00 03 00 00 00 00 00 00 00  >,...............<
000390 00 00 00 00 00 00 00 00 55 00 00 00 00 00 00 00  >........U.......<
0003a0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  >................<
0003b0 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  >................<
0003c0 31 00 00 00 01 00 00 00 02 00 00 00 00 00 00 00  >1...............<
0003d0 00 00 00 00 00 00 00 00 55 00 00 00 00 00 00 00  >........U.......<
0003e0 0c 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  >................<
0003f0 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  >................<
000400 39 00 00 00 01 00 00 00 30 00 00 00 00 00 00 00  >9.......0.......<
000410 00 00 00 00 00 00 00 00 61 00 00 00 00 00 00 00  >........a.......<
000420 13 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  >................<
000430 01 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00  >................<
000440 42 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00  >B...............<
000450 00 00 00 00 00 00 00 00 74 00 00 00 00 00 00 00  >........t.......<
000460 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  >................<
000470 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  >................<
000480 57 00 00 00 01 00 00 00 02 00 00 00 00 00 00 00  >W...............<
000490 00 00 00 00 00 00 00 00 78 00 00 00 00 00 00 00  >........x.......<
0004a0 38 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  >8...............<
0004b0 08 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  >................<
0004c0 52 00 00 00 04 00 00 00 40 00 00 00 00 00 00 00  >R.......@.......<
0004d0 00 00 00 00 00 00 00 00 00 02 00 00 00 00 00 00  >................<
0004e0 18 00 00 00 00 00 00 00 0a 00 00 00 08 00 00 00  >................<
0004f0 08 00 00 00 00 00 00 00 18 00 00 00 00 00 00 00  >................<
000500 01 00 00 00 02 00 00 00 00 00 00 00 00 00 00 00  >................<
000510 00 00 00 00 00 00 00 00 b0 00 00 00 00 00 00 00  >................<
000520 08 01 00 00 00 00 00 00 0b 00 00 00 09 00 00 00  >................<
000530 08 00 00 00 00 00 00 00 18 00 00 00 00 00 00 00  >................<
000540 09 00 00 00 03 00 00 00 00 00 00 00 00 00 00 00  >................<
000550 00 00 00 00 00 00 00 00 b8 01 00 00 00 00 00 00  >................<
000560 13 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  >................<
000570 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  >................<
000580 11 00 00 00 03 00 00 00 00 00 00 00 00 00 00 00  >................<
000590 00 00 00 00 00 00 00 00 18 02 00 00 00 00 00 00  >................<
0005a0 61 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  >a...............<
0005b0 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  >................<
0005c0

最後に何バイトあるか16進数で表示される. つまりx0005c0=1472バイト,これがファイルサイズになる. Windows側でhello.oのプロパティをみるとサイズが一致する.

ファイルサイズに対応
ファイルサイズ

実行ファイルの生成(-oオプション)

-oで出力ファイル名を指定する. 何も指定しないとa.outになる.

オブジェクトコードから実行ファイルを生成するには直接オブジェクトコードを指定すればいい.

gcc hello.o -o hello
# or
> docker run --rm -v ${PWD}:/usr/src/myapp -w /usr/src/myapp gcc:latest gcc hello.o -o hello

WSL上のUbuntuで実行すると

:/mnt/c/Users/<user>/Desktop/c$ ./hello/hello
hello world

odコマンドで中を見る. printf関数が静的にリンクされておりファイルが長くなっているので 最初と最後の3x16バイトを表示する.

:/mnt/c/Users/<user>/Desktop/c$ od -t x1z -A x hello/hello

000000 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00  >.ELF............<
000010 02 00 3e 00 01 00 00 00 40 10 40 00 00 00 00 00  >..>.....@.@.....<
000020 40 00 00 00 00 00 00 00 b8 38 00 00 00 00 00 00  >@........8......<
...
003fe0 00 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00  >................<
003ff0 00 00 00 00 00 00 00 00                          >........<
003ff8

複数のオブジェクトコードから実行ファイルの生成

複数ソースのコンパイルと同様の挙動になるが, 事前にオブジェクトコードを作っておけばそのままリンカで実行ファイルを生成できる. dll(so)のように一部の関数を書き換えて実行ファイルの生成ができる.

実際に試すには, 独自関数を利用するように変更. ただしライブラリ化はしてないのでincludeはない.

hello.c:

#include<stdio.h>

int main(void){
     printf("hello world\n");
     printf("%s", str_num(0));
     return;
 }

オブジェクトコードを生成する.

gcc -g -c hello.c -o hello2.o
# or
> docker run --rm -v ${PWD}:/usr/src/myapp -w /usr/src/myapp gcc:latest gcc -g -c hello.c -o hello2.o
hello.c: In function 'main':
hello.c:5:21: warning: implicit declaration of function 'str_num' [-Wimplicit-function-declaration]
    5 |      printf("%s\n", str_num(0));
      |

関数が見つからないので警告がでるが,オブジェクトコードは生成される.

このままオブジェクトコードから実行ファイルの生成をするとエラーになる.

gcc hello2.o -o hello2
# or
> docker run --rm -v ${PWD}:/usr/src/myapp -w /usr/src/myapp gcc:latest gcc hello2.o -o hello2
/usr/bin/ld: hello2.o: in function `main':
/usr/src/myapp/hello.c:5: undefined reference to `str_num'
collect2: error: ld returned 1 exit status

独自関数を別のソースファイルで定義する.

str_num.c:

#include <stdio.h>

char s[5];
char* str_num(int n){
    snprintf(s, 5, "%04d", n);
    return s;
}

この関数のオブジェクトコードを生成する.

gcc -c str_num.c -o str_num.o

2つのオブジェクトコードから実行ファイルを生成する.

gcc hello2.o str_num.o -o hello2
# or
> docker run --rm -v ${PWD}:/usr/src/myapp -w /usr/src/myapp gcc:latest gcc hello2.o str_num.o -o hello2

Ubuntu on WSL上で実行すると

$ ./hello/hello2
hello world
0000

(静的)ライブラリファイルからオブジェクトコードを抽出する

例として,printf.oを抽出する.

dockerのgcc image内にbashで入る.

 docker run --rm -it -v ${PWD}:/usr/src/myapp -w /usr/src/myapp gcc:latest /bin/bash

標準ライブラリのライブラリファイルlibc.aを探す.

※(静的)ライブラリのファイル拡張子は

:/usr/src/myapp# find / -name libc.a
/usr/lib/x86_64-linux-gnu/libc.a

arコマンドで中の関数を確認する.

まず,arコマンドが使えるかを確認する.

:/usr/src/myapp# ar V
GNU ar (GNU Binutils for Debian) 2.31.1
Copyright (C) 2018 Free Software Foundation, Inc.
This program is free software; you may redistribute it under the terms of
the GNU General Public License version 3 or (at your option) any later version.
This program has absolutely no warranty.
  • tオプション: 中のファイルをリスト表示
  • vオプション:詳細表示
 ar tv /usr/lib/x86_64-linux-gnu/libc.a

ar cmdline (GNU Binary Utilities)

全部は表示しきれないので一部抜粋して表示すると

rw-r--r-- 0/0   1040 Jan  1 00:00 1970 time.o
rw-r--r-- 0/0   1496 Jan  1 00:00 1970 sleep.o
rw-r--r-- 0/0   1608 Jan  1 00:00 1970 pause.o
rw-r--r-- 0/0   3016 Jan  1 00:00 1970 fork.o
rw-r--r-- 0/0   1272 Jan  1 00:00 1970 mkdir.o
rw-r--r-- 0/0    496 Jan  1 00:00 1970 open.o
rw-r--r-- 0/0   1712 Jan  1 00:00 1970 read.o
rw-r--r-- 0/0   1712 Jan  1 00:00 1970 write.o
rw-r--r-- 0/0   1744 Jan  1 00:00 1970 select.o
rw-r--r-- 0/0   1760 Jan  1 00:00 1970 send.o
rw-r--r-- 0/0   1272 Jan  1 00:00 1970 shutdown.o
rw-r--r-- 0/0   1272 Jan  1 00:00 1970 socket.o
rw-r--r-- 0/0   3288 Jan  1 00:00 1970 printf_chk.o
rw-r--r-- 0/0   3160 Jan  1 00:00 1970 fprintf_chk.o
rw-r--r-- 0/0   3048 Jan  1 00:00 1970 vprintf_chk.o
rw-r--r-- 0/0   2928 Jan  1 00:00 1970 vfprintf_chk.o

printf_chk関数はおそらくこっち. https://refspecs.linuxbase.org/LSB_5.0.0/LSB-Core-generic/LSB-Core-generic/baselib---printf-chk-1.html

ここには表示してないがprintf.oも中に入ってる. 展開するにはxオプションを使う.

:/usr/src/myapp# ar x /usr/lib/x86_64-linux-gnu/libc.a printf.o
:/usr/src/myapp# ls -l printf.o
-rw-r--r-- 1 root root 1584 Jan 14 16:21 printf.o

アセンブリでオブジェクトコードから中間(アセンブリ)コードを生成する

gccコンテナに入る.

> docker run --rm -it -v ${PWD}:/usr/src/myapp -w /usr/src/myapp gcc:latest /bin/bash

objdumpが入ってるかを確認.

:/usr/src/myapp# objdump -v
GNU objdump (GNU Binutils for Debian) 2.31.1
Copyright (C) 2018 Free Software Foundation, Inc.
This program is free software; you may redistribute it under the terms of
the GNU General Public License version 3 or (at your option) any later version.
This program has absolutely no warranty.
  • -d, --disassemble: Display assembler contents of executable sections
  • -l, --line-numbers: Include line numbers and filenames in output
:/usr/src/myapp# objdump -d -l hello.o

hello.o:     file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <main>:
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

オブジェクトコードからソースコードまで見ることができる. -Sオプションを使う.

まず,デバッグ情報をつけてオブジェクトコードを作っておく.

gcc -g -c hello.c
:/usr/src/myapp# objdump hello.o -S

hello.o:     file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <main>:
#include<stdio.h>

int main(void){
   0:   55                      push   %rbp
   1:   48 89 e5                mov    %rsp,%rbp
     printf("hello world\n");
   4:   bf 00 00 00 00          mov    $0x0,%edi
   9:   e8 00 00 00 00          callq  e <main+0xe>
     return 0;
   e:   b8 00 00 00 00          mov    $0x0,%eax
  13:   5d                      pop    %rbp
  14:   c3                      retq