手动运行gcc的步骤,编译,汇编,链接。

51
如果您有一个简单的 C 程序,例如:
int main(void) {return 0;}

可以使用 gcc -o test test.c 进行编译。

据我了解,gcc 执行编译、汇编和链接三个步骤。后两个步骤通过运行 asld 完成。

您可以使用 gcc -S test.c 生成汇编代码。

要将汇编代码转换为可执行文件,您应该在终端输入什么?(这样做的原因是为了学习汇编语言)


3
实际上,它首先执行预处理,然后编译,接着汇编,最后链接。如果使用了 Boost 库,预处理可能是整个过程中比较复杂的部分。 - Kerrek SB
5
您可以使用-v参数查看GCC如何调用其子程序,例如:gcc -o test test.c -v - makes
通过在gcc中传递“-###”进行检查 - Johannes Schaub - litb
7个回答

98

以下是使用gcc编译器的不同阶段:

gcc -E  --> Preprocessor, but don't compile
gcc -S  --> Compile but don't assemble
gcc -c  --> Preprocess, compile, and assemble, but don't link
gcc with no switch will link your object files and generate the executable

4
目前的GCC版本不再拥有单独的预处理器(虽然您可以像上面那样请求仅进行预处理)。 - vonbrand
1
不错的答案,可惜没有解释得太多。 - CristiFati
1
如果像我这样的人想要将链接作为单独的命令进行连接,只有ZeZNiQ的回答可以解决。 - Marisha

32
// main.c
#include <stdio.h>

int main(void)
{
        printf("Hello World !\n");
        return 0;
}

为了预处理、编译、汇编,并最终链接简单的“hello world”程序,请按照以下步骤进行:

第一步/4)对main.c进行预处理以生成main.i:

$: gcc -E main.c -o main.i

注意:你也可以直接调用 C 预处理器:

$: cpp main.c -o main.i

第二步/第四步:编译main.i生成main.s:

$: gcc -S main.i -o main.s

第三步/第四步:将main.s组装生成main.o:

$: as main.s -o main.o

注意:您可以使用gcc的-c(小写C)标志将前面提到的1、2、3步骤合并:

$: gcc -c main.s -o main.o // OR $: gcc -c main.c -o main.o

步骤4/4) 将main.o与其它必要的目标文件链接,例如crti.o和crtn.o(它们分别定义函数前导和epilog),crt1.o(包含启动程序初始执行的_start符号),libc.so路径或-lc标志用于libc,最后设置动态链接器的名称,以生成一个动态链接的ELF可执行文件:

在x86_64上:

$: ld /usr/lib/x86_64-linux-gnu/crti.o /usr/lib/x86_64-linux-gnu/crtn.o /usr/lib/x86_64-linux-gnu/crt1.o -lc main.o -dynamic-linker /lib64/ld-linux-x86-64.so.2 -o main_ELF_executable

或者(如果您想指定到libc.so的路径)

$: ld /usr/lib/x86_64-linux-gnu/crti.o /usr/lib/x86_64-linux-gnu/crtn.o /usr/lib/x86_64-linux-gnu/crt1.o /usr/lib/x86_64-linux-gnu/libc.so main.o -dynamic-linker /lib64/ld-linux-x86-64.so.2 -o main_ELF_executable

在32位ARM上:

$: ld /usr/lib/arm-linux-gnueabihf/crti.o /usr/lib/arm-linux-gnueabihf/crtn.o /usr/lib/arm-linux-gnueabihf/crt1.o -lc main.o -dynamic-linker /lib/ld-linux.so.3 -o main_ELF_executable

或者(如果您想指定libc.so的路径)

$: ld /usr/lib/arm-linux-gnueabihf/crti.o /usr/lib/arm-linux-gnueabihf/crtn.o /usr/lib/arm-linux-gnueabihf/crt1.o /usr/lib/arm-linux-gnueabihf/libc.so main.o -dynamic-linker /lib/ld-linux-armhf.so.3 -o main_ELF_executable

您可以运行ELF可执行文件'main_ELF_executable':

$: ./main_ELF_executable

你好,世界!

参考资料:

https://linux.die.net/man/1/gcc

https://linux.die.net/man/1/ld

https://dev.gentoo.org/~vapier/crt.txt


本来想点赞的,但你绝对不应该使用那样的命令行来链接 - 让GCC调用正确的链接器命令! - Antti Haapala -- Слава Україні
8
没错,但OP问的是“你会在终端里输入什么来将汇编代码转化为可执行文件?”,所以我只是想展示如何手动执行每个步骤。这就是为什么我也发布了资源以鼓励OP学习更多,包括但不限于链接顺序也很重要的事实。 - ZeZNiQ
2
如果像我这样的人想要将链接作为单独命令进行连接,只有ZeZNiQ的回答给出了方法。 - Marisha

3

gcc test.s -o test会为您编译test.s文件并生成可执行文件test

NASM也是值得学习的,它可能比gcc更容易、更友好地编译汇编代码。


