使用readelf查看程序或者动态库的信息时,可以看到函数的静态地址,当程序开始执行时,又会有一个运行时地址,这两者有什么关系呢?
1.测试代码
编写如下代码
foo.c
#include <stdio.h>
void foo()
{
printf( "foo address is %p\n", foo );
return;
}
bar.c
#include <stdio.h>
void bar()
{
printf( "bar address is %p\n", bar );
return;
}
demo.c
#include <stdio.h>
void foo();
void bar();
int main()
{
printf( "main address is %p\n", main );
printf( "main foo address is %p\n", foo );
printf( "main bar address is %p\n", bar );
foo();
bar();
}
编译生成demo,libfoo.so,libbar.so
[root@kino-chen ~]# gcc -fPIC -shared foo.c -o libfoo.so
[root@kino-chen ~]# gcc -fPIC -shared bar.c -o libbar.so
[root@kino-chen ~]# gcc demo.c -o demo -g -L./ -lfoo -lbar
执行结果
[root@kino-chen ~]# ./demo
main address is 0x40072d
main foo address is 0x400630
main bar address is 0x4005f0
foo address is 0x400630
bar address is 0x4005f0
2. 静态地址
使用readelf查看demo,得到如下信息,其中第一列就是函数静态地址
000000000040072d 86 FUNC GLOBAL DEFAULT 13 main
000000000400630 0 FUNC GLOBAL DEFAULT UND foo
00000000004005f0 0 FUNC GLOBAL DEFAULT UND bar
使用readelf查看libfoo.so,可以看到foo()在libfoo.so的静态地址是0x6d5
00000000000006d5 34 FUNC GLOBAL DEFAULT 11 foo
结合程序的输出信息,可以发现程序运行时输出的函数地址与在demo中看懂的地址是一样的,因此可以知道程序输出动态库的函数地址时,是其在主程序文件中编译时确定的静态地址,而不是运行时地址。
3.运行时地址
通过gdb,可以看到foo()和bar的运行时地址分别如下,与程序输出并不一致
(gdb) p foo
$1 = {<text variable, no debug info>} 0x7ffff7bdb6d5 <foo>
(gdb) p bar
$2 = {<text variable, no debug info>} 0x7ffff79d96d5 <bar>
但是main()的地址就是其编译时确定的静态地址
(gdb) p main
$3 = {int ()} 0x40072d <main>
4.两者的关系
从/proc/pid/maps文件中可以找到动态库的加载地址,一般都会有多个输出,带有执行权限(x)的就是代码段的地址,也就是我们需要查看的地址。
[root@kino-chen ~]# cat /proc/27960/maps|grep libfoo.so
7ffff7bdb000-7ffff7bdc000 r-xp 00000000 fd:01 917518 /root/libfoo.so
[root@kino-chen ~]# cat /proc/27960/maps|grep libbar.so
7ffff79d9000-7ffff79da000 r-xp 00000000 fd:01 917529 /root/libbar.so
可以看到libfoo.so的加载地址是0x7ffff7bdb000,其函数foo()的运行地址是0x7ffff7bdb6d5 ,其差值是0x6d5,就是foo()在libfoo.so的静态地址。
综上可知,主程序中的函数其静态地址和运行时地址都是一样的,而动态库的中的函数其运行时地址是动态库加载地址+函数在动态库中的静态地址。
6.额外
gdb中可以查看到libfoo.so的地址是从0x00007ffff7bdb5f0开始的,并非0x7ffff7bdb000
(gdb) info shared
From To Syms Read Shared Object Library
0x00007ffff7dddaf0 0x00007ffff7df7520 Yes (*) /lib64/ld-linux-x86-64.so.2
0x00007ffff7bdb5f0 0x00007ffff7bdb6f8 Yes (*) ./libfoo.so
0x00007ffff79d95f0 0x00007ffff79d96f8 Yes (*) ./libbar.so
0x00007ffff76373b0 0x00007ffff777c11f Yes (*) /lib64/libc.so.6
查看libfoo.so,可以知道0x5f0是libfoo.so的代码段入口地址,加上动态库加载地址7ffff7bdb000,就是0x00007ffff7bdb5f0了,也就是说gdb的info shared
看到的是从代码段开始地址到结束地址。
[root@kino-chen ~]# readelf -a libfoo.so |grep 5f0
Entry point address: 0x5f0
[11] .text PROGBITS 00000000000005f0 000005f0
11: 00000000000005f0 0 SECTION LOCAL DEFAULT 11
27: 00000000000005f0 0 FUNC LOCAL DEFAULT 11 deregister_tm_clones