我有以下一段C代码,它打印 rip 寄存器和函数foo的地址。多次运行可执行文件会导致打印相同的 rip 和& foo值。
#include <stdio.h>
#include <inttypes.h>
void foo(int x) {
printf("foo sees %d\n", x);
}
int main(int argc, char *argv[]) {
uint64_t ip;
asm("leaq (%%rip), %0;": "=r"(ip));
printf("rip is 0x%016" PRIx64 "\n", ip);
void (*fp)(int) = &foo;
printf("foo is at offset %p\n", fp);
(*fp)(10);
return 0;
}
Q1:为什么rip保持不变?
Q2:如果二进制和机器保持不变,那么&amp; foo会保持不变吗?
问题3:什么时候可以改变?
背景:我试图将函数的执行时间存储在历史表中。我正在考虑使用函数地址索引到表中并计算与先前执行的偏差。
答案 0 :(得分:2)
Q1:
取决于您的平台。有些平台将程序加载到虚拟地址空间,因此完全相同的代码将具有与 foo 完全相同的虚拟地址(假设程序和OS的加载程序在运行和加载程序之间不会更改)不是根据评论随机化加载地址的那个)。在其他未将可执行文件加载到虚拟地址空间的平台上,根据其他程序是否在运行之间执行和/或终止,您可能会也可能不会获得相同的地址。
Q2:
不要指望它。如果 nothing 完全没有变化,您将拥有确定性行为(相同的地址)。但是有许多事情可以改变(再次,取决于平台)。
Q3:
他们可以在不分配虚拟地址的平台上随时更改(因为其他进程开始/继续进行工作/终止)。在一个分配虚拟地址的平台上,如果你的程序或相关库发生了变化,如果有一个操作系统补丁改变了加载程序的行为,或者可能是由于我目前没想到的其他情况,它们的地址可能会发生变化
底线
存储地址可能适合您的具体案例,但这是一个脆弱的解决方案。
答案 1 :(得分:1)
没有任何保证。
解决方案是使用函数 name 进行索引,而不是使用其地址(C99标准提供__func__
标识符)。这样,您的索引可以保证在操作系统,编译器,选项和月相的所有更改中保持相同。在你重构函数名之前,当然: - )
答案 2 :(得分:0)
由于您使用的是Linux,因此可以使用dladdr()
询问内存中位置附近的符号。例如:
#define _GNU_SOURCE
#include <dlfcn.h>
#include <stdio.h>
void foo() {
}
int main() {
Dl_info info;
void *test = foo; // Note: not standard C
dladdr(test, &info);
printf("closest symbol: %s in %s\n", info.dli_sname, info.dli_fname);
return 0;
}
编译时使用:
gcc -Wall -Wextra test.c -ldl -rdynamic
正确地将void*
标识为foo
,无论在何处foo
加载,这都是正确的。