【问题标题】:Builtins in Clang not so builtin?Clang中的内置函数不是那么内置?
【发布时间】:2016-12-01 14:09:32
【问题描述】:

如果我在strlen.c 中有以下内容:

int call_strlen(char *s) {
  return __builtin_strlen(s);
}

然后像这样用 gcc 和 clang 编译它:

gcc -c -o strlen-gcc.o strlen.c

clang -c -o strlen-clang.o strlen.c

我很惊讶地看到 strlen-clang.o 包含对“strlen”的引用,而 gcc 预期内联了该函数并且没有这样的引用。 (参见下面的 objdumps)。这是clang中的错误吗?我已经在多个版本的 clang 编译器中对其进行了测试,包括 3.8。

编辑:这对我来说很重要的原因是我正在与-nostdlib 链接,而clang 编译的版本给了我一个找不到strlen 的链接错误。

叮当

@> objdump -d strlen-clang.o 

strlen-clang.o:     file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <call_strlen>:
   0: 55                    push   %rbp
   1: 48 89 e5              mov    %rsp,%rbp
   4: 48 83 ec 10           sub    $0x10,%rsp
   8: 48 89 7d f8           mov    %rdi,-0x8(%rbp)
   c: 48 8b 7d f8           mov    -0x8(%rbp),%rdi
  10: e8 00 00 00 00        callq  15 <call_strlen+0x15>
  15: 89 c1                 mov    %eax,%ecx
  17: 89 c8                 mov    %ecx,%eax
  19: 48 83 c4 10           add    $0x10,%rsp
  1d: 5d                    pop    %rbp
  1e: c3                    retq   


@> objdump -t strlen-clang.o 

strlen-clang.o:     file format elf64-x86-64

SYMBOL TABLE:
0000000000000000 l    df *ABS*  0000000000000000 strlen.c
0000000000000000 l    d  .text  0000000000000000 .text
0000000000000000 l    d  .data  0000000000000000 .data
0000000000000000 l    d  .bss 0000000000000000 .bss
0000000000000000 l    d  .comment 0000000000000000 .comment
0000000000000000 l    d  .note.GNU-stack  0000000000000000 .note.GNU-stack
0000000000000000 l    d  .eh_frame  0000000000000000 .eh_frame
0000000000000000 g     F .text  000000000000001f call_strlen
0000000000000000         *UND*  0000000000000000 strlen

GCC

@> objdump -d strlen-gcc.o 

strlen-gcc.o:     file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <call_strlen>:
   0:   55                      push   %rbp
   1:   48 89 e5                mov    %rsp,%rbp
   4:   48 89 7d f8             mov    %rdi,-0x8(%rbp)
   8:   48 8b 45 f8             mov    -0x8(%rbp),%rax
   c:   48 c7 c1 ff ff ff ff    mov    $0xffffffffffffffff,%rcx
  13:   48 89 c2                mov    %rax,%rdx
  16:   b8 00 00 00 00          mov    $0x0,%eax
  1b:   48 89 d7                mov    %rdx,%rdi
  1e:   f2 ae                   repnz scas %es:(%rdi),%al
  20:   48 89 c8                mov    %rcx,%rax
  23:   48 f7 d0                not    %rax
  26:   48 83 e8 01             sub    $0x1,%rax
  2a:   5d                      pop    %rbp
  2b:   c3                      retq   

@> objdump -t strlen-gcc.o 

strlen-gcc.o:     file format elf64-x86-64

SYMBOL TABLE:
0000000000000000 l    df *ABS*  0000000000000000 strlen.c
0000000000000000 l    d  .text  0000000000000000 .text
0000000000000000 l    d  .data  0000000000000000 .data
0000000000000000 l    d  .bss 0000000000000000 .bss
0000000000000000 l    d  .note.GNU-stack  0000000000000000 .note.GNU-stack
0000000000000000 l    d  .eh_frame  0000000000000000 .eh_frame
0000000000000000 l    d  .comment 0000000000000000 .comment
0000000000000000 g     F .text  000000000000002c call_strlen

【问题讨论】:

  • 您没有将编译器设置为优化,所以 clang 没有优化。令人震惊。
  • @EOF 这有什么关系?当然,如果被告知使用内置函数,它应该使用内置函数 - 而不是“在更优化的模式下使用内置函数,也许,如果你能被打扰的话”。 builtin 只是像inline 这样的可选请求吗?如果是这样,您能否将我们链接到 Clang 文档中指定的那个位?
  • @EOF 仅供参考,我使用-O3 编译,程序集要短得多,但仍然包含jmp _strlen
  • @Siguza:我的 gcc 4.8.4 仅在未优化 (-O0) 时为调用 strlen()__builtin_strlen() 创建不同的代码,在所有其他优化级别它为两者生成相同的代码。有时它内联(-O1-Os),有时它调用库(-O2-O3)。
  • @EOF,我不认为这是关于优化,而是关于 GCC 内置函数的文档,其中提到“GCC 内置函数总是内联扩展”。诚然,我找不到任何与 Clang 等效的语言,但我认为在没有文档的情况下,否则 clang 在功能上应该与 GCC 等效。

