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

标签 c embedded clang llvm 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
}

它按预期工作,但需要一些汇编程序胶水代码才能调用它,这是一个非常肮脏的黑客攻击,它之所以有效,是因为我可以假设关于代码生成过程的几件事。

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

任何人都可以提出一种更简洁的方法来获取 C 函数的大小,而不必诉诸诸如上面概述的那些令人难以置信的丑陋和不可靠的 hack?

最佳答案

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

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

铿锵的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

将其编译为 .oclang-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 将您关心的函数大小放入表中
  • 编译sizes.c
  • 将所有内容链接在一起


  • 警告

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

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

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

    另一个想法,我不确定是否安全 :

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

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

    但是我不确定在 asm volatile 之后最终会产生多少上限。

    关于c - 测量使用 Clang/LLVM 生成的函数的大小?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36681014/

    相关文章:

    c - 程序堆大小?

    c - 为什么嵌入式使用 q15 而不是标准 int16

    linux - 对硬件抽象层的说明

    embedded - 我如何编写将全局变量/静态变量放在填充的 BSS 段中的 Rust 代码?

    c++ - 在 SFINAE 中将 int 缩小为 bool,gcc 和 clang 之间的输出不同

    c++ - 如果配置中未使用字段,则修复警告 "field a is not used"的好方法

    c - 将 2 个数字相加,并根据使用 C 从相加得到的位数给出相应的输出

    c - 如何捕获回车键( '\n')?

    c# - 重命名文件Powershell看不到

    templates - GCC(/Clang) : Merging functions with identical instructions (COMDAT folding)