如何编写在现代 x64 处理器上高效运行的自修改代码?

2024-03-30

我正在尝试加速可变位宽整数压缩方案,并且我对动态生成和执行汇编代码感兴趣。目前,大量时间花费在错误预测的间接分支上,并且根据发现的一系列位宽生成代码似乎是避免这种损失的唯一方法。

一般技术被称为“子例程线程”(或“调用线程”,尽管这也有其他定义)。目标是利用处理器有效的调用/返回预测以避免停顿。该方法在这里得到了很好的描述:http://webdocs.cs.ualberta.ca/~amaral/cascon/CDP05/slides/CDP05-berndl.pdf http://webdocs.cs.ualberta.ca/%7Eamaral/cascon/CDP05/slides/CDP05-berndl.pdf

生成的代码将只是一系列调用,然后返回。如果有 5 个宽度“块”[4,8,8,4,16],它看起来像:

call $decode_4
call $decode_8
call $decode_8
call $decode_4
call $decode_16
ret

在实际使用中,这将是一个较长的调用系列,具有足够的长度,使得每个系列可能是唯一的并且仅调用一次。生成和调用代码在这里和其他地方都有详细记录。但除了简单的“不要这样做”或经过深思熟虑的“有龙”之外,我还没有发现太多关于效率的讨论。即便是英特尔文档 http://download.intel.com/products/processor/manual/325462.pdf主要是笼统地说:

8.1.3 处理自修改和交叉修改代码

处理器将数据写入当前正在执行的代码的行为 旨在将数据作为代码执行的段被调用 自修改代码。 IA-32 处理器表现出特定于型号的行为 执行自修改代码时,取决于提前多远 当前执行指针代码已被修改。 ... 自修改代码的执行性能水平将低于 非自修改或正常代码。表现程度 恶化将取决于修改的频率和 代码的具体特征。

11.6 自修改代码

对当前代码段中的内存位置进行写入 缓存在处理器中会导致关联的缓存行(或多行) 被宣告无效。该检查基于物理地址 操作说明。此外,P6 系列和 Pentium 处理器检查 对代码段的写入是否可以修改具有 已预取以供执行。如果写入影响预取 指令,预取队列无效。后一项检查是 基于指令的线性地址。对于奔腾 4 和 Intel Xeon 处理器,代码中指令的写入或监听 段,目标指令已解码并驻留在其中 在跟踪缓存中,使整个跟踪缓存无效。后者 行为意味着自修改代码的程序可能会导致严重的后果 在 Pentium 4 和 Intel Xeon 上运行时性能下降 处理器。

虽然有一个性能计数器可以确定是否发生了不好的事情(C3 04 MACHINE_CLEARS.SMC:检测到的自修改代码机器清除数)我想了解更多细节,特别是 Haswell。我的印象是,只要我能够提前足够远的时间编写生成的代码,以便指令预取尚未到达那里,并且只要我不通过修改同一页面上的代码来触发 SMC 检测器(四分之一)页?)作为当前正在执行的任何内容,那么我应该获得良好的性能。但所有细节似乎都极其模糊:多近才算太近?多远才算足够远?

尝试将这些问题变成具体的问题:

  1. 当前指令之前的最大距离是多少 Haswell 预取器运行过吗?

  2. 当前指令后面的最大距离是多少 Haswell“跟踪缓存”可能包含什么?

  3. MACHINE_CLEARS.SMC 事件的实际周期损失是多少 哈斯韦尔?

  4. 如何在预测循环中运行生成/执行周期 防止预取器吃掉自己的尾巴?

  5. 我怎样才能安排流程,以便每一段生成的代码都是 总是“第一次见到”,不按指示操作 已经缓存了?


这不一定是自修改代码完全可以 - 可以动态创建的代码相反,即运行时生成的“蹦床”。

这意味着您保留一个(全局)函数指针,它将重定向到内存的可写/可执行映射部分 - 然后您可以在其中主动插入您想要进行的函数调用。

这样做的主要困难是call是与 IP 相关的(大多数jmp),这样您就必须计算蹦床的内存位置和“目标函数”之间的偏移量。这本身就很简单 - 但是将其与 64 位代码结合起来,您就会遇到相对位移call只能处理+-2GB范围内的位移,它变得更加复杂 - 你需要通过链接表进行调用。

所以你本质上会创建这样的代码(/me 严重 UN*X 偏见,因此 AT&T 汇编,以及一些对 ELF-isms 的引用):