标签: c gcc clang


【解决方案1】:

GCC 和 Clang 都没有承诺内联这个内置函数。 You quoted 一些 GCC 文档似乎做出了这样的承诺:

...GCC 内置函数总是内联扩展...

但这是一个脱离上下文的句子片段。 The complete sentence

除了具有库等效项的内置函数,例如下面讨论的标准 C 库函数,或扩展为库调用,GCC 内置函数总是内联扩展,因此没有相应的入口点,无法获取它们的地址。

__builtin_strlen 具有等效的库strlen,因此这句话没有承诺它是否被内联。

【讨论】:

    【解决方案2】:

    只是为了优化:

    clang -O0:

    t.o:
    (__TEXT,__text) section
    _call_strlen:
    0000000000000000    pushq   %rbp
    0000000000000001    movq    %rsp, %rbp
    0000000000000004    subq    $0x10, %rsp
    0000000000000008    movq    %rdi, -0x8(%rbp)
    000000000000000c    movq    -0x8(%rbp), %rdi
    0000000000000010    callq   _strlen
    0000000000000015    movl    %eax, %ecx
    0000000000000017    movl    %ecx, %eax
    0000000000000019    addq    $0x10, %rsp
    000000000000001d    popq    %rbp
    000000000000001e    retq
    

    clang -O3

    t.o:
    (__TEXT,__text) section
    _call_strlen:
    0000000000000000    pushq   %rbp
    0000000000000001    movq    %rsp, %rbp
    0000000000000004    popq    %rbp
    0000000000000005    jmp _strlen
    

    现在,进入问题:

    clang 文档声称 clang 支持所有 GCC 支持的内置函数。
    但是,GCC documentation 似乎将内置函数及其库等价物的名称视为同义词:

    两种形式都具有相同的类型(包括原型)、相同的地址(当它们的地址被占用时)以及与 C 库函数相同的含义 [...]。

    此外,它也不保证具有等效库的内置函数(如 strlen 的情况)确实得到优化:

    其中许多功能仅在某些情况下进行了优化;如果它们在特定情况下未优化,则会发出对库函数的调用。

    此外,clang internals manual 仅提及一次__builtin_strlen

    • __builtin_strlenstrlen:如果参数是字符串字面量,它们是折叠为整数常量表达式的常量。

    除此之外,他们似乎没有做出任何承诺。

    由于在您的情况下__builtin_strlen 的参数不是字符串文字,并且由于 GCC 文档允许将对内置函数的调用转换为库函数调用,因此 clang 的行为似乎完全有效。

    "patch for review" on the clang developers mailing list 还说:

    [...] 它仍然会退回到库 strlen 的运行时使用,如果 编译时评估是不可能的/必需的[...]。

    那是在 2012 年,但文字表明至少在当时,仅支持编译时评估。

    现在,我看到了两个选项:

    • 如果您只需要自己编译程序然后使用和/或分发它,我建议您直接使用 gcc。
    • 如果您需要其他人能够在 gcc 和 clang 下编译您的代码,我建议添加一个 C 库作为静态链接的依赖项。

    我强烈建议反对滚动您自己的标准库函数实现,即使对于看似简单的情况也是如此(如果您不同意,请尝试编写自己的 strlen 实现,然后将其与 the glibc one 进行比较)。

    【讨论】:

    • “我强烈建议不要滚动你自己的标准库函数实现”——除了性能之外还有什么特别的原因吗?请注意,有一个调试模式很容易,您可以在该模式下运行这两个函数并检查它们是否输出相同的内容。
    • @TLW 错误和规范的一致性。基本功能并不难得到正确或调试,但很难发现边缘情况。如果您尝试优化性能,即使是简单的功能也会变得复杂。如果他们接触到用户输入,只需so many memory errors are exploitable。与此相反,C 标准库函数已被审核了数十年。
    • @Siguza - 很好的答案,虽然 AFL / UBSAN / Valgrind,+ 与标准库行为的比较,可以让你走得很远。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-01-24
    • 1970-01-01
    • 2017-07-28
    • 2016-02-03
    • 2012-08-15
    • 1970-01-01
    相关资源
    最近更新 更多