【问题标题】:Measuring size of a function generated with Clang/LLVM?测量使用 Clang/LLVM 生成的函数的大小?
【发布时间】:2016-04-17 19:13:55
【问题描述】:

最近,在处理一个项目时,我需要测量 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
}

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

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

谁能提出一种更简洁的方法来获取 C 函数的大小,而不必求助于上述那些令人难以置信的丑陋和不可靠的 hack?

【问题讨论】:

  • 在 C 中使用 sizeof 函数是未定义的行为。但是您可以在编译到函数部分时使用链接器来生成适当的符号。
  • 用汇编语言写sdram_init_late()合理吗?如果是这样,您将完全控制如何提供要复制的代码块的开头和结尾。
  • 它当然可以用汇编语言编写,但用 C 语言编写,并与其他 SDRAM 初始化代码一起保存在同一个文件中(除了用于跳转到该函数和保存/恢复寄存器的例程)使其更易于维护。

标签: c embedded clang llvm low-level


【解决方案1】:

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


如果您可以解析目标文件 (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

clang-3.8 -O3 -Wall -Wextra func-size.c -c 将其编译为.o,然后我可以这样做:

$ 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 之后 有多少可以结束的上限。

【讨论】:

  • readelf -s 方法是明智的。如果您查看目标代码(例如objdump -d func-size,该函数的目标代码确实占用了所有 21 个字节。(但是,gcc-4.8.4 和 clang-3.5 都设法在仅 7 个字节中完成它,并且这也是readelf -s 在他们的目标文件上显示的内容;另请注意,大小字段是十进制的。)我想说你关于readelf -s 的答案是正确的,而你只是被clang-3.8 误导了为您生成额外的二进制代码,您没有注意到要检查。我想。:)
  • @NominalAnimal: 21 个字节来自默认的-O0。不是填充,只是通常的臃肿代码。 /掌心。我在 Godbolt 上查看相同的代码,所以我可以发布到 asm 的链接。我没有意识到我实际上并没有查看我在桌面上测试的内容的反汇编。很抱歉在这种混乱中浪费了人们的时间!
  • :) 对于它的价值,我确实依赖readelf -s 报告的尺寸。但是,我总是担心函数之外的跳转;总有一天,编译器会找到一种方法来合并函数实现以节省代码使用。这将是一件非常好的事情(减少缓存污染),但它会完全破坏这个特定的用例。因此,我个人会对此添加警告,并建议将此类函数编译在单独的编译单元中,以避免这种情况。
  • @nominalanimal 对于它的价值,已经有编译器可以做到这一点。如果足够相似,ARM realview 编译器和 Greenhills 编译器(至少对于 ARM)可以进行链接时优化并将函数折叠在一起。
  • @PeterCordes 是的,GHS 会的。或者至少在它打开的情况下,调试比平时更成问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-10-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-08-29
  • 1970-01-01
  • 2015-05-13
相关资源
最近更新 更多