与gcc相比,为什么clang在寄存器变量中表现怪异?

时间:2019-04-12 09:14:33

标签: c assembly clang

背景:

我需要在C代码中获取寄存器值,因此我在gcc usage中找到了它。我使用以下代码获得ebp值。

   register int ebp asm("ebp");
   printf("currently ebp is %08x\n", ebp);
   // then some code use the value

在我将程序的编译器更改为clang之前,一切似乎都还不错。在gcc中,它通常会打印类似于0x7f1284978的内容,绝对是类似value的指针。

但是当使用clang时,输出变得很奇怪,它打印出一个类似于0x9的值。 Ebp不能具有这样的值。

问:

  • clang不支持此寄存器变量用法吗?
  • 如果它不支持此功能,为什么不抱怨警告或错误(使用以下代码进行编译)?

    #include <stdio.h>
    
    static size_t vfp = 0x233;
    
    int main(void) {
        register int ebp asm("ebp");
        vfp = (size_t) ebp;
        printf("vfp value is 0x%lx\n", vfp);
        return 0;
    }
    

3 个答案:

答案 0 :(得分:3)

在Gcc中,register关键字执行以下操作(如此处所述:Using Gcc - Local Register Variables):

如果您使用Inline Assembly中的变量,gcc将尝试将其放入您指定的寄存器中。在任何其他情况下,register关键字都不起作用,并且如第一个链接底部所述,在行内程序集的输入中指定变量是不可替代的。

与不知道的clang一起使用时,该关键字的作用,最有可能被忽略(请参阅Is the register keyword still used?

答案 1 :(得分:3)

TL; DR:

Clang目前不支持显式寄存器变量。

详细信息:

请参见clang documentation

  

clang仅在指定的寄存器不可分配时才支持全局寄存器变量(例如,堆栈指针)。对通用全局寄存器变量的支持不太可能很快实现,因为它需要额外的LLVM后端支持。

在我的机器(x86_64 ubuntu 16.04)上,如果我使用Clang-5.0进行编译,则得到的程序集是:

 08048410 <main>:
 8048410:       55                      push   %ebp
 8048411:       89 e5                   mov    %esp,%ebp
 8048413:       83 ec 18                sub    $0x18,%esp
 8048416:       8d 05 c0 84 04 08       lea    0x80484c0,%eax
 804841c:       8b 4d fc                mov    -0x4(%ebp),%ecx ;this line is wrong, the behavior is meaningless
 804841f:       89 0d 1c a0 04 08       mov    %ecx,0x804a01c
 8048425:       8b 0d 1c a0 04 08       mov    0x804a01c,%ecx
 804842b:       89 04 24                mov    %eax,(%esp)
 804842e:       89 4c 24 04             mov    %ecx,0x4(%esp)
 8048432:       e8 89 fe ff ff          call   80482c0 <printf@plt>
 8048437:       89 45 f8                mov    %eax,-0x8(%ebp)
 804843a:       83 c4 18                add    $0x18,%esp
 804843d:       5d                      pop    %ebp
 804843e:       c3                      ret
 804843f:       90                      nop

如果我使用GCC-5.5.0进行编译,则这是我得到的程序集:

0000051d <main>:


 51d:   8d 4c 24 04             lea    0x4(%esp),%ecx
 521:   83 e4 f0                and    $0xfffffff0,%esp
 524:   ff 71 fc                pushl  -0x4(%ecx)
 527:   55                      push   %ebp
 528:   89 e5                   mov    %esp,%ebp
 52a:   53                      push   %ebx
 52b:   51                      push   %ecx
 52c:   e8 33 00 00 00          call   564 <__x86.get_pc_thunk.ax>
 531:   05 a7 1a 00 00          add    $0x1aa7,%eax
 536:   89 ea                   mov    %ebp,%edx ; this is the correct location to get the value of ebp
 538:   89 90 30 00 00 00       mov    %edx,0x30(%eax)
 53e:   8b 90 30 00 00 00       mov    0x30(%eax),%edx
 544:   83 ec 08                sub    $0x8,%esp
 547:   52                      push   %edx
 548:   8d 90 18 e6 ff ff       lea    -0x19e8(%eax),%edx
 54e:   52                      push   %edx
 54f:   89 c3                   mov    %eax,%ebx
 551:   e8 5a fe ff ff          call   3b0 <printf@plt>
 556:   83 c4 10                add    $0x10,%esp
 559:   90                      nop
 55a:   8d 65 f8                lea    -0x8(%ebp),%esp
 55d:   59                      pop    %ecx
 55e:   5b                      pop    %ebx
 55f:   5d                      pop    %ebp
 560:   8d 61 fc                lea    -0x4(%ecx),%esp
 563:   c3                      ret

我们可以看到GCC通常支持显式寄存器值访问,而Clang不支持。

解决方案:

如果您希望使用Clang来访问ebp值,则可以使用内联汇编,例如:asm("\t movl %%ebp,%0" : "=r"(vfp));

答案 2 :(得分:2)

作为@ThePatrickStar和@Boden_Units答案的补充:在LLVM IR生成期间,Clang驱动程序将显式寄存器初始化删除。 这是运行inline_asm.ll(clang-7)时clang -emit-llvm -S inline_asm.c -o inline_asm.ll的内容。

; ModuleID = 'inline_asm.c'
source_filename = "inline_asm.c"
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-pc-linux-gnu"

@vfp = internal global i64 563, align 8
@.str = private unnamed_addr constant [20 x i8] c"vfp value is 0x%lx\0A\00", align 1

; Function Attrs: noinline nounwind optnone uwtable
define dso_local i32 @main() #0 {
  %1 = alloca i32, align 4
  %2 = alloca i32, align 4
  store i32 0, i32* %1, align 4
  %3 = load i32, i32* %2, align 4
  %4 = sext i32 %3 to i64
  store i64 %4, i64* @vfp, align 8
  %5 = load i64, i64* @vfp, align 8
  %6 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([20 x i8], [20 x i8]* @.str, i32 0, i32 0), i64 %5)
  ret i32 0
}

declare dso_local i32 @printf(i8*, ...) #1

attributes #0 = { noinline nounwind optnone uwtable "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" "no-infs-fp-math"="false" "no-jump-tables"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+fxsr,+mmx,+sse,+sse2,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" }
attributes #1 = { "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+fxsr,+mmx,+sse,+sse2,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" }

!llvm.module.flags = !{!0}
!llvm.ident = !{!1}

!0 = !{i32 1, !"wchar_size", i32 4}
!1 = !{!"clang version 7.0.1-svn348686-1~exp1~20190113235231.54 (branches/release_70)"}

实际上,为register int ebp asm("ebp");生成的IR与register int ebp;没什么不同,好像ebp从未被初始化或绑定到ebp寄存器。