为什么一个只调用 printf 的函数,对应的汇编代码这么复杂?

2024-06-03 16:36:46 +08:00
 amiwrong123
#include <stdio.h>

void print_banner()
{
    printf("Welcome to World of PLT and GOT\n");
}

int main(void)
{
    print_banner();

    return 0;
}

如上,有一个 test.c ,使用 gcc -Wall -g -o test.o -c test.c -m32 编译后(最开始报错了,然后通过 sudo apt-get install libc6-dev:i386 解决),得到了 test.o 文件。

然后通过 objdump -d test.o 查看汇编,却发现print_banner函数的汇编很奇怪,是这样的:

test.o:     file format elf32-i386


Disassembly of section .text:

00000000 <print_banner>:
   0:   f3 0f 1e fb             endbr32
   4:   55                      push   %ebp
   5:   89 e5                   mov    %esp,%ebp
   7:   53                      push   %ebx
   8:   83 ec 04                sub    $0x4,%esp
   b:   e8 fc ff ff ff          call   c <print_banner+0xc>
  10:   05 01 00 00 00          add    $0x1,%eax
  15:   83 ec 0c                sub    $0xc,%esp
  18:   8d 90 00 00 00 00       lea    0x0(%eax),%edx
  1e:   52                      push   %edx
  1f:   89 c3                   mov    %eax,%ebx
  21:   e8 fc ff ff ff          call   22 <print_banner+0x22>
  26:   83 c4 10                add    $0x10,%esp
  29:   90                      nop
  2a:   8b 5d fc                mov    -0x4(%ebp),%ebx
  2d:   c9                      leave
  2e:   c3                      ret

感觉 call 22 之前做的很多事情都不理解。比如为什么上面还有一次 call c ?

实际上我看别人生成的汇编都是这样的( https://blog.csdn.net/linyt/article/details/51635768 ):

00000000 <print_banner>:
      0:  55                   push %ebp
      1:  89 e5                mov %esp, %ebp
      3:  83 ec 08             sub   $0x8, %esp
      6:  c7 04 24 00 00 00 00 movl  $0x0, (%esp)
      d:  e8 fc ff ff ff       call  e <print_banner+0xe>
     12:  c9                   leave
     13:  c3                   ret

问一下各位大佬,为什么我的print_banner函数的汇编这么奇怪啊?

另外,用gcc -S -o test.s test.c -m32生成了 test.s 这种方式来看汇编,发现是这样的,第一次的 call 是调用的__x86.get_pc_thunk.ax

print_banner:
.LFB0:
        .cfi_startproc
        endbr32
        pushl   %ebp
        .cfi_def_cfa_offset 8
        .cfi_offset 5, -8
        movl    %esp, %ebp
        .cfi_def_cfa_register 5
        pushl   %ebx
        subl    $4, %esp
        .cfi_offset 3, -12
        call    __x86.get_pc_thunk.ax
        addl    $_GLOBAL_OFFSET_TABLE_, %eax
        subl    $12, %esp
        leal    .LC0@GOTOFF(%eax), %edx
        pushl   %edx
        movl    %eax, %ebx
        call    puts@PLT
        addl    $16, %esp
        nop
        movl    -4(%ebp), %ebx
        leave
        .cfi_restore 5
        .cfi_restore 3
        .cfi_def_cfa 4, 4
        ret
        .cfi_endproc
3422 次点击
所在节点    程序员
34 条回复
chitaotao
2024-06-03 23:56:10 +08:00
一堆 sub 应该是在做栈对齐,i386 System V ABI 要求栈 esp+4 ( 4 是返回地址大小)对齐到 16 字节,按他这样算在 call 的时候刚好会对齐到 16 字节
amiwrong123
2024-06-04 00:01:54 +08:00
@bfc0 #10
@chitaotao #17
![]( https://s3.bmp.ovh/imgs/2024/06/03/03f454618e14e907.png)

关于这个 get_pc_thunk 附件的汇编,感觉有点神奇哦(请看上图)。

明明“可重定位目标文件”里面还是 add $0x1,%eax 和 lea 0x0(%eax),%edx ,用 gdb 调试时,就变成了其他值,这是发生了 重定位吗
amiwrong123
2024-06-04 00:03:01 +08:00
@AoEiuV020JP #2
printf 这个 f 可不简单,可以进一步说一下吗
chitaotao
2024-06-04 00:03:32 +08:00
@amiwrong123 是,去掉-c 进行链接之后就可以看到重定位的地址
ashong
2024-06-04 00:11:43 +08:00
指定 entry 试试
iceheart
2024-06-04 05:48:44 +08:00
没链接的外部函数当然没地址了。
编译加 -O2 会有新发现
chayuu
2024-06-04 09:46:25 +08:00
@amiwrong123 #20
`R_386_PC32`、`R_386_GOTPC`、`R_386_GOTOFF`这几个都是重定位类型,指示链接器在重定位的时候要怎么计算这个偏移,也就是你在#22 提到的替换。具体的类型是什么意思,具体去查一下就知道了
ZhiyuanLin
2024-06-04 12:05:34 +08:00
@amiwrong123
> printf 这个 f 可不简单,可以进一步说一下吗
printf 用了 vararg 。一般 libc 实际实现是在 vprintf ,printf 是个 macro 。
amiwrong123
2024-06-04 14:47:24 +08:00
@ysc3839 #7
我试了,加-fcf-protection=none 参数,然后就没有 endbr32 了。
但 print_banner 的其他汇编还是一样的。
amiwrong123
2024-06-04 14:52:52 +08:00
@chitaotao #21
前两次 sub 确实是 为了汇编里面的 这两次 call 的对齐要求,来做的。我用 gdb 看了后,发现确实是这样的。
ysc3839
2024-06-04 16:12:40 +08:00
@amiwrong123 所以开了优化吗?
e3c78a97e0f8
2024-06-04 18:45:58 +08:00
你好歹开个-O3
nitro123
2024-06-04 22:49:24 +08:00
因为需要可变参数?
amiwrong123
2024-06-05 10:24:34 +08:00
@ysc3839 #31
@e3c78a97e0f8 #32
@MrKrabs #13
@iceheart #26

我这里试了 gcc -Wall -g -O3 -o test.o -c test.c -m32 && gcc -o test test.o -m32
然后用 objdump -dx test ,直接查看最后的可执行文件。

![]( https://s3.bmp.ovh/imgs/2024/06/05/3b0683fd31bd5d68.png)

如上图,是执行的结果。是 objdump -dx test 的汇编。


看起来就是优化掉了,函数开头结尾的栈帧维护操作,比如开头的 push %ebp ; mov %esp,%ebp 。比如结束的 leave 。

PS:抱歉试得有点迟了

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://yangjunhui.monster/t/1046414

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX