Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/jsf-2/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C 指令指针和函数指针的恒定性_C_Function Pointers - Fatal编程技术网

C 指令指针和函数指针的恒定性

C 指令指针和函数指针的恒定性,c,function-pointers,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"(

我有下面一段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;
}
#包括
#包括
void foo(int x){
printf(“foo看到%d\n”,x);
}
int main(int argc,char*argv[]){
uint64_t ip;
asm(“leaq(%%rip),%0;”:“=r”(ip));
printf(“rip是0x%016”PRIx64“\n”,ip);
无效(*fp)(int)=&foo;
printf(“foo位于偏移量%p\n”,fp);
(*fp)(10);
返回0;
}
问题1:为什么rip保持不变

问题2:如果二进制和机器保持不变,&foo会保持不变吗

问题3:何时可以更改&foo

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

Q1:

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

问题2:

别指望了。如果没有任何变化,您将具有确定性行为(相同的地址)。但是有很多很多事情可以改变(同样,取决于平台)

问题3:

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

底线

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

没有任何保证


解决方案是使用函数名而不是其地址编制索引(C99标准提供了
\uuuuu func\uuuu
标识符)。这样,在操作系统、编译器、选项和月球阶段的所有更改中,您的索引都将保持不变。在重构函数名之前,当然:-)

因为您使用的是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;
}
定义GNU源
#包括
#包括
void foo(){
}
int main(){
Dl_信息;
void*test=foo;//注意:不是标准的C
dladdr(测试和信息);
printf(“最近的符号:%s位于%s\n”,info.dli\u sname,info.dli\u fname);
返回0;
}
使用以下工具编译时:

gcc -Wall -Wextra test.c -ldl -rdynamic gcc-Wall-Wextra试验.c-ldl-rdynamic
正确地将
void*
标识为
foo
,无论在哪里加载
foo
,都是正确的。

谢谢!平台始终是Linux x86(32或64位)SMP机器。解决方案是否仍然太脆弱?您不应该期望将每个进程加载到自己的地址空间的系统每次都使用相同的地址。加载程序可能会将程序加载到不同的地址,这可能是针对恶意软件的不同防御措施。(一些恶意软件利用内存中的已知地址来利用缓冲区溢出,导致子例程返回到这些地址的分支。更改地址会阻止该操作。)@nandu在某些情况下,你可能会发现命令行参数和/或环境变量的数量会使事情发生变化。所有的优点都在评论中。事实上,一些加载程序出于各种原因随机分配地址,包括但不限于反恶意软件。根据操作系统的不同,环境变量也会占用额外的内存(我知道DOS分配了一个固定的缓冲区,但我认为现代操作系统更灵活)。他的操作系统上的加载器没有随机分配地址(根据观察到的行为),但我更新了完整性的答案。伟大的C99解决方案!假设不是C99,我需要将函数地址转换为函数名。我正在考虑使用backtrace\u symbols\u fd函数(man 3 backtrace)来实现这一点。您知道更好的替代方法吗?创建一个结构数组,将函数指针映射到相应的字符串(或者立即映射到数字索引!)。但是,由于您声明有一个Linux x86框,您可以依靠gcc或clang来支持
\uu func\uu