程序员自我修养1:GCC构建过程

2020-10-09  本文已影响0人  梦工厂

示例代码:hello.c

#include <stdio.h>
int main()
{
    printf("Hello, World!");
    return 0;
}

1. 预处理 gcc –E hello.c –o hello.i

预处理过程主要处理源代码中以#开始的预编译指令,将要包含(include)的文件插入原文件中、将宏定义展开、根据条件编译命令选择要使用的代码,最后将这些代码输出到一个 ".i" 文件中等待进一步处理。

......
extern char *ctermid (char *__s) __attribute__ ((__nothrow__ , __leaf__));
# 914 "/usr/include/stdio.h" 3 4
extern void flockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__));
extern int ftrylockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__)) ;
extern void funlockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__));
# 944 "/usr/include/stdio.h" 3 4
# 2 "hello.c" 2
# 3 "hello.c"
int main()
{
 printf("Hello, World!");
 return 0;
}

2. 编译 gcc –S hello.c –o hello.s

汇编过程就是把预处理完的文件进行一系列词法分析、语法分析、语义分析、优化,生成相应的汇编代码。 构建的核心
程序员自我修养2:编译过程

cat hello.s
        .file        "hello.c"
        .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
        leaq        .LC0(%rip), %rdi
        movl        $0, %eax
        call        printf@PLT
        movl        $0, %eax
        popq        %rbp
        .cfi_def_cfa 7, 8
        ret
        .cfi_endproc
.LFE0:
        .size        main, .-main
        .ident        "GCC: (Debian 6.3.0-18+deb9u1) 6.3.0 20170516"
        .section        .note.GNU-stack,"",@progbits

3. 汇编 gcc –c hello.c –o hello.o

汇编过程是将汇编代码转变为机器指令,每个汇编语句几乎对应一条机器指令。
过程比较简单,没有复杂语法和语义,也没指令优化,根据汇编指令和机器指令的对照表一一翻译即可。

4. 链接 gcc hello.o –o hello

链接过程将多个模块的目标文件和静态库一起拼接成最终的可执行文件,并自动的去修正函数和变量的地址;

静态链接的步骤

  1. 空间和地址分配
    扫描所有的输入目标文件,获得各个段的长度、属性和位置,相似段合并。将各个目标文件的符号表收集统一到全局符号表。
    此时各个段的虚拟地址已经确定,各个符号的虚拟地址也确定了(段内偏移+段的偏移)。
  2. 符号的解析与重定位
    每个段中需要修正的外部符号都会存储在重定位表中,对某个符号重定位时,从全局符号表中查找符号的地址。

链接之前,printf函数地址为空;

objdump -r hello.o //重定位信息
RELOCATION RECORDS FOR [.text]:
OFFSET           TYPE              VALUE
0000000000000007 R_X86_64_PC32     .rodata-0x0000000000000004
0000000000000011 R_X86_64_PLT32    printf-0x0000000000000004

objdump -d hello.o //反汇编
0000000000000000 <main>:
   0:        55                           push   %rbp
   1:        48 89 e5                     mov    %rsp,%rbp
   4:        48 8d 3d 00 00 00 00         lea    0x0(%rip),%rdi        # b <main+0xb>
   b:        b8 00 00 00 00               mov    $0x0,%eax
  10:        e8 00 00 00 00               callq  15 <main+0x15>
  15:        b8 00 00 00 00               mov    $0x0,%eax
  1a:        5d                           pop    %rbp
  1b:        c3                           retq

链接之后,确定了函数的调用地址;

00000000000006b0 <main>:
 6b0:        55                           push   %rbp
 6b1:        48 89 e5                     mov    %rsp,%rbp
 6b4:        48 8d 3d 99 00 00 00         lea    0x99(%rip),%rdi        # 754 <_IO_stdin_used+0x4>
 6bb:        b8 00 00 00 00               mov    $0x0,%eax
 6c0:        e8 9b fe ff ff               callq  560 <printf@plt>
 6c5:        b8 00 00 00 00               mov    $0x0,%eax
 6ca:        5d                           pop    %rbp
 6cb:        c3                           retq
 6cc:        0f 1f 40 00                  nopl   0x0(%rax)
上一篇下一篇

猜你喜欢

热点阅读