编译时候参数:
-shared:
Produce a shared object which can then be linked with other objects to form an executable.
Not all systems support this option. For predictable results, you must also specify the
same set of options that were used to generate code (-fpic, -fPIC, or model suboptions) when
you specify this option.[1]
现在默认情况下,大家习惯把共享库 成为动态库了(可能受windows dll影响)
-fPIC:
emit position-independent code, suitable for dynamic linking and avoiding any limit on
the size of the global offset table.
表示编译为位置独立(无关)代码(position independent code),不用此选项的话编译后的代码是位置
相关的所以动态载入时是通过代码拷贝的方式来满足不同进程的需要,而不能达到真
正代码段共享的
目 的。
动态链接一般含义是:用到时候加载库,用到时候解析符号(比如函数调用)。其原理主要是 所有解析的符号地址映射存放在GOT表内。对于函数,还需要plt表内对应的桩代码辅助完成。函数每次调用都跳转到对应plt表内的代码,如果是首次,该代码则调用_dl_runtime_resolve函数来解析对应函数地址,然后返回函数地址到GOT表对应位置,下次访问时候则直接调用该函数了。
关键要理解GOT表和PLT表功能,其对应的段分别为.GOT 和 .GOT.plt. GOT[0]存放dynamic section的地址;
GOT[1]是一个重要数据结构link_map的的地址(也叫库映射数据结构),该链表维护了程序中使用的库的信息,通过该信息可以查找到待解析符号的
地址信息。
GOT[2]存放_dl_runtime_resolve函数的地址。在调用时候还要把待解析函数符号在GOT中的偏移n传入
大致函数:_dl_runtime_resolv(n,link_map) 没有看源码,估计必有这两个参数
在加载时候,GOT[1]和GOT[2] 是特殊处理的,一般赋值为0.当运行时候,加载动态库时,填充真正的值如上所示。
实践: readelf -x .got.plt a.out 可看到文件中GOT[1],GOT[2]处 的确是0
而当
gdb a.out ;
break main;
x/4 0x08049ff4
此时发现 该两处地址已被填充。
/////////////////////////////////////
以上主要是分析函数的动态解析, 在编译可执行主程序时候,是否指定-fPIC都一样。但是如果是外部全局变量,就不同了。如下分析
////////////可执行主程序
#include
#include
void goodbye_world(void);
extern int g_abc;
int main(int argc, char **argv)
{
goodbye_world();
int a=0;
a= g_abc;
exit(0);
}
///////////共享库源文件 abc.c
#include
int g_abc =2;
void goodbye_world(void)
{
printf("dbye, world!\n”");
}
1、gcc -shared -fPIC -o libabc.so abc.c
2、gcc -labc -g -fPIC main.c //待 有-fPIC
或者gcc -labc -g main.c
采用-fPIC后,会根据ebx或者ecx(是情况而定)加上偏移来表示外部变量(ld链接后才能确定偏移量),ebx或者ecx是指向GOT表的(一般是GOT表后端),否则在链接时候直接写死地址。GOT表内存放外部变量的真是地址。
用-fPIC后会用__i686.get_pc_thunk.bx或者 __i686.get_pc_thunk.cx函数来调整ebx或者ecx.
这一点和函数符号动态加载是不同的。 外部数据变量是存储在 .got段内的。
readelf -S a.out 可看到 .got表段,在没运行时候,默认为0,运行是变量地址被加载并填充到GOT.
阅读(2629) | 评论(1) | 转发(0) |