.Lstart_of_modifyable_section:
callq 0f
callq 1f
callq 2f
callq 3f
callq 4f
....
ret
.align 32
0:        jmpq tgt0
.align 32
1:        jmpq tgt1
.align 32
2:        jmpq tgt2
.align 32
3:        jmpq tgt3
.align 32
4:        jmpq tgt4
.align 32
...

这可以在编译时创建(只需创建一个可写文本部分),也可以在运行时动态创建。

然后,您在运行时修补跳跃目标。这类似于.pltELF 部分(PLT = 过程链接表)有效 - 只是在那里,它是修补 jmp 插槽的动态链接器,而在您的情况下,您自己执行此操作。

如果您选择所有运行时,那么甚至可以通过 C/C++ 轻松创建上面这样的表;从数据结构开始,例如:

typedef struct call_tbl_entry __attribute__(("packed")) {
    uint8_t call_opcode;
    int32_t call_displacement;
};
typedef union jmp_tbl_entry_t {
    uint8_t cacheline[32];
    struct {
        uint8_t jmp_opcode[2];    // 64bit absolute jump
        uint64_t jmp_tgtaddress;
    } tbl __attribute__(("packed"));
}

struct mytbl {
    struct call_tbl_entry calltbl[NUM_CALL_SLOTS];
    uint8_t ret_opcode;
    union jmp_tbl_entry jmptbl[NUM_CALL_SLOTS];
}

这里唯一关键且有点依赖于系统的事情是它的“打包”性质,需要告诉编译器(即不要填充callarray out),并且应该对跳转表进行缓存行对齐。

