最近,在进行项目工作时,我需要测量C函数的大小,以便能够将其复制到其他地方,但是却找不到任何“干净的”解决方案(最终,我只是想拥有一个我可以引用的函数末尾插入的标签)。
为该架构编写了LLVM后端(虽然看上去像ARM,但不是),并且知道它发出了该架构的汇编代码,所以我选择了以下技巧(我认为注释很好地说明了这一点):
/***************************************************************************
* if ENABLE_SDRAM_CALLGATE is enabled, this function should NEVER be called
* from C code as it will corrupt the stack pointer, since it returns before
* its epilog. this is done because clang does not provide a way to get the
* size of the function so we insert a label with inline asm to measure the
* function. in addition to that, it should not call any non-forceinlined
* functions to avoid generating a PC relative branch (which would fail if
* the function has been copied)
**************************************************************************/
void sdram_init_late(sdram_param_t* P) {
/* ... */
#ifdef ENABLE_SDRAM_CALLGATE
asm(
"b lr\n"
".globl sdram_init_late_END\n"
"sdram_init_late_END:"
);
#endif
}
它可以按需工作,但是需要一些汇编程序粘合代码才能调用它,并且这是一个非常肮脏的技巧,它只能起作用,因为我可以假设有关代码生成过程的几件事。
我还考虑了其他方法,如果LLVM发出机器代码,这种方法会更好(因为一旦向LLVM后端添加MC发射器,这种方法就会失效)。我考虑的方法涉及采取函数并搜索终止符指令(可能是b lr
指令或的变体pop ..., lr
),但这也可能带来其他复杂性(尽管它似乎比我最初的解决方案要好)。
谁能建议一种更干净的方法来获得C函数的大小,而不必诉诸于上面概述的丑陋而又不可靠的黑客程序?
我认为您是对的,没有任何真正可移植的方式来执行此操作。允许编译器对函数进行重新排序,因此以源顺序获取下一个函数的地址并不安全(但在某些情况下确实可行)。
如果您可以解析目标文件(也许使用libbfd
),则可以从中获取函数的大小。
clang的asm输出具有此元数据(.size
每个函数后的汇编器指令),但是我不确定它是否最终出现在目标文件中。
int foo(int a) { return a * a * 2; }
## clang-3.8 -O3 for amd64:
## some debug-info lines manually removed
.globl foo
foo:
.Lfunc_begin0:
.cfi_startproc
imul edi, edi
lea eax, [rdi + rdi]
ret
.Lfunc_end0:
.size foo, .Lfunc_end0-foo ####### This line
编译这一个.o
有clang-3.8 -O3 -Wall -Wextra func-size.c -c
,我可以再做:
$ readelf --symbols func-size.o
Symbol table '.symtab' contains 4 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FILE LOCAL DEFAULT ABS func-size.c
2: 0000000000000000 0 SECTION LOCAL DEFAULT 2
3: 0000000000000000 7 FUNC GLOBAL DEFAULT 2 foo ### This line
这三个指令共7个字节,与size
这里的输出相匹配。它不包含用于对齐入口点的填充或下一个函数:.align
指令位于两个标签的相加处,两个标签相减后得出.size
。
这对于剥离的可执行文件可能效果不佳。即使它们的全局功能也不会出现在可执行文件的符号表中。因此,您可能需要两步构建过程:
readelf | some text processing > sizes.c
一个真正聪明的编译器可以编译多个相似的函数以共享一个公共实现。因此,一个功能跳入另一个功能主体的中间。如果幸运的话,所有功能都组合在一起,每个功能的“大小”从其入口点一直到其使用的代码块的末尾。(但是这种重叠会使总大小加起来超过文件的大小。)
当前的编译器不执行此操作,但是可以通过将函数放在单独的编译单元中,而不使用整个程序的链接时优化来防止此情况。
编译器可以决定将条件执行的代码块放在函数入口之前,因此分支可以为较小的位移使用较短的编码。这使该块看起来像一个静态的“ helper”函数,可能不会包含在该函数的“ size”计算中。不过,当前的编译器也从未这样做。
我不确定的另一个想法很安全:
把一个asm volatile
与刚才在你的函数的最后一个标签定义,然后承担的功能尺寸最大的是+ 32字节或东西。因此,当您复制函数时,您分配的缓冲区32B大于“已计算”的大小。希望标签之外只有一个“ ret” insn,但实际上它可能在函数结尾之前,该函数弹出所有它使用的保留调用的寄存器。
我认为优化器不能复制一条asm volatile
语句,因此它将迫使编译器跳转到一个通用的结尾,而不是像在某些早期情况下那样复制结尾。
但是我不确定在自动柜员机挥发后最终能存多少钱是有上限的。
本文收集自互联网,转载请注明来源。
如有侵权,请联系[email protected] 删除。
我来说两句