2

我有以下一段 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:如果二进制文件和机器保持不变,&foo 会保持不变吗?

Q3: &foo 什么时候可以改变?

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

4

3 回答 3

2

Q1:

取决于你的平台。某些平台将您的程序加载到虚拟地址空间中,因此完全相同的代码将具有完全相同的foo虚拟地址(假设程序和操作系统的加载器在运行之间不会改变,并且加载器不是随机化的根据评论加载地址)。在不将您的可执行文件加载到虚拟地址空间的其他平台上,您可能会或可能不会获得相同的地址,具体取决于其他程序是否已执行和/或在运行之间终止。

Q2:

不要指望它。如果根本没有任何变化,您将具有确定性行为(相同地址)。但是有很多很多东西可以改变(同样,取决于平台)。

问题 3:

它们可以在不分配虚拟地址的平台上随时更改(因为其他进程开始/继续工作/终止)。在确实分配虚拟地址的平台上,如果您的程序或相关库发生变化,如果有一个操作系统补丁改变了加载程序行为,或者可能是由于我目前没有想到的其他情况,它们的地址可能会改变.

底线

存储地址可能适用于您非常具体的情况,但这是一个脆弱的解决方案。

于 2012-07-30T14:49:58.383 回答
1

没有什么是保证的。

解决方案是使用函数进行索引,而不是它的地址(C99 标准提供了__func__标识符)。这样可以保证您的索引在操作系统、编译器、选项和月相的所有更改中保持不变。直到你重构函数名,当然:-)

于 2012-07-30T14:55:17.967 回答
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*as foo,无论在哪里foo加载都是正确的。

于 2012-07-30T15:16:09.480 回答