最近在看《程序员的自我修养——链接,装载与库》,然后看到了链接部分,当然之前看csapp某一章节时,也看到了链接部分,本想手动实验一下,但是,到关键的链接部分了给了如下程序:!!!
ld -o prog [system object files and args] /tmp/main.o /tmp/sum.o
[system object files and args]
? 这玩意是个啥,咋不说清楚?然后我就从《程序员的自我修养——链接,装载与库》跳章节开始找答案,找到
这一章,看了他说Linux 的/usr/lib/libc.a 里面存放了 printf.o这个目标文件,我们把他取出来在链接一下就可以了,但是反转来了,我看到他文章里写的
我看到这里直接被气到了,一个不幸和一个代价太大,我就无法实现我想链接的目的了,这不和csapp上一模一样吗,坑人!!!但是我今天就一定要实现这个过程,哪怕我跳过这些目标文件,写两个不调用头文件的代码。然后我就写了两个简单的程序。
main.c
int sum(int* a, int n);
int array[2]={1,2};
int main() {
int val = sum(array, 2);
return 0;
}
sum.c
int sum(int* a, int n) {
int i = 0, s = 0;
for (int i = 0; i < n; i++) {
s += a[i];
}
return s;
}
这两个程序没有输出,没有调用头文件,应该可以链接成功了吧!
这里多提一嘴,《程序员的自我修养——链接,装载与库》里写道
虽然我们的程序都没有用到头文件,这个情况应该不会有吧,但是我们还是加上吧,反正我感觉加不加都一样,我没有试,你要想试的话,试一试也可以,帮我验证一下猜想。然后我们准备好这两个程序就开始操作吧:
gcc -c -fno-builtin main.c -o main.o
gcc -c -fno-builtin sum.c -o sum.o
执行以上两个程序产生main.o sum.o
然后我们现在就可以进行链接了,使用下面这条语句
ld -e main main.o sum.o -o prog
这里解释一下 -e main,看下图蓝色画线应该就能理解了,最好全部读一读,蓝色部分是原因
-e main 其实是指定入口,指定入口就是main函数,这里我们不依靠Glibc我们可以把int main()
这个主函数名改成我们想改的样子,正如上图所说他写成 niam 也是可以的。
然后我们执行prog
./prog
上图显示了段错误,卧槽这又是为啥?!!!!
不知道有没有人注意上上图画红线的地方,就是那句对于一个进程的开始和结束其实就是依靠操作系统提供的api来实现的,那么如何调用操作系统的api,就是通过语言库,在我们的测试环境下,c语言库就是gnu搞的Glibc库,此时我们已经不依赖人家的库了,那么进程的结束的活就没人干了。进程的开始可以理解为,我们通过shell,也就是壳进行了系统调用,让进程开始了,但是结束却没人管了,但为什么会产生段错误,请看下图:
所以我们需要手动实现一个EXIT 来让进程结束。由于c语言可以内嵌汇编代码,所以在main.c 内内嵌汇编程序,通过系统中断完成进程结束。
改变后的main.c:
int sum(int* a, int n);
int array[2] = {1, 2};
void exit() {
asm("movq $14,%rdi \n\t"
"movq $60,%rax \n\t"
"syscall \n\t");
}
int main() {
int val = sum(array, 2);
exit();
return 0;
}
我们先看结果,最后我在讲解为什么这样内嵌汇编就能实现exit(),我们通过之前的方法将main.c 和 sum.c重现编译成 .o文件,然后 ld -e main main.o sum.o -o prog,在执行看看结果:
可以看出执行成功,没有报段错误,而且我们通过echo $?看到了返回的状态
记录上一个命令退出后的状态的寄存器是%rdi。
这里讲一下为什么上述汇编程序可以实现exit()
void exit() {
asm("movq $14,%rdi \n\t"
"movq $60,%rax \n\t"
"syscall \n\t");
}
看下面解释:
系统调用号不同,比如x86中sys_write是4,sys_exit是1。
而x86_64sys_write是1, sys_exit是60。linux系统调用号实际上定义 在/usr/include/asm/unistd_32.h和/usr/include/asm/unistd_64.h中。
系统调用所使用的寄存器不同,x86_64中使用与eax对应的rax传递系统调用号,但是 x86_64中分别使用rdi/rsi/rdx传递前三个参数,而不是x86中的ebx/ecx/edx。系统调用使用“syscall”而不是“int 80”
这个汇编语言的过程如下:
1.rdi=14,rdi传递参数,当指令退出时,记录状态。
2.rax=60,在x86-64系统中,sys_exit的系统调用号60,rax传递系统调用号。
3.syscall ,含义是产生中断,切换用户态为内核态,通过rax传递的系统调用号,进行系统调用,这里传递的系统调用号是60,调用sys_exit,结束进程。
使用裸ld手动链接c程序的过程,就此结束,本文有何错误之处,望指出改正!并且本文借鉴知乎某篇牛人的文章,这里给出链接 :知乎某同学的文章链接,讲的十分不错!