你需要做calltbl[i].call_displacement = (int32_t)(&jmptbl[i]-&calltbl[i+1]),初始化空/未使用的跳转表memset(&jmptbl, 0xC3 /* RET */, sizeof(jmptbl))然后根据需要填写跳转操作码和目标地址的字段。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何编写在现代 x64 处理器上高效运行的自修改代码? 的相关文章

  • .NET File.Exists 在 Windows\System32\Drivers 文件夹中不起作用?

    该过程已提升 并且我确保 VS 调试器中的路径是正确的 我使用 Environment GetFolderPath Environment SpecialFolder System 而不是对其进行硬编码 但 File Exists 仍然返回
  • 装配中出现奇怪的字符?

    我写了以下代码 386 model small stack 100h data text db Paper 0 code start lea dx text mov ah 9h int 21h mov ah 4ch int 21h end
  • 如何在 Windows 8 上安装 sqlite 或 postgresql 以进行 ruby​​ on Rails 设置?

    我一直在尝试安装数据库作为 ruby on Rails 设置的一部分 我正在运行 64 位 Windows 8 基于 x64 的计算机 我的ruby版本是2 1 3p242 rails版本是4 0 0 sqlite3版本是3 8 6 pos
  • 什么时候汇编比C更快? [关闭]

    Closed 这个问题需要多问focused help closed questions 目前不接受答案 Locked 这个问题及其答案是locked help locked posts因为这个问题是题外话 但却具有历史意义 目前不接受新的
  • 为什么 Solaris 汇编器生成的机器代码与 GNU 汇编器在这里不同?

    我为 amd64 编写了这个小汇编文件 对于这个问题来说 代码的作用并不重要 globl fib fib mov edi ecx xor eax eax jrcxz 1f lea 1 rax ebx 0 add rbx rax xchg r
  • NASM 轮班操作员

    您将如何在寄存器上进行 NASM 中的位移位 我读了手册 它似乎只提到了这些操作员 gt gt lt lt 当我尝试使用它们时 NASM 抱怨移位运算符处理标量值 您能解释什么是标量值并举例说明如何使用 gt gt and lt lt 另外
  • Visual Studio 2017 上的简单装配程序

    386 model flat c stack 100h printf PROTO arg1 Ptr Byte data msg1 byte Hello World 0Ah 0 code main proc INVOKE printf ADD
  • 汇编基础知识:输出寄存器值

    我刚刚开始学习汇编语言 我已经陷入了 在屏幕上显示存储在寄存器中的十进制值 的部分 我使用 emu8086 任何帮助将不胜感激 model small Specifies the memory model used for program
  • 减法进位标志

    我正在使用 MASM32 有了这个代码 mov eax 5 sub eax 10 CF 状态标志将被设置 但使用我的铅笔和纸 我实际上看到 MSB 没有任何进位 是的 我知道从较少的数字中减去大的数字集CF 但我想知道为什么 因为使用这段代
  • 从汇编程序获取命令行参数

    通读 专业汇编语言书籍 似乎它提供了用于读取命令行参数的错误代码 我纠正了一点 现在它从段错误变成了读取参数计数 然后是段错误 这是完整的代码 data output1 asciz There are d params n output2
  • 两个基本的 ANTLR 问题

    我正在尝试使用 ANTLR 来获取简单的语法并生成汇编输出 我在 ANTLR 中选择的语言是 Python 许多教程看起来非常复杂或详细阐述与我无关的事情 我真的只需要一些非常简单的功能 所以我有两个问题 将值从一个规则 返回 到另一规则
  • 为什么当大小大于 50 时,该程序花费的时间会呈指数级增长?

    所以我正在为类编写一个 ARM 汇编快速排序方法 我对大部分内容都有了解 除了复杂性没有意义 我们将其与我们制作的另一种冒泡排序方法进行比较 它对于具有 1 个参数和 10 个参数的示例表现更好 然而 我什至无法比较 100 个参数测试 因
  • directshow.net 视频输入设备过滤器枚举是否已损坏(在较新的环境中)?

    directshow net 示例文件夹 标记为 2010 February 中的每个示例都可以正常编译 并且那些从文件播放视频的示例效果也很好 但是 每个尝试枚举 FilterCategory VideoInputDevices 的示例始
  • Nasm 打印到下一行

    我用 nasm Assembly 编写了以下程序 section text global start start Input variables mov edx inLen mov ecx inMsg mov ebx 1 mov eax 4
  • 为什么在强度降低乘法和循环进位加法之后,这段代码的执行速度会变慢?

    我正在读书阿格纳 雾 https en wikipedia org wiki Agner Fog s 优化手册 https en wikipedia org wiki Agner Fog Optimization 我遇到了这个例子 doub
  • 寄存器寻址模式与直接寻址模式

    我在试卷中遇到过这个问题 它指出 哪种给定的寻址模式更快 为什么 寄存器寻址方式 直接寻址方式 现在根据我的说法 寄存器寻址模式应该更快 因为寄存器是计算机中最快的存储位置 这是正确答案吗 请帮忙 谢谢 两种寻址模式之间的区别是 地址的来源
  • “rep stos”x86 汇编指令序列有什么作用?

    我最近偶然发现了以下汇编指令序列 rep stos dword ptr edi For ecx重复 存储内容eax到哪里edi指向 递增或递减edi 取决于方向标志 每次 4 个字节 通常 这用于memset型操作 通常 该指令简单地写成r
  • 从 NASM 调用 C 函数 _printf 会导致分段错误

    我一直在尝试使用 NASM 在 Mac OS 和 Windows 上学习 64 位汇编 我的代码是 extern printf section data msg db Hello World 10 0 section text global
  • 如何在命令行中使用 Visual Studio 编译 x64 代码?

    我想使用 Windows 命令行编译一个简单的 hello world 风格的程序 cl file name c 很容易 现在我想在 64 位中做同样的事情 我应该怎么办 您需要使用发出 x64 代码的 cl exe 编译器版本 哪一个有点
  • 如何在 Debian 上编译 DOS 程序?

    在我的汇编语言课程中 我们使用 DPMI 编写 DOS 程序 不幸的是 我无法一直使用 32 位 Windows 机器 我在我使用的几乎每台计算机上都安装了 Debian 虚拟机 我已经安装了 DOSBox 和 DOSEMU 有什么办法可以

