Osheep

时光不回头,当下最重要。

函数静态地址和运行时地址

使用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
点赞