Table of Contents
链接过程 Link to 链接过程
C
1234567
/* main.c */
int sum(int *a, int n);
int array[2] = {1,2};
int main(){
int val = sum(array, 2);
return val;
}
C
12345678
/* sum.c */
int sum(int *a, int n){
int i, s = 0;
for(i=0;i<n;i++){
s += a[i];
}
return s;
}
静态链接
SHELL
1
gcc -Og -o prog main.c sum.c
使用链接器的原因 Link to 使用链接器的原因
- 模块化
- 高效
- 时间:修改一部分源文件无需重新编译其他源文件
- 空间:提升复用性(库函数)
连接器的行为 Link to 连接器的行为
- 符号解析(Symbol Resolution)
- 程序定义和引用符号(函数和全局变量)
- 汇编器将符号定义存储在目标文件的符号表中
- 符号表是结构体数组,每个成员包含名称,大小,位置等信息
- 符号解析过程中,连接器将每个符号引用跟符号定义关联起来.
三类链接器符号
- 全局符号 模块中能够被其他模块使用的符号,如非static的函数和全局变量
- 外部符号 由其他模块定义的全局符号
- 局部符号 在模块中定义且只能在该模块中使用的符号,如static函数和变量
- 局部非静态变量存储在堆栈上
- 局部静态变量存储在.data或.bss中
C123456789101112int f(){ static int x=2; return x; } int g(){ static int x=1; return x; } int h(){ int x=5; return x; }
虽然
f()
和g()
函数的静态变量都叫x,但编译器用别名(x.1,x.2)会加以区分.强符号和弱符号
- 强符号:函数定义和已初始化的全局变量
- 弱符号:未初始化的全局变量
符号规则
- 不允许多个同名的强符号
- 同名的一个强符号和多个弱符号,选择强符号 引用弱符号将被解析到强符号的定义处C123
int x=5; int y=7; void p1(){}
在p2中使用x,会改变y的值!C12double x; void p2(){}
- 多个同名的弱符号,任意选择一个 使用
杜绝这种行为.SHELL1gcc -fno-common
建议
- 减少使用全局变量
- 定义时初始化
- 使用
static
和extern
加以限制
- 重定向(Relocation) 把分离的代码和数据合并在可执行文件的一个板块内.
在重定向前,编译器生成的目标文件的函数和数据的地址只是它在模块中的偏移量,链接器决定当程序执行时它们存储在哪里,将地址和符号绑定起来.
===========================>>>>>>>>>>>>>>>>>>>>>>
三类目标文件 Link to 三类目标文件
可重定位目标文件( *.o) 汇编器的输出;可以和其他.o文件由连接器合并为可执行目标文件.
可执行目标文件(a.out) 连接器输出的可执行文件
共享目标文件(*.so) 动态链接库(dll),运行时加载.
可执行可链接格式(ELF) Link to 可执行可链接格式(ELF)
以上三种目标文件的标准统一格式
- ELF header 定义了字长,字节序,文件类型,机器类型
- 段头部表
- .text 代码
- .rodata 只读数据,如跳转表
- .data 有初始化的全局变量
- .bss 未初始化的全局变量
- .symtab 包含程序全局变量的结构体数组;stactic变量
- .rel.text 需要在可执行目标文件中重定位的指令
- .rel.data 需要在可执行目标文件中重定位的数据
- .debug
gcc -g
包含了源代码的行号与机器代码的行号相关联的信息 - 节头部表 每个section的偏移量和大小.
加载可执行目标文件 Link to 加载可执行目标文件
库:打包常用函数 Link to 库:打包常用函数
通过静态链接常用函数的源文件,有两种实现方式:
- 将所有常用函数写在一个源文件中,这会导致生成的目标文件冗余
- 将每个函数写在单独的源文件中,这会导致链接命令非常长 为了解决这个问题,引入了链接库的技术.
静态链接库(.a文件) Link to 静态链接库(.a文件)
- 原理
- 链接多个.o文件成一个文档
- 当连接器遇到未解析的外部引用时,去文档中寻找该符号
- 如果能找到,将其链接进可执行目标文件
- 创建静态库
- 使用静态库
- 链接器解析外部引用的算法:
- 按照命令行顺序扫描.o和.a文件
- 扫描过程中,维护一个未解析引用列表
- 每次遇到一个新的.o或.a文件,尝试在其中解析未解析引用
- 如果到了末尾还有未解析引用,就报linker error 所以,命令行参数的顺序就十分重要:
是正确的写法,而SHELL1gcc -L . libtest.o -lmine
会发生SHELL1gcc -L . -lmine libtest.o
undefined reference
.-L [dir]
指定所用到库文件所在目录 - 链接器解析外部引用的算法:
动态链接库/共享库(.so文件) Link to 动态链接库/共享库(.so文件)
- 静态库的缺点:
- 可执行文件中冗余存储库函数
- 运行时内存冗余存储库函数
- 库发生更改,程序需要重新链接
- 共享库:目标文件包含在程序运行时加载的代码和数据.
- 动态链接发生在可执行文件第一次加载或运行时
- 也可以发生在开始运行后(
dlopen()
函数) 分布式软件/高性能web服务器/库打桩C12345678910111213141516171819202122232425262728293031323334353637#include <stdio.h> #include <stdlib.h> #include <dlfcn.h> int x[2] = {1, 2}; int y[2] = {3, 4}; int z[2]; int main() { void *handle; void (*addvec)(int *, int *, int *, int); char *error; /* Dynamically load the shared library that contains addvec() */ handle = dlopen("./libvector.so", RTLD_LAZY); if (!handle) { fprintf(stderr, "%s\n", dlerror()); exit(1); } /* Get a pointer to the addvec() function we just loaded */ addvec = dlsym(handle, "addvec"); if ((error = dlerror()) != NULL) { fprintf(stderr, "%s\n", error); exit(1); } /* Now we can call addvec() just like any other function */ addvec(x, y, z, 2); printf("z = [%d %d]\n", z[0], z[1]); /* Unload the shared library */ if (dlclose(handle) < 0) { fprintf(stderr, "%s\n", dlerror()); exit(1); } return 0; }
- 共享库可以被多个进程共同使用 加载时的动态链接:
库打桩技术 Link to 库打桩技术
截获对库函数的调用,取而代之执行自己的代码.使用库打桩技术,可以追踪对某个库函数的调用次数,输入和输出值,甚至替换为不同的函数实现.
应用举例 Link to 应用举例
C
12345678
/* int.c */
#include <stdio.h>
#include <malloc.h>
int main() {
int *p = malloc(32);
free(p);
return(0);
}
我希望使用库打桩技术,在不修改源代码的前提下,追踪分配的地址和大小信息. 有三种实现方式,分别在编译时,链接时和运行时.
编译时 编写
mymalloc.c
:C1234567891011121314151617#ifdef COMPILETIME #include <stdio.h> #include <malloc.h> /* malloc wrapper func */ void *mymalloc(size_t size){ void *ptr = malloc(size); ... // 追踪指针信息 return ptr; } /* free wrapper func */ void myfree(void *ptr){ free(ptr); ... // 追踪指针信息 } #endif
编写头文件
malloc.h
:C12345#define malloc(size) mymalloc(size) #define free(size) free(size) void *mymalloc(size_t size); void myfree(void *ptr);
GCC构建:
SHELL123456$ make gcc -Wall -DCOMPILETIME -c mymalloc.c gcc -Wall -I. -o intc int.c mymalloc.o $ ./intc ...输出地址和大小信息
-I [dir]
参数添加头文件的路径-D[define]
参数预定义宏链接时 编写
mymalloc.c
文件:C123456789101112131415161718#ifdef LINKTIME #include <stdio.h> void *__real_malloc(size_t size); void __real_free(void *ptr); /* malloc wrapper function */ void *__wrap_malloc(size_t size) { void *ptr = __real_malloc(size); /* Call libc malloc */ printf("malloc(%d) = %p\n", (int)size, ptr); return ptr; } /* free wrapper function */ void __wrap_free(void *ptr) { __real_free(ptr); /* Call libc free */ printf("free(%p)\n", ptr); } #endif
gcc构建:
SHELL123456789linux> make intl gcc -Wall -DLINKTIME -c mymalloc.c gcc -Wall -c int.c gcc -Wall -Wl,--wrap,malloc -Wl,--wrap,free -o intl int.o mymalloc.o linux> make runl ./intl malloc(32) = 0x1aa0010 free(0x1aa0010)
-Wl
将后面的逗号替换为空格 使用--wrap=symbol
,symbol
也是一个函数时,对symbol
的引用会解析为__wrap_symbol
函数. 另外还有一个__real_symbol
函数,只声明不定义时,对其调用会解析到真正的symbol
函数. 即:malloc
->__wrap_malloc
__real_malloc
->malloc
运行时 编写
mymalloc.c
:C12345678910111213141516171819202122232425262728#ifdef RUNTIME #define _GNU_SOURCE #include #include #include /* malloc wrapper function */ void *malloc(size_t size) { void *(*mallocp)(size_t size); char *error; mallocp = dlsym(RTLD_NEXT, "malloc"); /* Get addr of libc malloc */ if ((error = dlerror()) != NULL) { fputs(error, stderr); exit(1); } char *ptr = mallocp(size); /* Call libc malloc */ printf("malloc(%d) = %p\n", (int)size, ptr); return ptr; } /* free wrapper function */ void free(void *ptr) { void (*freep)(void *) = NULL; char *error; if (!ptr) return; freep = dlsym(RTLD_NEXT, "free"); /* Get address of libc free */ if ((error = dlerror()) != NULL) { fputs(error, stderr); exit(1); } freep(ptr); /* Call libc free */ printf("free(%p)\n", ptr); } #endif
gcc构建:
SHELL1234567linux> make intr gcc -Wall -DRUNTIME -shared -fpic -o mymalloc.so mymalloc.c -ldl gcc -Wall -o intr int.c linux> make runr (LD_PRELOAD="./mymalloc.so" ./intr) malloc(32) = 0xe60010 free(0xe60010)
LD_PRELOAD
环境变量告诉动态连接器优先在mymalloc.so
中寻找未解析引用.dlsym(RTLD_NEXT, "malloc")
让动态链接器寻找下一个malloc
函数指针
Thanks for reading!