我正在使用 Linux 机器,我想在运行时找出 Position-Independent-Code 共享库中符号的地址,现在我可以根据一些观察来实现,但是,我仍然对程序/库有一些疑问加载(是的,我知道怎么做,但我不知道为什么)。假设我们有以下两个 C 源文件:
// file: main.c
#include <stdio.h>
extern int global_field;
void main() {
printf("global field(%p) = %d\n", &global_field, global_field);
}
// file: lib.c
int global_field = 1;
我们用下面的命令编译上面的代码:
gcc -fPIC -g -c lib.c -o lib.o # note the -fPIC flag here
gcc -fPIC -g -c main.c -o main.o # note the -fPIC flag here
gcc -shared -o lib.so lib.o
gcc -o main main.o ./lib.so
并readelf -sW lib.so
显示global_field
符号:
Num: Value Size Type Bind Vis Ndx Name
...
8: 0000000000201028 4 OBJECT GLOBAL DEFAULT 21 global_field
...
并readelf -lW lib.so
输出以下程序头:
...
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
LOAD 0x000000 0x0000000000000000 0x0000000000000000 0x00065c 0x00065c R E 0x200000
LOAD 0x000df8 0x0000000000200df8 0x0000000000200df8 0x000234 0x000238 RW 0x200000
DYNAMIC 0x000e18 0x0000000000200e18 0x0000000000200e18 0x0001c0 0x0001c0 RW 0x8
NOTE 0x000190 0x0000000000000190 0x0000000000000190 0x000024 0x000024 R 0x4
GNU_STACK 0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RW 0x10
GNU_RELRO 0x000df8 0x0000000000200df8 0x0000000000200df8 0x000208 0x000208 R 0x1
现在我们运行程序,它输出以下内容:
global field(0x7ffff7dda028) = 1
并cat /proc/<pid>/maps
输出以下内容:
...
7ffff7bd9000-7ffff7bda000 r-xp 00000000 fd:02 18650951 /.../lib.so
7ffff7bda000-7ffff7dd9000 ---p 00001000 fd:02 18650951 /.../lib.so
7ffff7dd9000-7ffff7dda000 r--p 00000000 fd:02 18650951 /.../lib.so
7ffff7dda000-7ffff7ddb000 rw-p 00001000 fd:02 18650951 /.../lib.so
...
抱歉,这里的代码太多了......现在我的问题是:
如您所见,程序头中有两个
LOAD
段,但内存映射有四个,为什么还有两个映射?对于这两个
LOAD
段,如何确定哪个段映射到哪个内存区域?有没有标准或手册?但是,根据 ELF 标准,符号
global_field
的值是0000000000201028
(参见 的输出):readelf -sW lib.so
在可执行文件和共享对象文件中,
st_value
拥有一个虚拟地址。为了使这些文件的符号对运行时链接器更有用,节偏移(文件解释)让位于与节号无关的虚拟地址(内存解释)。
我知道这是与位置无关的代码,它不能是虚拟地址,并且必须是某种偏移量。global_field
用符号的值减去的地址: 0x7ffff7dda028 - 0x201028 = 0x7ffff7bd9000
,似乎偏移量是基于最低内存映射的起始地址(参见 的输出cat /proc/<pid>/maps
)。但是,是否有任何标准告诉我们,如何以编程方式检测符号的值类型(虚拟地址或偏移量)?如果它是一个偏移量,为什么偏移量应该基于它,为什么它不基于它自己的内存区域(我猜它自己的区域是最后一个,因为它有写权限)?