本文学习自狄泰软件学院 唐佐林老师的 操作系统课程,本文图片来源于唐佐林老师课程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函数。