【问题标题】:eval at Assembly level组装级别的评估
【发布时间】:2016-10-15 08:27:07
【问题描述】:

我开始学习非常基本的汇编语言,我了解到编译后的代码进入一个名为Code Segment 的特殊段,它(至少在现代架构中)是一个写保护段。

但一个问题突然出现:在某些编程语言中(即:EcmaScriptPython 等)有一个神奇的eval() 函数,它接受一个字符串,解析它然后执行它。

由于代码在运行时被评估(然后在填充代码段之后)并且代码段被写保护,它会执行什么样的魔法?

我认为它与 JIT 编译有关,但没有关于它在低级别如何工作的线索。

【问题讨论】:

  • python 被解释(除非使用 pypi,但即使在那里你也可以动态调用解释器)。所以eval 只是使用内置解释器计算表达式。这在汇编或编译语言中是不可能的。
  • 不,看我的编辑。解释的代码是……解释的!字节码系统可以节省解析时间,但这是同一件事:解释。 JIT 在非只读段中即时生成本机代码,但您无法轻松访问该段(就像在 C 中一样),因此它相当安全。

标签: assembly eval


【解决方案1】:

我们以python为例。

Python 被解释(除非使用 pypi 或支持 JIT 的引擎,但即使在那里你也可以动态调用解释器)。运行时,程序始终可以访问当时正在运行的 python 可执行文件中内置的解释器(评估是 runtime 的一部分,这是随之而来的)

所以eval 只是使用内置解释器计算表达式。

由于 python 意味着高性能,因此在加载模块时将代码转换为字节码以节省文本解析时间(例如,Java 在编译时执行此操作),但执行的真实机器指令包含在 @ 987654322@ 可执行文件(解释字节码并执行适当的操作)或加载的.pyd DLL 文件。

JIT 只是字节码之上的另一种优化:它在内存段中即时生成本机代码,但您无法轻松访问该段(就像您在 C 中使用函数地址一样)所以从 python 程序中破解这段代码是非常困难的。

这在汇编或编译语言(C、C++、Ada...)中是不可能的(至少不容易),并不是因为代码段的写保护(不能保证),而仅仅是因为无法正在运行的程序来汇编/编译代码:它确实嵌入了编译器/汇编器。运行时(如果存在)是最小的,当然不包含源代码评估。

最简单的方法是使用您的程序创建一个临时文件,从您的程序中调用编译器/汇编程序并在单独的进程中执行它或动态加载 DLL,但这并非易事。

正如弗兰克所说,另一个可能的事情是在你的程序中创建一个虚拟机来评估机器代码指令,就像真正的 CPU 会做的那样(或者像编译器会做的高级指令)。不用说这不是微不足道的,但是一些已经存在的库可以做到这一点(例如 QEMU),即使使用现有的材料,实现它也远非易事。

【讨论】:

  • 感谢您的回答。请原谅我,但低级对我来说仍然很模糊。基本上,我们已经编译了字节码(在 Python 运行时,在 Java 编译时),它是汇编程序的等效项,并且通过 Pyton(或 JVM)执行。通过这种方式,用户输入的 eval 代码在运行时编译为动态加载(解释)的字节码?
  • @Fylax:当然,这些虚拟机并没有什么神奇之处。您只需要自己实现编译器。只需自己实现编译器和字节码解释器,或者直接生成机器码并将输出发送到分配有写入和执行权限的内存块中。当然,对于一种非平凡的编程语言来说,在汇编中执行此操作是一项繁琐的工作,但您可能需要对类似于图形计算器的一些东西进行概念验证,以便很容易地编译用于重复评估的函数。
  • 您上面的答案看起来好像您无法在汇编程序中编写动态解释器? 当然你可以,因为它只是一个数据驱动和评估,而不是机器代码生成和编译。
  • @FrankC。对于大多数普通人来说是不可能的:) 我已经编辑了我的帖子。
  • 谢谢大家。我有一张关于发生了什么的照片
【解决方案2】:

从不同的角度回答...

“代码段”的不可写标志只是操作系统在加载可执行文件期间所做的安排。在硬件层面也没有什么可以阻止操作系统准备可写+可执行的内存页面,在写保护的内存页面中运行可执行文件只是一种方便的安全措施和错误预防。并且应用程序的创建者尊重这一点并且不再使用可自我修改的代码(这是早期汇编编程中的常见做法)。 (除非他们专门为此目的从操作系统分配额外的内存,以便在此处写入并在之后执行)

整个“代码段”也是高级抽象,CPU 本身不知道这样的事情。

(x86) CPU 只有当前权限级别和虚拟内存映射,因此它访问的任何内存地址,都会通过虚拟映射定义转换为物理内存地址,同时检查该内存“页面”的权限(可以-read / can-write) 对请求的操作。

如果访问无效,它将陷入错误处理程序,通常是操作系统提供的。

无论是应用程序在单独的内存页面中加载代码和数据,还是数据部分有可写和只读的细微区别,都取决于操作系统和应用程序加载器通过简单权限的方式进行设置/flag CPU 提供的内存映射机制。如果你有自己的操作系统,你也可以将整个内存映射到一个不受保护的大块中,每个人都可以读+执行+写。

【讨论】:

  • 也很容易将JIT变成一个可写的页面,然后要求操作系统将其更改为没有写权限的读+执行。因此,即使在拒绝允许 W+X 页面的严格操作系统上,您仍然可以 JIT。但不是动态修改自身的自修改代码。
【解决方案3】:

我不是在 Python 中蒙皮,但在嵌入式系统中是的。在 PC 中,我认为操作系统(Windows/Unix/Android/etc)将通过 MMU(内存管理单元)为每个段保留物理内存区域并为其分配访问权限。为了动态加载可执行程序以在之后执行它,就像 Python 可以做的那样,它应该预先声明一个为此目的的具有读/写/执行权限的段。 Python默认应该这样做,但它不应该是“代码段”,因为你说它是只读的。也就是说,并非所有“程序代码”都进入同一段。 例如,在汇编代码中,可以修复在“段/节名称”上声明它的代码/数据片段的预定位位置。链接器将采用与项目中包含的文件同名的段以顺序模式连接那些具有相同名称的段,并将它们分配到由“链接器指令文件”(有时是“.ld”扩展名)固定的地址文件)。 不同的编译器对段有命名约定(典型的是“code”、“data”、“text”、“bss”等)。每个人通常都有其读/写/执行访问权限的属性。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2022-11-18
    • 1970-01-01
    • 1970-01-01
    • 2015-03-28
    • 1970-01-01
    • 1970-01-01
    • 2016-08-19
    • 2013-02-01
    相关资源
    最近更新 更多