随机推荐

  • Ruby Rails - 为 AJAX 调用控制器操作构建数据

    我的网站上需要一个按钮 可以将信息发送到控制器的创建操作 页面时间 它似乎有效 尽管它没有发送我指定的所有数据 可能与我无法构建数据向量有关 我已通过以下方式在我的 config routes rb 文件中提供 POST 请求post pa
  • Admob实施错误

    我在应用程序中实施 Admob 时遇到问题 这是我的 main xml
  • Java 在 ^(插入符号?)上分割不起作用,这是一个特殊字符吗?

    在Java中 我试图拆分 性格 但它无法识别它 逃跑 抛出代码错误 这是一个特殊字符还是我需要做其他事情才能让它识别它 String splitChr String fmgStrng aryToSplit split splitChr Th
  • `a = b || 的语法糖a`, `a = b && a`

    For a a b a a b 有很好的语法糖a b a b 同样经常出现的还有 a b a a b a 有没有好的语法糖或简短的方法来编写这个 a b a 可以重写为 a b if b 可以重写而无需重复a or b as x b and
  • 强制 MATLAB mmreader 或 avireader 使用不同的编解码器?

    当我需要播放 AVI 文件时 如何强制 MATLAB 函数使用不同的编解码器 我使用的是 windows7 我发现 indeo5 编解码器由于操作系统的原因无法正常工作 所有代码在 XP 上都可以正常运行 Thanks 如果您的问题是如何使
  • Rails 3 自定义操作表单

    我在 Rails 3 中将表单路由到自定义操作时遇到问题 以下是我的路由 resources photos do resources comments collection do get update states end member d
  • Delphi:解析未知结构的记录

    有没有一种方法可以获取记录并以编程方式遍历其所有字段 而无需显式执行 Record somerfield 我想要做的是一个将记录保存 加载为 INI 文件的通用函数 以便我可以从记录结构中添加或删除字段 而无需每次在记录中删除或添加字段时重
  • 是否可以在react项目中使用dotenv?

    我正在尝试设置一些环境变量 用于对 dev prod 端点进行 API 调用 密钥取决于 dev prod 等 我想知道使用 dotenv 是否有效 我已经安装了 dotenv 并且正在使用 webpack 我的 webpack 入口是ma
  • 在 Python 数据框中获取一天的一部分(早上、下午、晚上、夜晚)

    这是我的数据框 我需要根据行值的时间创建一个新列 早上 下午 晚上 晚上 这是我的代码 if prods hour lt 4 prods hour gt 8 prods session Early Morning elif prods ho
  • 一个应用程序具有多个 component.js:如何加载共享模块?

    我们有一个 SAPUI5 应用程序 其中定义了多个组件 即多个 Component js 文件 每个组件都在各自的子文件夹中作为应用程序根目录的直接子文件夹 下图显示了项目结构 组件 1 指定为com test component1 分量
  • Ecto 构建多个关联

    目前正在使用 Ecto Postgres 从事 Phoenix 项目 创建评论时 作为评论belongs to用户和文章 有没有一种方法可以构建多个关联来生成一个变更集 像这样的伪代码 comment changeset build ass
  • 将外部 png 加载到 AS2 swf 中,该 AS2 swf 加载到 AS3 swf 包装器中

    我有一个 Wrapper SWF 可以加载一系列 AS2 电影 每个 AS2 影片都会加载一系列 png 文件 AS3 wrapper swf gt AS2 1 swf gt image 1 png gt image 2 png gt AS
  • sqlalchemy - query.all() - 要字典的元组列表

    这是我的电话 session query User username User first name User last name all 它返回 myUsername myFirstname myLastname 我希望其格式如下 use
  • 将新管理员添加到活动管理员

    我正在为我的用户使用设备 我最近安装了导轨活跃管理宝石 http activeadmin info 一切都很顺利 但是我不知道如何添加新的管理员用户 我可以看到活跃管理员创建了一个admin user数据库中的表与用户 电子邮件受保护 cd
  • 数据表复选框没有获得价值

    长话短说 所以我尝试使用 POST 来获取我的 Flask 模板中的复选框值 但是 我在模板中的数据表 数据表按钮 中使用了一些动态表 这些数据表导致我的发布请求不起作用 在不使用数据表的情况下 如果它只是一个很长的常规表 那么我的发布请求
  • iphone开发-GDB错误信号EXC_BAD_ACCESS

    我收到信号错误EXC BAD ACCESS当尝试从 randomBallPick 方法检索返回输出时 我可能做错了 NSString temp self randomBallPick upBall1 image UIImage imageN
  • 当 btoa 被弃用时,如何在 ReactJS 中编码 Base 64 字符串?

    我正在使用一个 API 它要求我授权我的客户端 ID 和客户端密钥 我尝试使用 btoa 方法 但它说它已被弃用 这是我到目前为止所尝试过的 授权 基本 btoa ClientID ClientSecret 这似乎给出了相同的结果btoa
  • 如何使用 javascript 或 jQuery 处理多个 HTML 中的通用代码

    我有一个应用程序 其中有近 30 个 html 它们共享一些通用代码 如果我对其进行任何更改 我需要在每个页面上更改它 下面是我在所有页面上使用的代码片段 div ul li class current a href html a li l
  • 使用activestorage的直接上传上传到S3时如何指定前缀?

    使用标准 S3 配置 AWS ACCESS KEY ID AWS ID AWS BUCKET bucket name AWS REGION region AWS SECRET ACCESS KEY secret 我可以使用以下 Rails
  • 如何编写在现代 x64 处理器上高效运行的自修改代码?

    我正在尝试加速可变位宽整数压缩方案 并且我对动态生成和执行汇编代码感兴趣 目前 大量时间花费在错误预测的间接分支上 并且根据发现的一系列位宽生成代码似乎是避免这种损失的唯一方法 一般技术被称为 子例程线程 或 调用线程 尽管这也有其他定义