(Linux) 使用裸ld手动链接c程序

最近在看《程序员的自我修养——链接,装载与库》,然后看到了链接部分,当然之前看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程序的过程,就此结束,本文有何错误之处,望指出改正!并且本文借鉴知乎某篇牛人的文章,这里给出链接 :知乎某同学的文章链接,讲的十分不错!

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值