指令指针和函数指针常量

时间:2012-07-30 14:44:50

标签: c function-pointers

我有以下一段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:什么时候可以改变?

背景:我试图将函数的执行时间存储在历史表中。我正在考虑使用函数地址索引到表中并计算与先前执行的偏差。

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加载,这都是正确的。