7 链接器 整合目标文件和库 为可执行程序,代码段的起始地址

本文学习自狄泰软件学院 唐佐林老师的 操作系统课程,本文图片来源于唐佐林老师课程PPT,只用于个人笔记学习


  • 1 链接器的意义
  • 2 链接器的工作内容
  • 3 main()函数是第一个被调用执行的函数吗?
  • 4 自定义程序入口函数

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

目标文件是链接器的工作目标对象

在这里插入图片描述

所以目标文件中各个段没有起始地址,各个标识符也没有自己实际地址,所以目标文件是不可以被执行的。段和标识符的实际地址需要链接器具体确定。

在这里插入图片描述

#include <stdio.h>

int* g_pointer;

void func()
{
    g_pointer = (int*)"D.T.Software";

    return;
}

------------------------------------------------


#include <stdio.h>

int g_global = 0;
int g_test = 1;

extern int* g_pointer;
extern void func();

int main(int argc, char *argv[])
{
    printf("&g_global = %p\n", &g_global);
    printf("&g_test = %p\n", &g_test);
    printf("&g_pointer = %p\n", &g_pointer);
    printf("g_pointer = %p\n", g_pointer);
    printf("&func = %p\n", &func);
    printf("&main = %p\n", &main);
    
    func();
	
    return 0;
}
---------------------------------------------------

在这里插入图片描述

编译 func.c : gcc -c func.c -o func.o
nm 查看目标文件中的标识符

mhr@ubuntu:~/work$ nm func.o
0000000000000000 T func   // 表示 func 这个标识符位于目标文件代码段起始位置的 0偏移处。
0000000000000008 C g_pointer // 表示 暂时不知道 g_pointer 这个标识符应该放在哪个段,能知道的是 该标识符占用八个字节(64位系统)


mhr@ubuntu:~/work$ gcc -c test.c -o test.o
mhr@ubuntu:~/work$ 
mhr@ubuntu:~/work$ 
mhr@ubuntu:~/work$ nm test.o
                 U func     // 在 test.o中有 这个标识符的影子,但是不知道他是在哪里的定义的。在外部定义的。
0000000000000000 B g_global //在BSS段,相对于bss段的起始地址为0
                 U g_pointer  // 在 test.o中有 这个标识符的影子,但是不知道他是在哪里的定义的。在外部定义的。
0000000000000000 D g_test   // date段 具体地址不确定
0000000000000000 T main    // test代码段 具体地址不确定
                 U printf  // 在 test.o中有 这个标识符的影子,但是不知道他是在哪里的定义的。在外部定义的。
mhr@ubuntu:~/work$ 

使用链接器链接目标文件之后生成可执行程序,并执行:

mhr@ubuntu:~/work$ gcc test.o func.o -o test.out 
mhr@ubuntu:~/work$ ./test.out 
&g_global = 0x601044
&g_test = 0x601038
&g_pointer = 0x601048
g_pointer = (nil)
&func = 0x4005c3
&main = 0x400526
mhr@ubuntu:~/work$ 

使用nm 查看 test.out

mhr@ubuntu:~/work$ nm test.out
000000000060103c B __bss_start
0000000000601040 b completed.7585
0000000000601028 D __data_start
0000000000601028 W data_start
0000000000400460 t deregister_tm_clones
00000000004004e0 t __do_global_dtors_aux
0000000000600e18 t __do_global_dtors_aux_fini_array_entry
0000000000601030 D __dso_handle
0000000000600e28 d _DYNAMIC
000000000060103c D _edata
0000000000601050 B _end
0000000000400654 T _fini
0000000000400500 t frame_dummy
0000000000600e10 t __frame_dummy_init_array_entry
0000000000400818 r __FRAME_END__
00000000004005c3 T func //func 标识符位于 test 代码段,最终的地址也确定了
0000000000601044 B g_global
0000000000601000 d _GLOBAL_OFFSET_TABLE_
                 w __gmon_start__
00000000004006c8 r __GNU_EH_FRAME_HDR
0000000000601048 B g_pointer // 链接之后 g_pointer 标识符 位于BSS段 具体地址也有了。
0000000000601038 D g_test
00000000004003c8 T _init
0000000000600e18 t __init_array_end
0000000000600e10 t __init_array_start
0000000000400660 R _IO_stdin_used
                 w _ITM_deregisterTMCloneTable
                 w _ITM_registerTMCloneTable
0000000000600e20 d __JCR_END__
0000000000600e20 d __JCR_LIST__
                 w _Jv_RegisterClasses
0000000000400650 T __libc_csu_fini
00000000004005e0 T __libc_csu_init
                 U __libc_start_main@@GLIBC_2.2.5
0000000000400526 T main
                 U printf@@GLIBC_2.2.5
00000000004004a0 t register_tm_clones
0000000000400430 T _start
0000000000601040 D __TMC_END__
mhr@ubuntu:~/work$ 

由此验证了 链接器就是协调各个目标文件和库文件,将他们整合在一起,让他们协调工作,除此之外,还有重定位功能,就是给段中的各个标识符最终的地址,否则生成的可执行程序是不能正常执行的。


在这里插入图片描述

在这里插入图片描述

