我开始研究非常基本的汇编语言,并且我已经了解到编译后的代码会进入一个名为Code Segment
的特殊段,这段(至少在现代架构中)是一个写保护段。
但是出现了一个问题:在一些编程语言中(例如: EcmaScript , Python 等),有一个神奇的eval()
函数需要一个string,解析它然后执行它。
由于代码是在运行时评估的(然后在填充代码段之后)并且代码段被写保护,它会出现什么样的魔法?
我认为它与JIT编译有关,但没有关于它如何在低级别工作的线索。
答案 0 :(得分:4)
让我们以python为例。
Python被解释(除非使用pypi或支持JIT的引擎,但即使在那里你也可以动态调用解释器)。运行时,程序始终可以访问内置python可执行文件的解释器,该解释器当时正在运行(评估是 runtime 的一部分,后续结果)
所以eval
只是使用内置解释器来计算表达式。
由于python意味着性能良好,因此在加载模块以保存文本解析时(例如Java在编译时执行)时,代码将转换为字节码,但执行的实际机器指令包含在{ {1}}可执行文件(解释字节码并执行适当的操作)或加载python
个DLL文件。
JIT只是字节码之上的另一个优化:它在内存段中即时生成本机代码,但您无法轻松访问此段(就像您在C中使用函数地址一样)因此,从python程序中破解这段代码是非常困难的。
这在组装或编译语言(C,C ++,Ada ......)中是不可能的(至少不容易),不是因为代码段的写保护(不能保证),而仅仅是因为无法正在运行的程序汇编/编译代码:它不嵌入编译器/汇编器。运行时(如果存在)是最小的,当然不包含源代码评估。
最简单的方法是使用您的程序创建一个临时文件,从程序中调用编译器/汇编程序并在单独的进程中执行它或动态加载DLL,但这并非易事。
另一件可能的事情就是Frank在程序中创建虚拟机来评估机器代码指令,就像真正的CPU那样(或编译器会做的高级指令)。毋庸置疑,这不是微不足道的,但是一些现有的图书馆就是这样做的(例如QEMU),即使使用现有材料,也很难实现它。
答案 1 :(得分:3)
从不同的角度回答......
“代码段”的不可写标志只是在加载可执行文件时由OS完成的。硬件级别上没有任何东西阻止操作系统准备可写的可执行内存页面,它只是一个方便的安全措施和防止在写保护的内存页面中运行可执行文件的错误。并且应用程序的创建者尊重并且不再使用可自我修改的代码(这是早期Assembly编程中的常见做法)。 (除非他们为此专门为OS分配额外的内存,然后在那里写入并执行它)
整个“代码段”也是高级抽象,CPU本身并不知道类似的东西。
(x86)CPU只有当前权限级别和虚拟内存映射,因此它所访问的任何内存地址,都会通过虚拟映射定义转换为物理内存地址,同时检查该内存“页面”的权限(可以-read / can-write)针对请求的操作。
如果访问无效,它将陷入错误处理程序,通常是操作系统提供的。
应用程序是否在单独的内存页面中加载了代码和数据,甚至数据部分在可写和只读之间都有很好的区别,所有这些都取决于操作系统和应用程序加载程序通过简单权限进行设置/由CPU提供的内存映射机制。如果你有自己的操作系统,你也可以将整个内存映射到一个没有保护的大块中,每个人都可以读取+执行+写入。
答案 2 :(得分:1)
我没有在Python中绘制皮肤,但在嵌入式系统中是的。在PC中,我认为操作系统(Windows / Unix / Android /等)将通过MMU(内存管理单元)物理内存区域保留到每个段并为其分配访问权限。要动态加载可执行程序以执行它,就像Python可以做的那样,它应该通过读/写/执行权限主要声明为此目的的段。 Python默认情况下应该这样做,但它不应该是#34; Code Segment",因为你说它是只读的。也就是说,并非所有"程序代码"进入同一部分。 例如,在汇编代码中,可以修复将在预定位于"段/段名称"上的代码/数据片段的位置。链接器将获取与项目中包含的文件名相同的段,以顺序模式连接具有相同名称的文件,并将它们分配给由"链接器指令文件" (有时是" .ld"扩展文件)。 不同的编译器具有段的名称约定(典型的是"代码","数据","文本"," bss"等) 。每个人通常都有读/写/执行访问权限的属性。