理解由简单C程序生成的汇编代码

理解由简单C程序生成的汇编代码,c,linux,assembly,stack,disassembly,C,Linux,Assembly,Stack,Disassembly,我试图通过使用gdb的反汇编程序检查一个简单C程序来理解它的汇编级代码 以下是C代码: #include <stdio.h> void function(int a, int b, int c) { char buffer1[5]; char buffer2[10]; } void main() { function(1,2,3); } 我正在寻找以下问题的答案: 地址是如何工作的,我的意思是(main+0),(main+1),(main+3) 总的来说,为什么

我试图通过使用gdb的反汇编程序检查一个简单C程序来理解它的汇编级代码

以下是C代码:

#include <stdio.h>

void function(int a, int b, int c) {
   char buffer1[5];
   char buffer2[10];
}

void main() {
  function(1,2,3);
}
我正在寻找以下问题的答案:

  • 地址是如何工作的,我的意思是(main+0),(main+1),(main+3)
  • 总的来说,为什么要使用$0xfffffff0,%esp
  • 在函数中,为什么要使用%gs:0x14,%eax,%eax,-0xc(%ebp)
  • 如果有人能够解释,一步一步地发生,那将是非常感激的
  • 出现诸如
    main+0
    main+1
    main+3
    main+6
    等“奇怪”地址的原因是,每条指令占用的字节数可变。例如:

    main+0: push %ebp
    
    是一条单字节指令,因此下一条指令位于
    main+1
    。另一方面,

    main+3: and $0xfffffff0,%esp
    
    是一条三字节指令,因此之后的下一条指令位于
    main+6

    而且,由于您在注释中询问为什么
    movl
    似乎采用了可变的字节数,对此的解释如下

    指令长度不仅取决于操作码(如
    movl
    ),还取决于操作数的寻址模式(操作码所操作的对象)。我没有专门检查你的代码,但我怀疑

    movl $0x1,(%esp)
    
    指令可能更短,因为不涉及偏移量-它只使用
    esp
    作为地址。鉴于:

    movl $0x2,0x4(%esp)
    
    需要
    movl$0x1、(%esp)
    所做的一切,以及偏移量
    0x4
    的额外字节

    事实上,这里有一个调试会话,显示了我的意思:

    Microsoft Windows XP [Version 5.1.2600]
    (C) Copyright 1985-2001 Microsoft Corp.
    
    c:\pax> debug
    -a
    0B52:0100 mov word ptr [di],7
    0B52:0104 mov word ptr [di+2],8
    0B52:0109 mov word ptr [di+0],7
    0B52:010E
    -u100,10d
    0B52:0100 C7050700      MOV     WORD PTR [DI],0007
    0B52:0104 C745020800    MOV     WORD PTR [DI+02],0008
    0B52:0109 C745000700    MOV     WORD PTR [DI+00],0007
    -q
    c:\pax> _
    
    您可以看到,第二条带偏移量的指令实际上与第一条不带偏移量的指令不同。它长了一个字节(5个字节而不是4个字节,以保持偏移量),并且实际上具有不同的编码
    c745
    ,而不是
    c705

    您还可以看到,您可以用两种不同的方式对第一条指令和第三条指令进行编码,但它们基本上做相同的事情


    和$0xfffff0,%esp
    指令是一种强制
    esp
    位于特定边界上的方法。这用于确保变量的正确对齐。现代处理器上的许多内存访问如果遵循对齐规则(例如,4字节值必须与4字节边界对齐),将更加高效。如果不遵守这些规则,一些现代处理器甚至会出现故障

    执行此指令后,将保证
    esp
    小于或等于其先前的值,并与16字节边界对齐


    gs:
    前缀仅仅意味着使用
    gs
    段寄存器来访问内存,而不是默认值

    指令
    mov%eax,-0xc(%ebp)
    意味着获取
    ebp
    寄存器的内容,减去12(
    0xc
    ),然后将
    eax
    的值放入该内存位置


    重新解释代码。您的
    函数
    函数基本上是一个大的禁止操作。生成的程序集仅限于堆栈帧设置和拆卸,以及一些使用上述
    %gs:14
    内存位置的堆栈帧损坏检查

    它将该位置的值(可能类似于
    0xdeadbeef
    )加载到堆栈帧中,执行其工作,然后检查堆栈以确保其未损坏

    在这种情况下,它的工作毫无意义。所以你看到的只是功能管理的东西

    堆栈设置发生在
    函数+0
    函数+12
    之间。之后的所有工作都是在
    eax
    中设置返回代码,并拆除堆栈框架,包括损坏检查

    类似地,
    main
    由堆栈框架设置组成,按下
    函数的参数,调用
    函数,拆下堆栈框架并退出

    注释已插入到以下代码中:

    0x08048428 <main+0>:    push   %ebp                 ; save previous value.
    0x08048429 <main+1>:    mov    %esp,%ebp            ; create new stack frame.
    0x0804842b <main+3>:    and    $0xfffffff0,%esp     ; align to boundary.
    0x0804842e <main+6>:    sub    $0x10,%esp           ; make space on stack.
    
    0x08048431 <main+9>:    movl   $0x3,0x8(%esp)       ; push values for function.
    0x08048439 <main+17>:   movl   $0x2,0x4(%esp)
    0x08048441 <main+25>:   movl   $0x1,(%esp)
    0x08048448 <main+32>:   call   0x8048404 <function> ; and call it.
    
    0x0804844d <main+37>:   leave                       ; tear down frame.
    0x0804844e <main+38>:   ret                         ; and exit.
    
    0x08048404 <func+0>:    push   %ebp                 ; save previous value.
    0x08048405 <func+1>:    mov    %esp,%ebp            ; create new stack frame.
    0x08048407 <func+3>:    sub    $0x28,%esp           ; make space on stack.
    0x0804840a <func+6>:    mov    %gs:0x14,%eax        ; get sentinel value.
    0x08048410 <func+12>:   mov    %eax,-0xc(%ebp)      ; put on stack.
    
    0x08048413 <func+15>:   xor    %eax,%eax            ; set return code 0.
    
    0x08048415 <func+17>:   mov    -0xc(%ebp),%eax      ; get sentinel from stack.
    0x08048418 <func+20>:   xor    %gs:0x14,%eax        ; compare with actual.
    0x0804841f <func+27>:   je     <func+34>            ; jump if okay.
    0x08048421 <func+29>:   call   <_stk_chk_fl>        ; otherwise corrupted stack.
    0x08048426 <func+34>:   leave                       ; tear down frame.
    0x08048427 <func+35>:   ret                         ; and exit.
    
    然后sentinel将被覆盖,函数末尾的检查将检测到这一点,调用failure函数让您知道,然后可能会中止,以避免任何其他问题

    如果将
    0xdeadbeef
    放在堆栈上,并将其更改为其他内容,则带有
    0xdeadbeef
    xor
    将产生一个非零值,该值在带有
    je
    指令的代码中检测到

    此处对相关位进行了解释:

              mov    %gs:0x14,%eax     ; get sentinel value.
              mov    %eax,-0xc(%ebp)   ; put on stack.
    
              ;; Weave your function
              ;;   magic here.
    
              mov    -0xc(%ebp),%eax   ; get sentinel back from stack.
              xor    %gs:0x14,%eax     ; compare with original value.
              je     stack_ok          ; zero/equal means no corruption.
              call   stack_bad         ; otherwise corrupted stack.
    stack_ok: leave                    ; tear down frame.
    

    和平已经给出了一个明确的答案。然而,为了完整性,我想我应该添加一个注释,让GCC本身向您展示它生成的程序集

    GCC的
    -S
    选项告诉它停止编译并将程序集写入文件。通常,它要么将该文件传递给汇编程序,要么对某些目标直接将对象文件写入自身

    对于问题中的示例代码:

    #include <stdio.h>
    
    void function(int a, int b, int c) {
       char buffer1[5];
       char buffer2[10];
    }
    
    void main() {
      function(1,2,3);
    }
    
    有一点很明显,我的GCC(GCC(GCC)3.4.5(mingw vista special r3))默认情况下不包含堆栈检查代码。我想象有一个命令行选项,或者如果我有时间将我的MinGW安装升级到一个更新的GCC,它可能会这样做

    编辑:在Pax的推动下,这里是另一种让GCC做更多工作的方法

    C:\Documents and Settings\Ross\My Documents\testing>gcc -Wa,-al q3654898.c q3654898.c: In function `main': q3654898.c:8: warning: return type of 'main' is not `int' GAS LISTING C:\DOCUME~1\Ross\LOCALS~1\Temp/ccLg8pWC.s page 1 1 .file "q3654898.c" 2 .text 3 .globl _function 4 .def _function; .scl 2; .type 32; .endef 5 _function: 6 0000 55 pushl %ebp 7 0001 89E5 movl %esp, %ebp 8 0003 83EC28 subl $40, %esp 9 0006 C9 leave 10 0007 C3 ret 11 .def ___main; .scl 2; .type 32; .endef 12 .globl _main 13 .def _main; .scl 2; .type 32; .endef 14 _main: 15 0008 55 pushl %ebp 16 0009 89E5 movl %esp, %ebp 17 000b 83EC18 subl $24, %esp 18 000e 83E4F0 andl $-16, %esp 19 0011 B8000000 movl $0, %eax 19 00 20 0016 83C00F addl $15, %eax 21 0019 83C00F addl $15, %eax 22 001c C1E804 shrl $4, %eax 23 001f C1E004 sall $4, %eax 24 0022 8945FC movl %eax, -4(%ebp) 25 0025 8B45FC movl -4(%ebp), %eax 26 0028 E8000000 call __alloca 26 00 27 002d E8000000 call ___main 27 00 28 0032 C7442408 movl $3, 8(%esp) 28 03000000 29 003a C7442404 movl $2, 4(%esp) 29 02000000 30 0042 C7042401 movl $1, (%esp) 30 000000 31 0049 E8B2FFFF call _function 31 FF 32 004e C9 leave 33 004f C3 ret C:\Documents and Settings\Ross\My Documents\testing> C:\Documents and Settings\Ross\My Documents\testing>gcc-Wa,-al q3654898.C q3654898.c:在函数“main”中: q3654898.c:8:警告:“main”的返回类型不是“int” 气体清单C:\DOCUME~1\Ross\LOCALS~1\Temp/ccLg8pWC.s第1页 1.文件“q3654898.c” 2.文本 3.全局函数 4.定义函数。症状自评量表2。类型 32; .恩德夫 5_功能: 6 0000 55%推力ebp 7 0001 89E5
    #include <stdio.h>
    
    void function(int a, int b, int c) {
       char buffer1[5];
       char buffer2[10];
    }
    
    void main() {
      function(1,2,3);
    }
    
    .file "q3654898.c" .text .globl _function .def _function; .scl 2; .type 32; .endef _function: pushl %ebp movl %esp, %ebp subl $40, %esp leave ret .def ___main; .scl 2; .type 32; .endef .globl _main .def _main; .scl 2; .type 32; .endef _main: pushl %ebp movl %esp, %ebp subl $24, %esp andl $-16, %esp movl $0, %eax addl $15, %eax addl $15, %eax shrl $4, %eax sall $4, %eax movl %eax, -4(%ebp) movl -4(%ebp), %eax call __alloca call ___main movl $3, 8(%esp) movl $2, 4(%esp) movl $1, (%esp) call _function leave ret C:\Documents and Settings\Ross\My Documents\testing>gcc -Wa,-al q3654898.c q3654898.c: In function `main': q3654898.c:8: warning: return type of 'main' is not `int' GAS LISTING C:\DOCUME~1\Ross\LOCALS~1\Temp/ccLg8pWC.s page 1 1 .file "q3654898.c" 2 .text 3 .globl _function 4 .def _function; .scl 2; .type 32; .endef 5 _function: 6 0000 55 pushl %ebp 7 0001 89E5 movl %esp, %ebp 8 0003 83EC28 subl $40, %esp 9 0006 C9 leave 10 0007 C3 ret 11 .def ___main; .scl 2; .type 32; .endef 12 .globl _main 13 .def _main; .scl 2; .type 32; .endef 14 _main: 15 0008 55 pushl %ebp 16 0009 89E5 movl %esp, %ebp 17 000b 83EC18 subl $24, %esp 18 000e 83E4F0 andl $-16, %esp 19 0011 B8000000 movl $0, %eax 19 00 20 0016 83C00F addl $15, %eax 21 0019 83C00F addl $15, %eax 22 001c C1E804 shrl $4, %eax 23 001f C1E004 sall $4, %eax 24 0022 8945FC movl %eax, -4(%ebp) 25 0025 8B45FC movl -4(%ebp), %eax 26 0028 E8000000 call __alloca 26 00 27 002d E8000000 call ___main 27 00 28 0032 C7442408 movl $3, 8(%esp) 28 03000000 29 003a C7442404 movl $2, 4(%esp) 29 02000000 30 0042 C7042401 movl $1, (%esp) 30 000000 31 0049 E8B2FFFF call _function 31 FF 32 004e C9 leave 33 004f C3 ret C:\Documents and Settings\Ross\My Documents\testing>
    void function(int a, int b, int c) {
       char buffer1[5];
       char buffer2[10];
    }
    
    /* corrected calling convention of main() */
    int main() {
       function(1,2,3);
       return 0;
    }
    
    .globl _function
    _function:
        pushl   %ebp
        movl    %esp, %ebp
        pushl   %ebx
        subl    $36, %esp
        call    L4
    "L00000000001$pb":
    L4:
        popl    %ebx
        leal    L___stack_chk_guard$non_lazy_ptr-"L00000000001$pb"(%ebx), %eax
        movl    (%eax), %eax
        movl    (%eax), %edx
        movl    %edx, -12(%ebp)
        xorl    %edx, %edx
        leal    L___stack_chk_guard$non_lazy_ptr-"L00000000001$pb"(%ebx), %eax
        movl    (%eax), %eax
        movl    -12(%ebp), %edx
        xorl    (%eax), %edx
        je      L3
        call    ___stack_chk_fail
    L3:
        addl    $36, %esp
        popl    %ebx
        leave
        ret
    .globl _main
    _main:
        pushl   %ebp
        movl    %esp, %ebp
        subl    $24, %esp
        movl    $3, 8(%esp)
        movl    $2, 4(%esp)
        movl    $1, (%esp)
        call    _function
        movl    $0, %eax
        leave
        ret
    
        .text
    .globl _function
    _function:
        pushl   %ebp
        movl    %esp, %ebp
        leave
        ret
    .globl _main
    _main:
        pushl   %ebp
        movl    %esp, %ebp
        movl    $0, %eax
        leave
        ret