昨晚跟NS老兄聊的时候,聊到运行时生成代码的问题。单就“
在运行的时候制造出一块数据,让它被执行”而言,完全没难度可言——其实就是申请一块内存空间,往那里写入一些代表指令的数据,然后调用那块代码就行了。如果要生成的代码是固定的,或者是很有规律的(只是类似填空那样的),那很好办~
一般觉得动态生成代码难那主要是说按需要动态从某种形式的源代码生成出对应的可执行代码有难度。或者说编译器的后端不好写 T T
写了个极简陋的demo来演示怎么“在运行时生成代码”(其实没那么夸张……)
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
typedef int (*puts_ptr)(const char*);
typedef void (*myfunc_ptr)(puts_ptr);
/*
void foo(puts_ptr p) {
p("greetings from generated code!");
}
*/
/* corresponding assembler for generated code is like:
offset | bytes (in hex) | mnemonics
00 | 55 | push EBP
01 | 8BEC | mov EBP, ESP
03 | 68 ???????? | push ???????? ; address of string not known until run-time
08 | FF55 08 | call dword ptr [EBP + 8]
11 | 83C4 04 | add ESP, 4
14 | 5D | pop EBP
15 | C3 | ret
*/
int main() {
myfunc_ptr pMyfunc; /* pointer to generated code */
puts_ptr pPuts = &puts;
char* pCode = (char*)malloc(sizeof(char) * 50); /* allocate memory for code gen */
char* pGen = pCode; /* address to generate code to */
const char* pStr = "greetings from generated code!";
/* do code generation */
/* function prologue */
*pGen++ = 0x55; /* push EBP */
*pGen++ = 0x8B; *pGen++ = 0xEC; /* mov EBP, ESP */
/* function body */
*pGen++ = 0x68; /* push */
*((int*)pGen) = (int)pCode + 16; /* (address of string) */
pGen += 4;
*pGen++ = 0xFF; *pGen++ = 0x55; *pGen++ = 0x08; /* call dword ptr [EBP + 8] */
*pGen++ = 0x83; *pGen++ = 0xC4; *pGen++ = 0x04; /* add ESP, 4 */
/* function epilogue */
*pGen++ = 0x5D; /* pop EBP */
*pGen++ = 0xC3; /* ret */
/* make a copy of the string, following the generated code */
strcpy(pGen, pStr);
/* end of code generation */
/* invoke generated code */
pMyfunc = (myfunc_ptr)pCode;
pMyfunc(pPuts);
free(pCode);
return 0;
}
上面的代码所做的事情就是:在堆上申请一块空间,向里面填充一组指令,然后把这块空间当成一个函数,通过函数指针去调用刚生成的代码。生成的代码与它要显示的字符串“打包”在了一起。
所生成的函数内容如代码中注释所示,用C写的话大致等同于被注释掉的那个foo()。唯一不同的是:如果解除foo()的注释并编译的话,其中的"greetings from generated code!"字符串是直接编译到可执行文件的数据段,跟可执行代码所在的代码段不在一起;而生成的代码里,我是直接把这个字符串放在紧跟生成的可执行代码的后面。所以在第38行有个magic number,16,这个数字是生成的可执行代码的大小;我需要这个数字作为偏移量来计算出字符串在实际运行时的地址。
开头的push EBP和mov EBP, ESP,与末尾的pop EBP,这几条指令是用来管理栈帧指针(frame pointer,简称FP)的。在这里其实没什么实际用处,因为我没用到动态大小的栈帧;但我生成的代码并不是一个叶函数(它还调用了puts;叶函数是不调用别的函数的函数),而puts里还是用到了栈帧指针的,如果我不照例设置栈帧指针的话,整个过程就不太顺了……所以虽然没啥用还是写了这部分。
因为在32位Windows上,MSVCRT里puts的calling convention是
__cdecl,所以在调用了puts之后得自己清理栈,所以要生成那个add ESP, 4的指令。
在生成的代码里,设置好栈帧指针后,dword ptr [EBP]指向是就是老的FP的值,dword ptr [EBP+4]是函数的返回地址(由call指令压进来的),dword ptr [EBP+8]就是第一个参数(也是这个函数接收的唯一一个参数)。这也是遵循__cdecl的。
我用函数指针来调用生成的代码,是因为执行完那块代码后我还想让控制流回到main()里。如果不用回来的话,直接JMP也可以的。要JMP就得内嵌汇编了,直接用goto不行。
这个demo在32位Windows XP上用VC9和GCC 4.3编译运行都没问题,执行结果就是把问候语输出出来。但这其实作 弊了:在Windows XP上,
DEP(Data Execution Prevention)默认是不对一般应用程序启动的。因此我可以malloc得到堆上一块空间,然后通过call指令跳转到那里把它当作可执行代码来执行。如果DEP打开了的话,分配的空间必须标有PAGE_EXECUTE、PAGE_EXECUTE_READ、PAGE_EXECUTE_READWRITE或者PAGE_EXECUTE_WRITECOPY属性才可以执行;通过C++的
new、C的
malloc或者Win32 API的
HeapAlloc分配的内存空间都
不可执行。所以这个demo要是在Windows Vista或者Windows 7上运行就会出错。在这些平台上,可以通过
VirtualAlloc函数分配空间,只要设置要相应属性,还是可以申请到可执行的内存空间的。这种做法的好处是我自己调用VirtualAlloc的时候我肯定是准备要生成可信任的代码,那么执行不会有问题;如果别人想通过栈溢出之类的方法来hack我的程序,栈上的空间是不可执行的,他们就不能随意修改栈上内容当成代码来执行。
话说用
VirtualProtect能改变已提交的page的权限……可写可执行权限可以想办法搞过来~
要是NS老兄有兴趣的话我可以演示一下生成jump table的代码……如果你要自己来的话那我就不spoil the game了。
Have fun ^ ^
P.S. 借地放图。这是Windows XP SP2之后开始Performance对话框里新增的tab,可以设置DEP的应用范围。
P.S.S. (2009-09-07)刚才在Ubuntu 9.04下试一些代码的时候正好想起这个例子,也顺便试了下,果然能编译执行成功。我是不是应该换到有nx实现的CPU上再在Ubuntu里试试……?
Nikolay Igotti写过一篇稍微实用一些的同类小例子:
Simple JIT compiler for your application
这里还有一篇用Rust写的例子:
http://www.hydrocodedesign.com/2014/01/17/jit-just-in-time-compiler-rust/
- 大小: 31 KB
分享到:
相关推荐
微信小程序demo:商城(源代码+截图)微信小程序demo:商城(源代码+截图)微信小程序demo:商城(源代码+截图)微信小程序demo:商城(源代码+截图)微信小程序demo:商城(源代码+截图)微信小程序demo:商城(源代码+截图)...
微信小程序demo:简易抽奖(源代码+截图)微信小程序demo:简易抽奖(源代码+截图)微信小程序demo:简易抽奖(源代码+截图)微信小程序demo:简易抽奖(源代码+截图)微信小程序demo:简易抽奖(源代码+截图)微信小程序demo...
android 串口测试demo 代码简单 无bug 完美运行 android 串口测试demo 代码简单 无bug 完美运行 android 串口测试demo 代码简单 无bug 完美运行 android 串口测试demo 代码简单 无bug 完美运行 android 串口测试demo...
微信小程序demo:商城分销系统(源代码+截图)微信小程序demo:商城分销系统(源代码+截图)微信小程序demo:商城分销系统(源代码+截图)微信小程序demo:商城分销系统(源代码+截图)微信小程序demo:商城分销系统(源代码+...
微信小程序demo:页面框架(源代码+截图)微信小程序demo:页面框架(源代码+截图)微信小程序demo:页面框架(源代码+截图)微信小程序demo:页面框架(源代码+截图)微信小程序demo:页面框架(源代码+截图)微信小程序demo...
微信小程序demo:花店(源代码+截图)微信小程序demo:花店(源代码+截图)微信小程序demo:花店(源代码+截图)微信小程序demo:花店(源代码+截图)微信小程序demo:花店(源代码+截图)微信小程序demo:花店(源代码+截图)...
微信小程序demo:在线聊天功能(源代码+截图)微信小程序demo:在线聊天功能(源代码+截图)微信小程序demo:在线聊天功能(源代码+截图)微信小程序demo:在线聊天功能(源代码+截图)微信小程序demo:在线聊天功能(源代码+...
微信小程序demo组件:canvas股票分时图(源代码+截图)微信小程序demo组件:canvas股票分时图(源代码+截图)微信小程序demo组件:canvas股票分时图(源代码+截图)微信小程序demo组件:canvas股票分时图(源代码+截图)微信...
微信小程序demo:逗乐(源代码+截图)微信小程序demo:逗乐(源代码+截图)微信小程序demo:逗乐(源代码+截图)微信小程序demo:逗乐(源代码+截图)微信小程序demo:逗乐(源代码+截图)微信小程序demo:逗乐(源代码+截图)...
微信小程序demo:人脸识别 (2)(源代码+截图)微信小程序demo:人脸识别 (2)(源代码+截图)微信小程序demo:人脸识别 (2)(源代码+截图)微信小程序demo:人脸识别 (2)(源代码+截图)微信小程序demo:人脸识别 (2)(源代码+...
小程序源码 移动端小商城DEMO (商城demo源码) (代码源)小程序源码 移动端小商城DEMO (商城demo源码) (代码源)小程序源码 移动端小商城DEMO (商城demo源码) (代码源)小程序源码 移动端小商城DEMO (商城demo源码) ...
适用1028版本(源代码+截图)微信小程序demo:智能机器人;适用1028版本(源代码+截图)微信小程序demo:智能机器人;适用1028版本(源代码+截图)微信小程序demo:智能机器人;适用1028版本(源代码+截图)微信小程序demo:...
小程序源码 简单小商城模板 (商城demo源码) (代码源)小程序源码 简单小商城模板 (商城demo源码) (代码源)小程序源码 简单小商城模板 (商城demo源码) (代码源)小程序源码 简单小商城模板 (商城demo源码) (代码源)小...
微信小程序经典demo学习案例:学习用demo(源代码+截图)微信小程序经典demo学习案例:学习用demo(源代码+截图)微信小程序经典demo学习案例:学习用demo(源代码+截图)微信小程序经典demo学习案例:学习用demo(源代码+...
微信小程序 商城模板 京东首页demo (源代码+截图)微信小程序 商城模板 京东首页demo (源代码+截图)微信小程序 商城模板 京东首页demo (源代码+截图)微信小程序 商城模板 京东首页demo (源代码+截图)微信小...
小程序源码 医药类 (商城demo源码) (代码源)小程序源码 医药类 (商城demo源码) (代码源)小程序源码 医药类 (商城demo源码) (代码源)小程序源码 医药类 (商城demo源码) (代码源)小程序源码 医药类 (商城demo源码) ...
微信小程序学习demo:基础接口演示demo(源代码+截图)微信小程序学习demo:基础接口演示demo(源代码+截图)微信小程序学习demo:基础接口演示demo(源代码+截图)微信小程序学习demo:基础接口演示demo(源代码+截图)微信...
微信小程序服务端开发demo(源代码+截图)微信小程序服务端开发demo(源代码+截图)微信小程序服务端开发demo(源代码+截图)微信小程序服务端开发demo(源代码+截图)微信小程序服务端开发demo(源代码+截图)微信小程序...
微信小程序demo:计算器(源代码+截图)微信小程序demo:计算器(源代码+截图)微信小程序demo:计算器(源代码+截图)微信小程序demo:计算器(源代码+截图)微信小程序demo:计算器(源代码+截图)微信小程序demo:计算器(源...
使用百度地图API(源代码+截图)微信小程序demo:精品天气预报;使用百度地图API(源代码+截图)微信小程序demo:精品天气预报;使用百度地图API(源代码+截图)微信小程序demo:精品天气预报;使用百度地图API(源代码+...