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

Adi*_*Adi 15 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);
}
Run Code Online (Sandbox Code Playgroud)

以下是两个反汇编代码mainfunction

gdb) disass main
Dump of assembler code for function main:
0x08048428 <main+0>:    push   %ebp
0x08048429 <main+1>:    mov    %esp,%ebp
0x0804842b <main+3>:    and    $0xfffffff0,%esp
0x0804842e <main+6>:    sub    $0x10,%esp
0x08048431 <main+9>:    movl   $0x3,0x8(%esp)
0x08048439 <main+17>:   movl   $0x2,0x4(%esp)
0x08048441 <main+25>:   movl   $0x1,(%esp)
0x08048448 <main+32>:   call   0x8048404 <function>
0x0804844d <main+37>:   leave  
0x0804844e <main+38>:   ret
End of assembler dump.

(gdb) disass function
Dump of assembler code for function function:
0x08048404 <function+0>:    push   %ebp
0x08048405 <function+1>:    mov    %esp,%ebp
0x08048407 <function+3>:    sub    $0x28,%esp
0x0804840a <function+6>:    mov    %gs:0x14,%eax
0x08048410 <function+12>:   mov    %eax,-0xc(%ebp)
0x08048413 <function+15>:   xor    %eax,%eax
0x08048415 <function+17>:   mov    -0xc(%ebp),%eax
0x08048418 <function+20>:   xor    %gs:0x14,%eax
0x0804841f <function+27>:   je     0x8048426 <function+34>
0x08048421 <function+29>:   call   0x8048340 <__stack_chk_fail@plt>
0x08048426 <function+34>:   leave  
0x08048427 <function+35>:   ret    
End of assembler dump.
Run Code Online (Sandbox Code Playgroud)

我正在寻找以下事情的答案:

  1. 寻址如何工作,我的意思是(主+ 0),(主+ 1),(主+ 3)
  2. 总的来说,为什么要使用$ 0xfffffff0,%esp
  3. 在函数中,为什么使用%gs:0x14,%eax,%eax,-0xc(%ebp).
  4. 如果有人能够一步一步地解释,那将非常感激.

pax*_*blo 41

的原因"奇怪"地址如main+0,main+1,main+3,main+6等等,是因为每个指令占用的可变字节数.例如:

main+0: push %ebp
Run Code Online (Sandbox Code Playgroud)

是一个单字节指令,所以下一条指令是main+1.另一方面,

main+3: and $0xfffffff0,%esp
Run Code Online (Sandbox Code Playgroud)

是一个三字节指令,因此之后的下一条指令是main+6.

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

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

movl $0x1,(%esp)
Run Code Online (Sandbox Code Playgroud)

指令可能更短,因为没有涉及偏移 - 它只是esp用作地址.而类似的东西:

movl $0x2,0x4(%esp)
Run Code Online (Sandbox Code Playgroud)

需要做的所有事情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> _
Run Code Online (Sandbox Code Playgroud)

您可以看到带有偏移的第二条指令实际上与没有它的第一条指令不同.它长一个字节(5个字节而不是4个,用于保持偏移量)并且实际上具有不同的编码c745而不是c705.

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


and $0xfffffff0,%esp指令是强制esp在特定边界上的一种方式.这用于确保变量的正确对齐.如果现代处理器遵循对齐规则(例如,4字节值必须与4字节边界对齐),那么现代处理器上的许多存储器访问将更有效.如果您不遵守这些规则,一些现代处理器甚至会引发故障.

在此指令之后,您可以保证esp小于或等于其先前的值与16字节边界对齐.


gs:前缀只是意味着使用该gs段寄存器访问内存,而不是默认.

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


重新解释代码.你的function功能基本上是一个很大的无操作.生成的程序集仅限于堆栈帧设置和拆除,以及使用上述%gs:14存储器位置的一些堆栈帧损坏检查.

它将该位置的值(可能是类似的东西0xdeadbeef)加载到堆栈框架中,完成其工作,然后检查堆栈以确保它没有被破坏.

在这种情况下,它的工作一无所获.所有你看到的是功能管理的东西.

堆栈设置发生在function+0和之间function+12.之后的一切都是设置返回代码eax并拆除堆栈框架,包括损坏检查.

同样,main由堆栈帧设置,推送参数function,调用function,拆除堆栈帧和退出组成.

评论已插入以下代码中:

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.
Run Code Online (Sandbox Code Playgroud)

我认为其原因%gs:0x14可能从上面看出来,但为了以防万一,我将在此详述.

它使用这个值(一个sentinel)放入当前的堆栈帧,这样,如果函数中的某些东西愚蠢地写了一些东西,比如将1024字节写入堆栈上创建的20字节数组,或者在你的情况下:

char buffer1[5];
strcpy (buffer1, "Hello there, my name is Pax.");
Run Code Online (Sandbox Code Playgroud)

那么哨兵将被覆盖,并且函数末尾的检查将检测到,调用失败函数让你知道,然后可能中止,以避免任何其他问题.

如果它被放置0xdeadbeef到堆栈上并且这被改变为其他东西,那么xorwith 0xdeadbeef将产生非零值,该值在代码中用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.
Run Code Online (Sandbox Code Playgroud)