HelloWorld:从 C 到 ASM 之旅

2017/12/12

这篇文章似乎有不少错误,有空再看心情改=。=

/* filename: helloworld.c */

#include <stdio.h>

int hello(int b) {
    printf("Received: %d\n", b);
    return b+333;
}

int main() {
    int a=111, b=222;
    printf("Passing arguments: %d, %d\n", a, b);
    ++a;
    int c = hello(b);

    return 444;
}
/* gcc -S helloworld.c -o helloworld.asm */
/* filename: helloworld.asm */

    .file   "helloworld.c"
    .section .rdata,"dr"

LC0:
    .ascii "Received: %d\12\0"
    .text
    .globl  _hello
    .def    _hello; .scl    2;  .type   32; .endef
_hello:                             hello 函数开始的地址
LFB10:
    .cfi_startproc                  cfi stands for call frame information
                                    for debugging purpose [1] [2]
    pushl   %ebp                    保护 main() 的栈基
    .cfi_def_cfa_offset 8
    .cfi_offset 5, -8
    movl    %esp, %ebp              重设栈基,新空栈 [3]
                                    进入(stdcall, [4])函数后首先需要的步骤
    .cfi_def_cfa_register 5
    subl    $24, %esp               我也不清楚为什么要拓展这么大的空间
    movl    8(%ebp), %eax           8(%ebp) 就是传过来的 b
                                    准备调用 printf()
    movl    %eax, 4(%esp)           先把第二个参数入栈
    movl    $LC0, (%esp)            再把第一个参数入栈,LC0 正好就是对应数据
    call    _printf                 正式调用 printf()
    movl    8(%ebp), %eax           准备好当前函数的 b(从内存中挪到寄存器)
    addl    $333, %eax              b += 555
                                    不用挪回内存,因为本函数已经不需要用到 b 了
    leave
    .cfi_restore 5
    .cfi_def_cfa 4, 4
    ret                             回到 main 函数
                                    %eax 是约定好的放置函数返回值的地方 [4]
    .cfi_endproc

LFE10:
    .def    ___main;    .scl    2;  .type   32; .endef
    .section .rdata,"dr"
LC1:
    .ascii "Passing arguments: %d, %d\12\0"
    .text
    .globl  _main
    .def    _main;  .scl    2;  .type   32; .endef
_main:
LFB11:
    .cfi_startproc
    pushl   %ebp                    保护栈基
    .cfi_def_cfa_offset 8
    .cfi_offset 5, -8
    movl    %esp, %ebp              重设栈基,新空栈
    .cfi_def_cfa_register 5
    andl    $-16, %esp
    subl    $32, %esp
    call    ___main                 我也不清楚这个 __main 是什么
    movl    $111, 28(%esp)          局部变量存在于栈中 [5]
    movl    $222, 24(%esp)
    movl    24(%esp), %eax          参数开始入栈,从右到左 [4]
    movl    %eax, 8(%esp)
    movl    28(%esp), %eax
    movl    %eax, 4(%esp)
    movl    $LC1, (%esp)
    call    _printf                 正式调用 printf()
    addl    $1, 28(%esp)            ++a
    movl    24(%esp), %eax
    movl    %eax, (%esp)            这两行使参数 b 入栈
    call    _hello                  调用 hello
                                    gcc 自动给函数名前缀一个 _ 符号
                                    这在 c++ 中有不同的规则
                                    所以有些代码需要 extern "C"
    movl    %eax, 20(%esp)          hello() 的返回值置于 %eax,入栈成为局部变量 c
    movl    $444, %eax              同样,%eax 放置函数返回值,main 也不例外
    leave
    .cfi_restore 5
    .cfi_def_cfa 4, 4
    ret
    .cfi_endproc
LFE11:
    .ident  "GCC: (GNU) 5.3.0"
    .def    _printf;    .scl    2;  .type   32; .endef

Reference:

  1. In assembly code how .cfi directives work - An answer by @ysdx, Stackoverflow
  2. .cfi directives
  3. Call stack #Structure, wikipedia
  4. X86 调用约定 #stdcall, wikipedia
  5. C语言-内存管理基础, 简书
comments powered by Disqus