昨晚跟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
分享到:
相关推荐
在这个"对SQLite数据库增,删,改,查操纵的一个demo"中,我们将探讨如何利用C#进行基本的数据库操作。 首先,我们需要引入SQLite相关的库。在C#中,我们可以使用System.Data.SQLite库,这是一个官方的.NET框架...
总之,`DrawRect` Demo代码实例为我们提供了一个很好的平台,帮助我们学习和理解如何在iOS应用中使用`drawRect:`方法进行图形绘制,尤其是矩形的绘制。通过深入研究和实践,开发者可以创造出更多美观且功能丰富的...
- **Dexmaker**: Google提供的一款用于Android测试的库,它可以在运行时生成和注入Dex代码,实现对Dalvik虚拟机的静态Hook。 2. **动态Hook**: - **Xposed Framework**: 这是Android上最著名的动态Hook框架,它...
JavaScript是Web开发中的核心语言,它允许开发者在用户浏览器上运行代码,实现页面动态更新、事件处理和与服务器的实时通信等功能。在这个Demo中,JavaScript可能被用来处理游戏逻辑、动画效果、用户输入响应以及与...
《C语言求职招聘程序小Demo》是一个展示C语言综合运用能力的项目,它结合了指针、文件操作、键盘输入处理以及数据结构中的链表应用。这个项目旨在帮助求职者或者学习者提升C语言编程技能,同时也能为面试提供一个...
总结来说,"android拖动布局Demo"是一个关于在Android平台上实现可拖动布局的实例,主要利用`ViewDragHelper`进行视图拖放操作。它涵盖了触摸事件处理、边界检测以及可能的动画效果,对于想要增强自己应用交互性的...
"highlig_spring4"可能表示这是针对Spring 4.x版本的示例,Spring 4.x是一个重要的版本,引入了对Java 8的支持以及其他改进和新特性。 在Spring 4.x中,关键知识点包括: 1. **依赖注入(Dependency Injection, DI...
当目标类无法被继承(如final类)或者不希望修改源代码时,CGLib是一个很好的选择。它通过动态生成字节码生成目标类的子类,然后在子类中拦截并增强方法调用。 反射则是Java语言的一个强大特性,允许运行中的程序...
"Unity 地形Demo"是一个典型的示例项目,用于展示Unity引擎如何构建和操纵地形。在这个Demo中,我们可以学习到许多关于Unity地形系统的知识点。 首先,地形(Terrain)在Unity中是一个组件,可以通过“GameObject”...
这个Demo可能演示了如何创建一个全向滚动视图,允许用户通过操纵杆控制内容的平移,这对于理解和应用上述知识点非常有帮助。你可以通过分析和运行这个示例来更深入地了解操纵杆地图的实现细节。
例如,`requestAnimationFrame()`是一个高效的动画框架,它可以保证动画在浏览器重绘之前执行,从而得到流畅的视觉体验。 4. **CSS3变换和过渡**:JavaScript可以配合CSS3的transform和transition属性实现复杂的...
【标题】"jpct_ae的3D模型demo"是一个基于Java平台的3D图形库jpct_ae的实际应用展示,它着重展示了如何在应用程序中加载、操纵和交互3D模型。jpct_ae(Java Professional Computer Graphics - Advanced Edition)是...
在描述中,作者提到编写了一个DEMO,DEMO通常是指演示程序或示例代码,它提供了一个快速理解功能和工作原理的方式。作者可能通过这个DEMO展示了如何利用MATLAB与CST之间的接口进行交互,实现自动化建模和仿真过程。...
在Flash环境中,通常会有一个主文件(如`.fla`文件)包含了所有的源代码、图形和动画,而"PPL"可能是这个主文件的别名,或者是项目中某个关键组件的简称。如果"PPL"是一个单独的文件,那么它可能包含了游戏的特定...
在某些情况下,开发者可能需要在运行时动态地修改或注入代码到已存在的方法中,以实现特定功能,例如性能监控、日志记录或者行为调整。这就是所谓的“方法注入”或“拦截”。标题中的“c#方法注入替换,独立底层实现...
在文件名列表中,我们看到"Test_PNG",这很可能是DEMO中的一个特定文件,可能是一个源代码文件或者测试用的PNG图像。这个文件可能被用来展示如何在GDI+中加载和处理PNG图像,包括显示、裁剪、缩放、旋转等操作。 ...
这个demo很可爱的,是用鼠标和键盘来操纵观察一个小萝莉的3D立体模型。 【Visual C++】游戏开发笔记之【浅墨DirectX提高班】系列博文 配套详细注释源码之十 源码配套博文 《【Visual C++】游戏开发笔记四十二 浅墨...
浏览器Cookie获取插件Demo是一个用于演示如何在浏览器环境中获取指定URL的Cookie信息的示例项目。这个RAR压缩包中包含了实现这一功能的源代码和可能的文档,让我们深入了解一下相关知识点。 1. **浏览器插件**:...
在使用百度地图API时,为了防止滥用并确保服务稳定,开发者需要申请一个API密钥。这个DEMO中包含了密钥,意味着可以直接运行,但需要注意的是,公开的密钥可能会带来安全风险,因此在实际项目中,应当妥善保管自己的...
《笨笨鸟Demo源码》是一款网页小游戏的源代码,旨在提供一个学习和交流的平台。通过对这款小游戏的源码分析,我们可以深入了解HTML5、CSS3以及JavaScript等前端技术的应用,进一步提升我们的编程技能和游戏开发能力...