C程序编译链接全过程 - 弦外之音

/ 0评 / 0

本文主要对 C 程序的编译,链接等过程做一个简单的介绍,环境是 Ubuntu18 + gcc,代码如下:

main.h 头文件代码:

int add(int a,int b)

main.c 文件代码:

#include "main.h"
​
int main()
{
    int age_1 = 12;
    int age_2 = 13;
    int age_all = add(age_1,age_2);
​
    return age_all;
}
​
int add(int a,int b){
    return a+b;
}

比较便捷的编译方法是直接执行以下编译命令:

gcc -o main main.c

上面得 -o 是指定输出文件名。但是为了演示整个编译的过程。下面一步一步执行。

第一步,预处理,就是把 .c 文件的 include 宏指令,全部包含进来。命令如下:

gcc -E -o main.i main.c

上面的 -E 选项是告诉 gcc 不用编译或者链接程序。

可以打开 main.i 文件查看,main.h 头文件已经被包含进来了。



第二步,编译,也就是把 C程序 翻译成 汇编程序,命令如下:

gcc main.i -S -o main.s

gcc 的命令参数解析程序比较智能,无论我把 main.i 放在开头还是末尾,他都能识别到这是输入文件。跟 FFmpeg 的命令行解析类似,FFmpeg 命令行用 -i 指定输入,如果一个参数不是输入文件,就是输出文件参数。

生成的 main.s 就是汇编代码,如下:

    .file   "main.c"
    .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
    subq    $16, %rsp
    movl    $12, -12(%rbp)
    movl    $13, -8(%rbp)
    movl    -8(%rbp), %edx
    movl    -12(%rbp), %eax
    movl    %edx, %esi
    movl    %eax, %edi
    call    add
    movl    %eax, -4(%rbp)
    movl    -4(%rbp), %eax
    leave
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE0:
    .size   main, .-main
    .globl  add
    .type   add, @function
add:
.LFB1:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    movl    %edi, -4(%rbp)
    movl    %esi, -8(%rbp)
    movl    -4(%rbp), %edx
    movl    -8(%rbp), %eax
    addl    %edx, %eax
    popq    %rbp
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE1:
    .size   add, .-add
    .ident  "GCC: (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0"
    .section    .note.GNU-stack,"",@progbits

这里要注意, 里面 调了 call add, 所以EIP寄存器的指令会进行跳转,如下:

到这里,虽然 main.s 已经是汇编指令,汇编指令是机器指令的可读形式,但是现在的 main.s 还是 ASII码,CPU 是不可能能执行 ASII码的,必须转成二进制,也就是机器码。再执行以下编译命令:

gcc -c -o main.o main.s

上面的 -c 选项是 Compile and assemble, but do not link,此时 main.o 里面就是机器码指令。用notepad++ 查看 main.o 的二进制内容,如下图:

基本上是一条汇编对应一条机器码指令,既然是对应的,就找一下 相关的机器码,看一下相邻的汇编指令的机器码是不是也是相邻的。可以用以下命令查看:

objdump -sd main.o

如下图:

在 nodepad++ 搜索 8b 55 f8 8b 45 f4 ,这是两条 mov 指令的机器码。

可以看到整个机器码是连在一起的。

这里讲一个扩展知识点,上面 gcc 把 C程序翻译成 汇编指令,都是从 ASII码转成 ASII码,文本转文本。汇编器也是 gcc,把 汇编指令 转成 机器指令,就是 ASII码转二进制,也就是文本转二进制。还有一种编译技术是二进制翻译,也就是二进制转二进制,也就是翻译器把 某条机器指令 转换成另一条机器指令,通常在虚拟化领域会用到二进制翻译技术。


现在还剩 最后一步,生成可执行文件,命令如下:

gcc -o main main.o

上面的命令会将 main.o 与C标准输入输出库进行连接,最终生成程序 main 可执行文件。


由于笔者的水平有限, 加之编写的同时还要参与开发工作,文中难免会出现一些错误或者不准确的地方,恳请读者批评指正。如果读者有任何宝贵意见,可以加我微信 Loken1,QQ:2338195090。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注