在汇编级别进行评估
eval at Assembly level
我开始学习非常基本的汇编语言,我了解到编译后的代码会进入一个名为 Code Segment
的特殊段,该段(至少在现代体系结构中)是一个写保护段。
但是一个问题跳出来了:在某些编程语言中(即:EcmaScript、Python等)有是神奇的 eval()
函数,它接受一个字符串,解析它然后执行它。
由于代码是在运行时评估的(然后在代码段被填充之后)并且代码段是写保护的,它执行了什么样的魔法?
我想它与 JIT 编译有关,但还没有关于它在低级别如何工作的线索。
我们以python为例。
Python 被解释(除非使用 pypi 或支持 JIT 的引擎,但即使在那里你也可以动态调用解释器)。当 运行 时,程序始终可以访问内置 python 可执行文件的解释器,当时是 运行(评估是 runtime 这是结果)
因此 eval
仅使用内置解释器计算表达式。
由于 python 意味着性能,所以在加载模块时将您的代码转换为字节码以节省文本解析时间(Java 例如在编译时执行),但真正的执行的机器指令包含在 python
可执行文件(解释字节码并执行适当的操作)或加载的 .pyd
DLL 文件中。
JIT 只是字节码之上的另一种优化:它在内存段中即时生成本机代码,但您无法轻松访问该段(就像您在 C 中使用函数地址一样) 因此很难从 python 程序中破解这段代码。
这在汇编语言或编译语言(C、C++、Ada...)中是不可能的(至少不容易),并不是因为代码段的写保护(无法保证),而仅仅是因为运行 程序无法 assemble/compile 代码:它 而不是 嵌入 compiler/assembler。运行时(如果存在)是最小的,当然不包含源代码评估。
最简单的方法是用您的程序创建一个临时文件,从您的程序中调用 compiler/assembler 并在单独的进程中执行它或动态加载 DLL,但这并不简单。
正如 Frank 所说,另一种可能的做法是在您的程序中创建一个虚拟机来评估机器代码指令,就像真正的 CPU 所做的那样(或像编译器那样的高级指令) ).不用说这不是微不足道的,但是一些已经存在的库可以做到这一点(例如 QEMU),即使有现有的 material,实现它也远非易事。
不同角度的回答...
"code segment"的不可写标志只是OS在加载可执行文件时所做的安排。在硬件级别上也没有什么可以阻止 OS 准备可写+可执行内存页,它只是成为一种方便的安全措施和对写保护内存页中 运行 可执行文件的错误预防。应用程序的创建者尊重这一点,不再使用可自我修改的代码(这是早期汇编编程中的常见做法)。 (除非他们正是为此目的从 OS 分配额外的内存,在那里写入并在之后执行)
此外,整个 "code segment" 是高级抽象,CPU 本身并不知道类似的事情。
(x86) CPU 只有当前权限级别和虚拟内存映射,所以它访问的任何内存地址,它都会通过虚拟映射定义转换为物理内存地址,同时检查它的权限内存 "page"(可读/可写)针对请求的操作。
如果访问无效,它将陷入错误处理程序,这通常是 OS 提供的。
应用程序是否在单独的内存页中加载代码和数据,甚至数据部分是否在可写和只读之间有细微的区别,这都取决于 OS 和应用程序加载器来设置它CPU 提供的简单 privilege/flag 内存映射机制的方法。如果你有自己的OS,你也可以将整个内存映射到一个大的未受保护的块中,每个人都可以读+执行+写。
我在 Python 中没有皮肤,但在嵌入式系统中是。在 PC 中,我假设操作系统 (Windows/Unix/Android/etc) 将通过 MMU(内存管理单元)为每个段保留物理内存区域,并为它们分配访问权限。要动态加载一个可执行程序以在之后执行它,正如 Python 可以做到的那样,应该为此目的预先声明一个具有 read/write/execute 权限的段。 Python 默认情况下应该这样做,但它不应该是 "Code Segment",因为你说它是只读的。也就是说,并非所有 "program code" 都进入同一段。
例如,在汇编代码中,可以修复将要预先定位在 code/data 片段上的位置,并在 "segment/section name" 上声明它。链接器将获取与项目中包含的文件同名的段,以顺序模式加入那些具有相同名称的文件,并将它们分配到由 "linker directive file"(有时是“.ld”扩展名)固定的地址文件)。
不同的编译器对段有命名约定(典型的是 "code"、"data"、"text"、"bss" 等)。每个人通常都有 read/write/execution 访问权限的属性。
我开始学习非常基本的汇编语言,我了解到编译后的代码会进入一个名为 Code Segment
的特殊段,该段(至少在现代体系结构中)是一个写保护段。
但是一个问题跳出来了:在某些编程语言中(即:EcmaScript、Python等)有是神奇的 eval()
函数,它接受一个字符串,解析它然后执行它。
由于代码是在运行时评估的(然后在代码段被填充之后)并且代码段是写保护的,它执行了什么样的魔法?
我想它与 JIT 编译有关,但还没有关于它在低级别如何工作的线索。
我们以python为例。
Python 被解释(除非使用 pypi 或支持 JIT 的引擎,但即使在那里你也可以动态调用解释器)。当 运行 时,程序始终可以访问内置 python 可执行文件的解释器,当时是 运行(评估是 runtime 这是结果)
因此 eval
仅使用内置解释器计算表达式。
由于 python 意味着性能,所以在加载模块时将您的代码转换为字节码以节省文本解析时间(Java 例如在编译时执行),但真正的执行的机器指令包含在 python
可执行文件(解释字节码并执行适当的操作)或加载的 .pyd
DLL 文件中。
JIT 只是字节码之上的另一种优化:它在内存段中即时生成本机代码,但您无法轻松访问该段(就像您在 C 中使用函数地址一样) 因此很难从 python 程序中破解这段代码。
这在汇编语言或编译语言(C、C++、Ada...)中是不可能的(至少不容易),并不是因为代码段的写保护(无法保证),而仅仅是因为运行 程序无法 assemble/compile 代码:它 而不是 嵌入 compiler/assembler。运行时(如果存在)是最小的,当然不包含源代码评估。
最简单的方法是用您的程序创建一个临时文件,从您的程序中调用 compiler/assembler 并在单独的进程中执行它或动态加载 DLL,但这并不简单。
正如 Frank 所说,另一种可能的做法是在您的程序中创建一个虚拟机来评估机器代码指令,就像真正的 CPU 所做的那样(或像编译器那样的高级指令) ).不用说这不是微不足道的,但是一些已经存在的库可以做到这一点(例如 QEMU),即使有现有的 material,实现它也远非易事。
不同角度的回答...
"code segment"的不可写标志只是OS在加载可执行文件时所做的安排。在硬件级别上也没有什么可以阻止 OS 准备可写+可执行内存页,它只是成为一种方便的安全措施和对写保护内存页中 运行 可执行文件的错误预防。应用程序的创建者尊重这一点,不再使用可自我修改的代码(这是早期汇编编程中的常见做法)。 (除非他们正是为此目的从 OS 分配额外的内存,在那里写入并在之后执行)
此外,整个 "code segment" 是高级抽象,CPU 本身并不知道类似的事情。
(x86) CPU 只有当前权限级别和虚拟内存映射,所以它访问的任何内存地址,它都会通过虚拟映射定义转换为物理内存地址,同时检查它的权限内存 "page"(可读/可写)针对请求的操作。
如果访问无效,它将陷入错误处理程序,这通常是 OS 提供的。
应用程序是否在单独的内存页中加载代码和数据,甚至数据部分是否在可写和只读之间有细微的区别,这都取决于 OS 和应用程序加载器来设置它CPU 提供的简单 privilege/flag 内存映射机制的方法。如果你有自己的OS,你也可以将整个内存映射到一个大的未受保护的块中,每个人都可以读+执行+写。
我在 Python 中没有皮肤,但在嵌入式系统中是。在 PC 中,我假设操作系统 (Windows/Unix/Android/etc) 将通过 MMU(内存管理单元)为每个段保留物理内存区域,并为它们分配访问权限。要动态加载一个可执行程序以在之后执行它,正如 Python 可以做到的那样,应该为此目的预先声明一个具有 read/write/execute 权限的段。 Python 默认情况下应该这样做,但它不应该是 "Code Segment",因为你说它是只读的。也就是说,并非所有 "program code" 都进入同一段。 例如,在汇编代码中,可以修复将要预先定位在 code/data 片段上的位置,并在 "segment/section name" 上声明它。链接器将获取与项目中包含的文件同名的段,以顺序模式加入那些具有相同名称的文件,并将它们分配到由 "linker directive file"(有时是“.ld”扩展名)固定的地址文件)。 不同的编译器对段有命名约定(典型的是 "code"、"data"、"text"、"bss" 等)。每个人通常都有 read/write/execution 访问权限的属性。