测量使用Clang / LLVM生成的函数的大小?

时间:2016-04-17 19:13:56

标签: c embedded clang llvm low-level

最近,在处理项目时,我需要测量C函数的大小,以便能够将其复制到其他地方,但却无法找到任何" clean"解决方案(最终,我只想在我可以参考的函数末尾插入一个标签)。

为这个架构编写了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
}

它按预期工作但需要一些汇编程序粘合代码才能调用它,并且是一个非常脏的黑客只能起作用,因为我可以假设有关代码生成过程的几件事。

我还考虑过其他方法,如果LLVM发出机器代码会更好地工作(因为一旦我将MC发射器添加到我的LLVM后端,这种方法就会中断)。我考虑的方法涉及获取函数并搜索终结符指令(可能是b lr指令或pop ..., lr的变体),但这也可能引入额外的复杂性(尽管它似乎比我的更好)原始解决方案)。

任何人都可以建议一种更简洁的方法来获得C函数的大小,而不必诉诸上面列出的令人难以置信的丑陋和不可靠的黑客攻击吗?

1 个答案:

答案 0 :(得分:2)

我认为你没有任何真正便携的方法可以做到这一点。允许编译器重新排序函数,因此以源顺序获取下一个函数的地址是不安全的(但在某些情况下确实有效)。

如果你可以解析目标文件(maybe with libbfd),你可以从中获得函数大小。

clang的asm output有这个元数据(每个函数后面都是.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
  • 将您关注的函数的大小放入表格中
  • compile sizes.c
  • 将所有内容链接在一起

买者

一个非常聪明的编译器可以编译多个类似的函数来共享一个共同的实现。因此,其中一个函数跳转到另一个函数体的中间。如果幸运的话,所有功能都组合在一起,每个功能的“大小”从其入口点一直到它使用的代码块的末尾。 (但这种重叠会使总大小加起来超过文件的大小。)

当前编译器不会这样做,但是可以通过将函数放在单独的编译单元中来阻止它,而不是使用整个程序链接时优化。

编译器可以决定在函数入口点之前放置一个有条件执行的代码块,因此分支可以对较小的位移使用较短的编码。 This makes that block look like a static "helper" function可能不会包含在函数的“大小”计算中。但是,目前的编译器也从未这样做过。

另一个想法,我不自信是安全的

在函数末尾放置asm volatile 标签定义,然后假设函数大小最多为+ 32字节或其他东西。因此,当您复制该函数时,您将分配一个大于“计算”大小的缓冲区32B。希望标签之外只有一个“ret”insn,但实际上它可能会在函数结尾之前弹出所有保存的调用保留寄存器。

我认为优化器不能复制asm volatile语句,因此它会强制编译器跳转到一个共同的结尾,而不是像有时候为早期条件重复结尾。

但是我不确定在 asm volatile之后有多少可以结束的上限。