不完全是这样。gcc 为您处理了许多系统相关的繁文缛节,与它如何处理项目的其他部分完全兼容。 - vonbrand

2

在执行gcc -S -o test.s test.c命令后,输入gcc -o test test.s


1

你可能已经知道或者不知道,编译的四个阶段是预处理(-E)、编译为汇编代码(-S)、汇编为目标代码(-c)和最后的链接。对我来说最难理解的是如何使用预处理器输出。以下是如何操作:

gcc -E hello.c | gcc -S -xc -o hello.s -
gcc -c hello.s -o hello.o
gcc hello.o -o hello

0

你可以让 gcc 在任何地方开始和停止编译过程。使用命令 gcc test.s -o test 将会把 test.s 从汇编语言编译为可执行文件 test


0

我所做的是首先通过以下方式运行预处理器: clang++ test.cpp -E > test.i 然后使用以下方式编译它... clang++ -S test.i 它应该创建一个汇编文件test.s... 然后通过以下方式制作机器指令文件 as test.s -o test.o 现在你需要链接它,这对像我这样的蠢人来说有点困惑... 所以我们不知道我们最后一个过程的参数是什么... 要找出来... 运行 clang++ -v test.s 它应该给你一些大文本... 找到这一行“-dynamic-linker”... 在你的输出文本中肯定有一个-dynamic-linker... 现在复制从-dynamic-linker到输出的其余部分的文本... 只需复制包括“-dynamic-linker”在内的之后的所有内容... 现在我得到的是...

-dynamic-linker /system/bin/linker -o a.out /data/data/com.termux/files/usr/lib/gcc/arm-linux-androideabi/11.1.0/../../../../lib/crtbegin_dynamic.o -L/data/data/com.termux/files/usr/lib/gcc/arm-linux-androideabi/11.1.0 -L/data/data/com.termux/files/usr/lib/gcc/arm-linux-androideabi/11.1.0/../../../../lib -L/data/data/com.termux/files/usr/lib/gcc/arm-linux-androideabi/11.1.0/../../.. -L/data/data/com.termux/files/usr/lib -L/system/lib /data/data/com.termux/files/home/CPP/Cpp_Log/hello_world/test.o -lc++_shared -lgcc -ldl -lm -lc -lgcc -ldl /data/data/com.termux/files/usr/lib/gcc/arm-linux-androideabi/11.1.0/../../../../lib/crtend_android.o

在这里需要修改的是你的目标文件所在的位置... 在我的情况下,它是/data/data/com.termux/files/usr/tmp/test-169b42.o ... 我需要将其更改为我的test.o文件所在的位置 /data/data/com.termux/files/home/CPP/Cpp_Log/hello_world/test.o ... 这是我的test.o文件所在的位置... 所以我们需要传递的参数是...

-dynamic-linker /system/bin/linker -o a.out /data/data/com.termux/files/usr/lib/gcc/arm-linux-androideabi/11.1.0/../../../../lib/crtbegin_dynamic.o -L/data/data/com.termux/files/usr/lib/gcc/arm-linux-androideabi/11.1.0 -L/data/data/com.termux/files/usr/lib/gcc/arm-linux-androideabi/11.1.0/../../../../lib -L/data/data/com.termux/files/usr/lib/gcc/arm-linux-androideabi/11.1.0/../../.. -L/data/data/com.termux/files/usr/lib -L/system/lib /data/data/com.termux/files/home/CPP/Cpp_Log/hello_world/main.o -lc++_shared -lgcc -ldl -lm -lc -lgcc -ldl /data/data/com.termux/files/usr/lib/gcc/arm-linux-androideabi/11.1.0/../../../../lib/crtend_android.o

现在进行链接...使用ld命令...所以命令是ld args -o test或者在我们的情况下...

ld -dynamic-linker /system/bin/linker -o a.out /data/data/com.termux/files/usr/lib/gcc/arm-linux-androideabi/11.1.0/../../../../lib/crtbegin_dynamic.o -L/data/data/com.termux/files/usr/lib/gcc/arm-linux-androideabi/11.1.0 -L/data/data/com.termux/files/usr/lib/gcc/arm-linux-androideabi/11.1.0/../../../../lib -L/data/data/com.termux/files/usr/lib/gcc/arm-linux-androideabi/11.1.0/../../.. -L/data/data/com.termux/files/usr/lib -L/system/lib /data/data/com.termux/files/home/CPP/Cpp_Log/hello_world/main.o -lc++_shared -lgcc -ldl -lm -lc -lgcc -ldl /data/data/com.termux/files/usr/lib/gcc/arm-linux-androideabi/11.1.0/../../../../lib/crtend_android.o -pie -o test ...

因为 Android5+ 只能运行 pie elf 可执行文件,所以我在 -o test 前加了 "-pie" (位置无关可执行文件) ...

现在应该会给你一个名为 test 的可执行文件,只需通过 ./test 运行它即可。

它应该可以正常工作。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接