#include <stdio.h>
#include <stdlib.h>

int main()
{
    printf("D.T.Software\n");
	
    exit(0);
}
-----------------------------------------------

编译链接运行

mhr@ubuntu:~/work$ gcc -c program.c -o program.o
mhr@ubuntu:~/work$ 
mhr@ubuntu:~/work$ gcc program.o -o program.out
mhr@ubuntu:~/work$ 
mhr@ubuntu:~/work$ ./program.out
D.T.Software
mhr@ubuntu:~/work$ 

反汇编program.out 得到汇编源码:

mhr@ubuntu:~/work$ 
mhr@ubuntu:~/work$ objdump -S program.out > result.txt
mhr@ubuntu:~/work$ 

result.txt

0000000000400470 <_start>:  // _start函数是被第一个调用执行的函数吗
  400470:	31 ed                	xor    %ebp,%ebp
...
  40048d:	48 c7 c7 66 05 40 00 	mov    $0x400566,%rdi //将main的入口地址压入栈中,作为 <__libc_start_main 函数的参数
  400494:	e8 a7 ff ff ff       	callq  400440 <__libc_start_main@plt> //执行 <__libc_start_main 函数
  400499:	f4                   	hlt    
  40049a:	66 0f 1f 44 00 00    	nopw   0x0(%rax,%rax,1)

...

0000000000400566 <main>:
  400566:	55                   	push   %rbp // main 的入口地址
  400567:	48 89 e5             	mov    %rsp,%rbp
  40056a:	bf 04 06 40 00       	mov    $0x400604,%edi
  40056f:	e8 bc fe ff ff       	callq  400430 <puts@plt>
  400574:	bf 00 00 00 00       	mov    $0x0,%edi
  400579:	e8 d2 fe ff ff       	callq  400450 <exit@plt>
  40057e:	66 90                	xchg   %ax,%ax

_start函数是被第一个调用执行的函数吗?

_start函数的入口地址就是目标可执行程序的代码段的起始地址!!

objdump -h program.out 查看可执行程序中的详细段信息

mhr@ubuntu:~/work$ 
mhr@ubuntu:~/work$ objdump -h program.out

program.out:     file format elf64-x86-64

Sections:
Idx Name          Size      VMA               LMA               File off  Algn
...

// 0000000000400470   代码段的起始地址 。和反汇编里面的 _start的入口地址一致:0000000000400470 <_start>:
 13 .text         00000182  0000000000400470  0000000000400470  00000470  2**4
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
 ...
mhr@ubuntu:~/work$ 

Linux环境下可执行程序被加载执行的过程:

  • 1 创建续存空间
  • 2 将对应的段从可执行的文件里面拷贝到内存中
  • 3 执行程序

是怎么执行程序的?

当所有的段都位于内存中之后,就会将PC指针指向代码段的起始地址,并执行代码段起始地址中的指令了,即 _start函数就是应用程序被加载执行之后调用的第一个函数。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

#include <stdio.h>
#include <stdlib.h>

int program()
{
    printf("D.T.Software\n");
	
    exit(0);
}

编译,链接

mhr@ubuntu:~/work$ gcc -c program.c -o program.o
mhr@ubuntu:~/work$ 
mhr@ubuntu:~/work$ gcc -e program program.o -o program.out
/usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/crt1.o: In function `_start':
(.text+0x20): undefined reference to `main'
collect2: error: ld returned 1 exit status
mhr@ubuntu:~/work$ 
mhr@ubuntu:~/work$ 
mhr@ubuntu:~/work$ gcc -e program -nostartfiles program.o -o program.out
mhr@ubuntu:~/work$ 
mhr@ubuntu:~/work$ ./program.out 
D.T.Software
mhr@ubuntu:~/work$ 

nm 查看 program.out 可执行程序中的各个标识符信息

mhr@ubuntu:~/work$ nm program.out 
0000000000601028 D __bss_start
0000000000600ec0 d _DYNAMIC
0000000000601028 D _edata
0000000000601028 D _end
                 U exit@@GLIBC_2.2.5
0000000000601000 d _GLOBAL_OFFSET_TABLE_
00000000004003b8 r __GNU_EH_FRAME_HDR
0000000000400390 T program //代码段只有 program  一个函数 地址 0000000000400390 
                 U puts@@GLIBC_2.2.5
mhr@ubuntu:~/work$ 

objdump -h program.out 查看 program.out 可执行程序中的各个段的详细信息

mhr@ubuntu:~/work$ objdump -h program.out

program.out:     file format elf64-x86-64

Sections:
Idx Name          Size      VMA               LMA               File off  Algn
...
//可以发现 可执行程序代码段的起始地址是 0000000000400390   和  nm program.out  查看 program  标识符 的地址是一样的 0000000000400390 T program
  9 .text         00000018  0000000000400390  0000000000400390  00000390  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
...

mhr@ubuntu:~/work$ 

在这里插入图片描述
-nostartfiles : 在链接的时候不使用系统里面的标准启动文件!!

在这里插入图片描述

在默认情况下 gcc 在链接的时候 就会使用一系列系统当中定义好的.o文件 ,在这些.o文件中就对应的包含了 _start函数。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Linux老A

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值