Windows平台C++获取函数地址的异常问题排查
问题分析与解决办法
为啥VS编辑器显示的函数地址和运行时打印的不一样?
这十有八九是**ASLR(地址空间布局随机化)**在作祟:
- VS里光标悬停、反视图显示的都是链接器生成的「静态虚拟基地址」——说白了就是程序还没加载到内存时,理论上应该在的地址。
- 但Windows默认开了ASLR,程序一运行就会被加载到随机分配的内存地址上,这时候函数的实际运行地址是静态基地址加上一个随机偏移量,自然和编辑器里显示的对不上。
- 你已经关了优化和内联,还加了
noinline,这确实保证了gio是个独立的代码块,但ASLR的影响是绕不开的。
如何正确拿到能用来memcpy的函数地址?
你打印出来的0x00d213ca其实就是运行时的正确函数入口地址,完全可以直接用。但这里有几个坑得注意:
1. 搞清楚函数的大小(最头疼的问题)
C标准里根本没有提供获取函数代码长度的方法,随便用memcpy复制很容易翻车:
- 复制少了,函数代码不完整,运行必崩;
- 复制多了,会蹭到旁边的代码或数据,同样是未定义行为。
如果只是做测试,有几个野路子可以试试:
- 在
gio函数后面加个空函数当标记:
然后用__declspec(noinline) void gio() { // 你的函数逻辑 } void gio_end() {} // 用来标记gio的结束位置gio_end - gio来近似函数大小,但编译器可能会在函数之间插填充字节,结果不一定准。 - 看VS生成的.map文件:链接时生成的.map文件里会有每个函数的起始地址和大小,你可以从中找到
gio的大小然后硬编码到代码里——但这只对当前编译版本有效,重新编译后就可能变了。
2. 当心函数里的重定位代码
就算关了优化,编译器也可能生成带重定位信息的代码(比如访问全局变量的时候)。把函数复制到新缓冲区后,这些重定位的地址就失效了,运行起来肯定崩。
只有当你的函数是纯纯的自包含代码(不访问全局变量、不调用其他函数),复制后才有可能正常运行,只要有外部依赖就会出问题。
3. 一个测试用的memcpy示例
假设你已经通过.map文件查到gio的大小是0x50字节,代码可以这么写:
#include <stdio.h> #include <string.h> #include <windows.h> __declspec(noinline) void gio() { printf("Hello from gio!\n"); } int main() { // 打印运行时的函数地址 printf("gio runtime address: %p\n", gio); // 分配一块带可执行权限的内存(必须开执行权限,不然运行复制的函数会触发内存保护) char* func_buf = (char*)VirtualAlloc(NULL, 0x100, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); if (!func_buf) { printf("VirtualAlloc failed!\n"); return 1; } // 把gio的代码复制到缓冲区 memcpy(func_buf, gio, 0x50); // 转换成函数指针并调用 void (*copied_gio)() = (void(*)())func_buf; copied_gio(); // 释放内存 VirtualFree(func_buf, 0, MEM_RELEASE); return 0; }
最后总结一下
- 你打印的地址是对的,VS显示的是静态链接地址,差异来自ASLR;
- 复制函数到缓冲区最大的难点是获取准确的函数大小,以及处理重定位问题;
- 这种操作属于底层hack,只适合学习测试,千万别用到生产环境里。
内容的提问来源于stack exchange,提问作者Ojs
相关产品推荐
相关产品推荐

