我正在学习编译过程,我知道链接主要用于链接包含' main'的二进制文件。与其他二进制文件一起使用,其中包含在我们的主函数中使用的其他辅助函数。
但是,当我尝试使用代码运行目标文件时:
int main() {
return 0;
}
在Ubuntu的gcc中使用-c命令编译,我尝试运行它并得到错误:
" bash:./ source.o:无法执行二进制文件:执行格式错误"
答案 0 :(得分:6)
阅读Levine的Linkers & Loaders。
了解ELF。
尝试使用gcc -v
进行编译(您将看到使用的实际程序是什么:cc1
到compile C代码到一些汇编程序,as
到{{ 3}}进入某个目标文件,assemble& ld
到collect2
)。另请查看生成的汇编程序文件gcc -S -fverbose-asm -O
。请注意gcc
知道{{1>}函数(并编译特别)main
函数。可执行文件的起点由一些link等提供(它是不是 main
,但是一些_start
例程在汇编程序中编码,调用您的{{ 1}} ....)。
crt0与Object files不同。可执行文件包含executables和crt0之类的内容,或某种方式C standard library dynamically link(并且您需要链接main
编译的shared object您source.o
中的空main
- 因此而变为可执行文件。
在Linux上,使用objdump(1)& readelf(1)(在一些现有的二进制文件上,也在您的source.c
目标文件上)
另请参阅elf(5),execve(2),ld-linux(8),Linux assembly howto,syscalls(2),Advanced Linux Programming,Operating Systems: Three Easy Pieces和(至)了解source.o
)Drepper的How To Write Shared Libraries,Dragon Book ......
(你需要阅读整本书才能理解细节;我提供了一些参考资料)
另请参阅Common Lisp& SBCL。它的编译器有一个非常不同的模型(与C完全不同)。
答案 1 :(得分:3)
你没有引导程序。你是在这个鸡和蛋的问题。
代码(用于该函数)就在那里,但有一些假设,首先你需要一个堆栈。例如,根据架构,您的返回地址可能位于该堆栈上。返回值可以在该堆栈上。 C语言本身并不直接在语言中提供,因此总是至少需要一些程序集或一些其他语言才能“引导”你的函数。例如在ARM中用于gnu:
bs.s
.globl _start
_start:
mov sp,#0x8000
bl main
b .
so.c
int main ( void )
{
return(0);
}
对于ARM,函数完成后,链接器不需要修改指令。但是没有定义地址空间,无论是指定的还是反汇编程序都假定零作为此对象的地址,但它是一个对象而不是可加载的二进制文件。
00000000 <main>:
0: e3a00000 mov r0, #0
4: e12fff1e bx lr
现在,如果我们添加引导程序并链接到某个地址,我们将获得一个真实的,可执行的程序
00008000 <_start>:
8000: e3a0d902 mov sp, #32768 ; 0x8000
8004: eb000000 bl 800c <main>
8008: eafffffe b 8008 <_start+0x8>
0000800c <main>:
800c: e3a00000 mov r0, #0
8010: e12fff1e bx lr
这并不意味着无法创建操作系统,也无法使用编译器对象输出以这种方式加载函数。但这就是单词链,工具链的原因。编译器使用汇编语言,汇编程序汇编汇编语言,结合其他必要的对象(引导程序加编译器库和C库等),链接器为所有内容定义地址空间,并根据需要修改代码/数据以解析外部。获得最终结果的序列或事件链。
答案 2 :(得分:-2)
即使是最基本的命令,例如exit
,也不是直接使用语言,而是需要链接。