我试图将一个函数复制到一个可执行页面并从那里运行它,但我似乎遇到了一些问题。 这是我的代码:
#include <stdio.h>
#include <string.h>
#include <windows.h>
int foo()
{
return 4;
}
int goo()
{
return 5;
}
int main()
{
int foosize = (int)&goo-(int)&foo;
char* buf = VirtualAlloc(NULL, foosize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if (buf == NULL)
{
printf("Failed\n");
return 1;
}
printf("foo %x goo %x size foo %d\n", &foo, &goo, foosize);
memcpy (buf, (void*)&foo, foosize);
int(*f)() = &foo;
int ret1 = f();
printf("ret 1 %d\n", ret1);
int(*f2)() = (int(*)())&buf;
int ret2 = f2 (); // <-- crashes here
printf("ret2 %d\n", ret2);
return 0;
}
我知道一些代码在技术上是UB((int)&amp; goo-(int)&amp; foo),但在这种情况下它表现得很好。
我的问题是为什么这不按预期工作? 在我看来,我将页面映射为可执行文件并在那里复制了现有函数,我只是调用它。
我缺少什么?
使用mmap在linux上的表现会有所不同吗? 提前致谢
答案 0 :(得分:2)
正如每个人在评论中已经说过的那样,这是完全未定义的行为,并且绝不应该真正期望工作。但是,我使用调试器对你的代码进行了一些操作,并意识到它不起作用的原因(至少在Cygwin gcc编译器中)你是否正在创建f2
错误地指向地址指针存储分配的内存,即buf
。您想指向buf
指向的内存。因此,您的作业应该是
int(*f2)() = (int(*)())buf;
通过该更改,您的代码将为我执行。但是,即使它有效,只要对程序进行任何其他更改,它就可能再次中断。
答案 1 :(得分:1)
我在调试模式下尝试使用MVSC 2008的代码。编译器碰巧创建了一个带有相对偏移的jmp表,而&foo
和&goo
只是该表中的条目。
所以,即使你已经成功创建了一个可执行缓冲区并复制了代码(远远超过了有用的......),相对跳转现在指向一个不同的位置,并且(在我的例子中)很快落入{{1}陷阱!
TL / DR:由于编译器可以随意排列其代码,并且尽可能多的跳转使用相对偏移量,因此您不能依赖于复制可执行代码。它实际上是未定义的行为:
如果编译器足够聪明,只能生成如下内容:
int 3
它可以有效
如果编译器生成了具有相对跳转的更复杂的代码,它就会中断
结论:如果您完全控制二进制机器代码,则只能复制可执行代码,例如,如果您使用汇编代码并且知道您没有重定位问题
答案 2 :(得分:1)
您需要将foo
和goo
声明为static
,否则必须停用Incremental Linking。
增量链接用于缩短构建应用程序时的链接时间,正常链接和增量链接的可执行文件之间的区别在于,在逐步链接的可执行文件中,每个函数调用都会通过链接器发出的额外JMP
指令。 / p>
这些JMP允许链接器在内存中移动函数,而不更新引用该函数的所有CALL
指令。但正是这种JMP导致了你的问题。将函数声明为static
会阻止链接器创建此额外的JMP
指令。