gpt4 book ai didi

assembly - 防止 GCC 使用动态跳转/函数调用

转载 作者:行者123 更新时间:2023-12-04 15:17:02 26 4
gpt4 key购买 nike

我正在尝试为 GCC 编译的应用程序编写一个程序集检测器模块,作为安全框架的一部分。为了提高模块的性能,我需要尽可能地减少动态跳转/动态函数调用。这些基本上使用一些动态指针(例如寄存器)来执行跳转或调用函数。
当前的 GCC 编译器在多次调用同一个函数(代码中的某个标签)时,将标签加载到一个寄存器中,然后在需要调用该函数时跳转到该寄存器。这当然比每次都跳转到同一个标签(更小的代码和更少的时钟周期)要快得多,但是,正如我所提到的,我的框架效率低下。
为了给你一个我想避免的例子,这里有一个代码片段:

MOV #function_label, R10.  #Copy the label to the R10 register
CALL R10
...
...
CALL R10
...
...
CALL R10
虽然我希望 GCC 执行以下操作:
CALL #label_function
...
...
CALL #label_function
...
...
CALL #label_function
请注意,我实际上使用的是 mspgcc,这是 MSP430 系列微 Controller 的 GCC 编译器,但基于 GCC 应该不会有太大区别。

你认为有什么可以做的吗(除了重写 GCC 编译器)?非常感谢您的帮助

最佳答案

使用 -fno-function-cse不要对函数地址进行公共(public)子表达式消除。 GCC manual :

-fno-function-cse

Do not put function addresses in registers; make each instruction that calls a constant function contain the function’saddress explicitly.

This option results in less efficient code, but some strange hacksthat alter the assembler output may be confused by the optimizationsperformed when this option is not used.

The default is -ffunction-cse



如何找到特定的 GCC 选项
我看了 gcc -O1 -fverbose-asm asm 输出以查看 -O1 的所有优化选项暗示(GCC 在 asm 注释中列出)。 -O1 -fno-...所有版本都编译为 3 call每个指令都带有符号名称,确认其中一个是我想要的,所以我只需要通过将 -fno- 的列表一分为二来缩小范围。选项
我使用了带有 MSP430 GCC6.2.1 的 Godbolt 编译器资源管理器, test code + asm .我禁用了“评论”过滤器选项,所以我可以在 asm 输出中看到纯评论行。
由于有很多选择,我使用了 tr ' ' '\n' | sed -e 's/-f/-fno-/' -e '/;/d'-f选项变成它们的否定形式。我将整个 asm 注释块复制/粘贴到终端中的该命令中,并将结果复制/粘贴到 Godbolt 上的 GCC 选项框中。 (与 -O1 一起。 -O0 是一种用于一致调试的特殊反优化模式,因此即使使用正确的选项,跨语句优化可能永远不会在 -O0 处处于事件状态。这就是为什么我需要否定这些选项的原因在没有 -O1 的情况下尝试正数形式)
然后我选择并删除了一堆选项,看看是否改变了 asm。如果没有,请继续。当我发现一个块时,我知道我想要的选项在那里,所以我可以撤消 (control-z) 并删除所有其他 -f选项,然后将其缩小到一个。 (当我在该组中看到名称 -fno-function-cse 时,我认为这听起来像是正确的事情。幸运的是,如果您了解编译器/优化术语,GCC 选项确实具有有意义的名称。)
这比一次查看 1 个选项或翻阅手册要快,因为我什至不确定这些特定选项中的任何一个是否会控制这一点。

顺便说一句,GCC 不会对大多数其他 ISA 进行代码大小优化 因为这对他们来说不是一场表演胜利。代码大小不是 x86-64 甚至 ARM 上性能的最重要因素;间接跳转的可能分支错误预测的额外成本(以及分支预测器的额外污染)超过了代码大小成本。
It is a code-size win on x86 , 其中一个 5 字节 mov - 立即或 7 字节 RIP 相关 lea (x86-64) 可以设置多个 2 字节 call指示。
在 AArch64 或 ARM(Thumb 模式除外)等许多固定指令宽度的 ISA 上,它通常甚至不是代码大小的胜利,其中标准代码模型假设函数将在彼此的范围内进行相对分支和链接(调用)指令。因此调用任何函数都需要一条指令,与任何其他指令的大小相同。
即使与 -ffunction-cse显式启用,GCC 根本不会为 x86-64 或 ARM 拇指做这种优化,即使在它已经使用 GOT 中的函数指针的情况下。 (在 Godbolt 上的 x86-64 gcc -Os -fPIE -fno-plt -ffunction-cse 。我什至告诉 GCC 优化代码大小;保存/恢复像 RBX 这样的调用保留寄存器以用于 2 字节 call rbx 而不是 6 字节 call [RIP+rel32] 会即使在推送/弹出 RBX(每个 1 个字节)并加载到 RBX(一个具有 RIP 相对寻址模式的 mov)所需的额外指令之后,也可以节省大小。)
这可以被认为是对 -Os 的遗漏优化。 ,特别是对于像 -mcpu=cortex-m3 这样的“简单”内核的 ARM Thumb它甚至可能没有分支预测器。
(AArch64 会将函数指针加载到具有 -fPIE -fno-plt 的寄存器中,用于没有“隐藏”可见性的函数,即该函数可能仅位于共享库中。即使使用 -fno-function-cse 也会发生这种情况。 https://godbolt.org/z/f3MP56. )

关于assembly - 防止 GCC 使用动态跳转/函数调用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/64150636/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com