Kri*_*oks 5 c embedded llvm clang low-level
最近,在处理项目时,我需要测量C函数的大小,以便能够将其复制到其他地方,但无法找到任何"干净"的解决方案(最终,我只是想拥有在我可以参考的函数末尾插入的标签).
编写了这个体系结构的LLVM后端(虽然它可能看起来像ARM,但事实并非如此)并且知道它为该体系结构发出了汇编代码,我选择了以下hack(我认为评论很好地解释了它):
/***************************************************************************
* 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
}
Run Code Online (Sandbox Code Playgroud)
它按照需要工作,但需要一些汇编程序粘合代码才能调用它,这是一个非常糟糕的黑客只有工作,因为我可以假设代码生成过程的几个方面.
我还考虑过这样做的其他方法,如果LLVM发出机器代码会更好地工作(因为一旦我将MC发射器添加到我的LLVM后端,这种方法就会中断).我考虑的方法涉及获取函数并搜索终结符指令(可能是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
Run Code Online (Sandbox Code Playgroud)
将其编译为.owith 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
Run Code Online (Sandbox Code Playgroud)
这三个指令总共 7 个字节,与此处的输出相符size。它不包括用于对齐入口点的填充,也不包括下一个函数:指令.align位于两个标签之外,通过减去这两个标签来计算.size.
这对于剥离的可执行文件可能效果不佳。即使它们的全局函数也不会出现在可执行文件的符号表中。因此,您可能需要一个两步构建过程:
readelf | some text processing > sizes.c一个非常聪明的编译器可以编译多个相似的函数来共享一个公共的实现。因此其中一个函数会跳转到另一个函数体的中间。如果幸运的话,所有函数都会组合在一起,每个函数的“大小”从其入口点一直到它使用的代码块的末尾。(但是这种重叠会使总大小加起来超过文件的大小。)
当前的编译器不会这样做,但您可以通过将函数放在单独的编译单元中并且不使用整个程序链接时优化来防止这种情况发生。
编译器可以决定在函数入口点之前放置一个条件执行的代码块,因此分支可以使用较短的编码来实现小位移。 这使得该块看起来像一个静态“辅助”函数,它可能不会包含在函数的“大小”计算中。不过,当前的编译器也从不这样做。
我不确定另一个想法是否安全:
在函数末尾放置一个仅asm volatile包含标签定义的 ,然后假设函数大小最多为 + 32 字节或其他值。因此,当您复制该函数时,您分配的缓冲区比您“计算的”大小大 32B。希望标签之外只有一个“ret”insn,但实际上它可能位于函数尾声之前,该函数尾声会弹出它使用的所有调用保留的寄存器。
我不认为优化器可以复制asm volatile语句,因此它会强制编译器跳转到公共尾声,而不是像有时在早期退出条件下那样复制尾声。
但我不确定asm 易失性之后最终可能有多少上限。