『coredump問題原理探究』Linux 86版4.2節関数の逆方向の順序構造


まず例を見てみましょう.
#include <stdio.h>

int main()
{
     int a = 0, b = 0, c = 0, d = 0;
     scanf( "%d,%d", &a,&b );
     a += b;
     scanf( "%d", &c );
     a += c;
     scanf( "%d", &d );
     a += d;
 
     return a;
}

main関数のアセンブリを見てみましょう.
(gdb) disassemble main
Dump of assembler code for function main:
   0x08048570 <+0>:     push   %ebp
   0x08048571 <+1>:     mov    %esp,%ebp
   0x08048573 <+3>:     and    $0xfffffff0,%esp
   0x08048576 <+6>:     sub    $0x20,%esp
   0x08048579 <+9>:     movl   $0x0,0x1c(%esp)
   0x08048581 <+17>:    movl   $0x0,0x18(%esp)
   0x08048589 <+25>:    movl   $0x0,0x14(%esp)
   0x08048591 <+33>:    movl   $0x0,0x10(%esp)
   0x08048599 <+41>:    lea    0x18(%esp),%eax
   0x0804859d <+45>:    mov    %eax,0x8(%esp)
   0x080485a1 <+49>:    lea    0x1c(%esp),%eax
   0x080485a5 <+53>:    mov    %eax,0x4(%esp)
   0x080485a9 <+57>:    movl   $0x80486b4,(%esp)
   0x080485b0 <+64>:    call   0x8048440 <scanf@plt>
   0x080485b5 <+69>:    mov    0x1c(%esp),%edx
   0x080485b9 <+73>:    mov    0x18(%esp),%eax
   0x080485bd <+77>:    add    %edx,%eax
   0x080485bf <+79>:    mov    %eax,0x1c(%esp)
   0x080485c3 <+83>:    lea    0x14(%esp),%eax
   0x080485c7 <+87>:    mov    %eax,0x4(%esp)
   0x080485cb <+91>:    movl   $0x80486ba,(%esp)
   0x080485d2 <+98>:    call   0x8048440 <scanf@plt>
   0x080485d7 <+103>:   mov    0x1c(%esp),%edx
   0x080485db <+107>:   mov    0x14(%esp),%eax
   0x080485df <+111>:   add    %edx,%eax
   0x080485e1 <+113>:   mov    %eax,0x1c(%esp)
   0x080485e5 <+117>:   lea    0x10(%esp),%eax
   0x080485e9 <+121>:   mov    %eax,0x4(%esp)
   0x080485ed <+125>:   movl   $0x80486ba,(%esp)
   0x080485f4 <+132>:   call   0x8048440 <scanf@plt>
   0x080485f9 <+137>:   mov    0x1c(%esp),%edx
   0x080485fd <+141>:   mov    0x10(%esp),%eax
   0x08048601 <+145>:   add    %edx,%eax
   0x08048603 <+147>:   mov    %eax,0x1c(%esp)
   0x08048607 <+151>:   mov    0x1c(%esp),%eax
   0x0804860b <+155>:   jmp    0x8048615 <main+165>
   0x0804860d <+157>:   mov    %eax,(%esp)
   0x08048610 <+160>:   call   0x8048460 <_Unwind_Resume@plt>
   0x08048615 <+165>:   leave  
   0x08048616 <+166>:   ret    
End of assembler dump.

10行以上のコードが、非常に多くのアセンブリ文になり、非常に怖いです.実際、そんなに恐れる必要はありません.
まずcall命令の場所を見てみると,call命令は呼び出し関数であるため,このような範囲を大まかに定めることができる.
まず、3つのcallコマンドのアドレスを見てみましょう.
   0x080485b0 <+64>:    call   0x8048440 <scanf@plt>
   0x080485d2 <+98>:    call   0x8048440 <scanf@plt>
   0x080485f4 <+132>:   call   0x8048440 <scanf@plt>

わかりますが、
   0x08048570 <+0>:     push   %ebp
   0x08048571 <+1>:     mov    %esp,%ebp
   0x08048573 <+3>:     and    $0xfffffff0,%esp
   0x08048576 <+6>:     sub    $0x20,%esp
   0x08048579 <+9>:     movl   $0x0,0x1c(%esp)
   0x08048581 <+17>:    movl   $0x0,0x18(%esp)
   0x08048589 <+25>:    movl   $0x0,0x14(%esp)
   0x08048591 <+33>:    movl   $0x0,0x10(%esp)
   0x08048599 <+41>:    lea    0x18(%esp),%eax
   0x0804859d <+45>:    mov    %eax,0x8(%esp)
   0x080485a1 <+49>:    lea    0x1c(%esp),%eax
   0x080485a5 <+53>:    mov    %eax,0x4(%esp)
   0x080485a9 <+57>:    movl   $0x80486b4,(%esp)

このセグメントのアセンブリのアドレスは、最初のcall命令アドレス0 x 080485 b 0よりも少ないので、コードにほぼ対応します.
int a = 0, b = 0, c = 0, d = 0;

そして
   0x080485b5 <+69>:    mov    0x1c(%esp),%edx
   0x080485b9 <+73>:    mov    0x18(%esp),%eax
   0x080485bd <+77>:    add    %edx,%eax
   0x080485bf <+79>:    mov    %eax,0x1c(%esp)
   0x080485c3 <+83>:    lea    0x14(%esp),%eax
   0x080485c7 <+87>:    mov    %eax,0x4(%esp)
   0x080485cb <+91>:    movl   $0x80486ba,(%esp)

このセグメントのアセンブリは、アドレスが0 x 080485 b 0より大きく、0 x 080485 d 2より小さいため、コードにほぼ対応する.
a += b;

また
   0x080485d7 <+103>:   mov    0x1c(%esp),%edx
   0x080485db <+107>:   mov    0x14(%esp),%eax
   0x080485df <+111>:   add    %edx,%eax
   0x080485e1 <+113>:   mov    %eax,0x1c(%esp)
   0x080485e5 <+117>:   lea    0x10(%esp),%eax
   0x080485e9 <+121>:   mov    %eax,0x4(%esp)
   0x080485ed <+125>:   movl   $0x80486ba,(%esp)

このセグメントのアセンブリアドレスは0 x 080485 d 2より大きく、0 x 080485 f 4より小さいので、それらはほぼコードに対応する.
a += c;

そして
   0x080485f9 <+137>:   mov    0x1c(%esp),%edx
   0x080485fd <+141>:   mov    0x10(%esp),%eax
   0x08048601 <+145>:   add    %edx,%eax
   0x08048603 <+147>:   mov    %eax,0x1c(%esp)
   0x08048607 <+151>:   mov    0x1c(%esp),%eax
   0x0804860b <+155>:   jmp    0x8048615 <main+165>
   0x0804860d <+157>:   mov    %eax,(%esp)
   0x08048610 <+160>:   call   0x8048460 <_Unwind_Resume@plt>
   0x08048615 <+165>:   leave  
   0x08048616 <+166>:   ret    

このセグメントのアセンブリアドレスは0 x 080485 f 4より大きいので、コードに対応します.
a += d;

return a;

ほら、分析アセンブリは、そんなに怖いわけではありません.しかし、上にはコンパイラによって生成された命令や、関数呼び出し時にパラメータをスタックに入れる命令があるため、これらの命令をフィルタリングするには、最初のセグメントアセンブリのみを例に挙げる(すなわち、最初のscanf呼び出し前のアセンブリ):
   0x08048570 <+0>:     push   %ebp
   0x08048571 <+1>:     mov    %esp,%ebp
   0x08048573 <+3>:     and    $0xfffffff0,%esp
   0x08048576 <+6>:     sub    $0x20,%esp
   0x08048579 <+9>:     movl   $0x0,0x1c(%esp)
   0x08048581 <+17>:    movl   $0x0,0x18(%esp)
   0x08048589 <+25>:    movl   $0x0,0x14(%esp)
   0x08048591 <+33>:    movl   $0x0,0x10(%esp)
   0x08048599 <+41>:    lea    0x18(%esp),%eax
   0x0804859d <+45>:    mov    %eax,0x8(%esp)
   0x080485a1 <+49>:    lea    0x1c(%esp),%eax
   0x080485a5 <+53>:    mov    %eax,0x4(%esp)
   0x080485a9 <+57>:    movl   $0x80486b4,(%esp)

第3章から分かるように、
   0x08048570 <+0>:     push   %ebp
   0x08048571 <+1>:     mov    %esp,%ebp

関数の先頭に属するフィーチャーコマンドなので、コンパイラによって自動的に生成されます.
そして
   0x08048573 <+3>:     and    $0xfffffff0,%esp
   0x08048576 <+6>:     sub    $0x20,%esp

espを調整し、ローカル変数空間を割り当てるために使用され、コンパイラが自動的に生成します.だから、最初のセグメントのアセンブリは残ります.
   0x08048579 <+9>:     movl   $0x0,0x1c(%esp)
   0x08048581 <+17>:    movl   $0x0,0x18(%esp)
   0x08048589 <+25>:    movl   $0x0,0x14(%esp)
   0x08048591 <+33>:    movl   $0x0,0x10(%esp)
   0x08048599 <+41>:    lea    0x18(%esp),%eax
   0x0804859d <+45>:    mov    %eax,0x8(%esp)
   0x080485a1 <+49>:    lea    0x1c(%esp),%eax
   0x080485a5 <+53>:    mov    %eax,0x4(%esp)
   0x080485a9 <+57>:    movl   $0x80486b4,(%esp)

はい、そうです.
 int a = 0, b = 0, c = 0, d = 0;

対応する.によって
scanf( "%d,%d", &a,&b );

3つのパラメータがあり、3章の内容によると、
   0x08048599 <+41>:    lea    0x18(%esp),%eax
   0x0804859d <+45>:    mov    %eax,0x8(%esp)
   0x080485a1 <+49>:    lea    0x1c(%esp),%eax
   0x080485a5 <+53>:    mov    %eax,0x4(%esp)
   0x080485a9 <+57>:    movl   $0x80486b4,(%esp)

ちょうどscanfの3つのパラメータをスタックに入れる操作で、実行時に0 x 80486 b 4が指す内容を得ることができます.
gdb) tbreak *0x080485b0
Temporary breakpoint 1 at 0x80485b0
(gdb) r
Starting program: /home/buckxu/work/4/1/xuzhina_dump_c4_s1 

Temporary breakpoint 1, 0x080485b0 in main ()
(gdb) x /s 0x80486b4
0x80486b4 <__dso_handle+4>:      "%d,%d"

つまり、ただ
   0x08048579 <+9>:     movl   $0x0,0x1c(%esp)
   0x08048581 <+17>:    movl   $0x0,0x18(%esp)
   0x08048589 <+25>:    movl   $0x0,0x14(%esp)
   0x08048591 <+33>:    movl   $0x0,0x10(%esp)

こそ和
int a = 0, b = 0, c = 0, d = 0;

対応する.
 
まとめ:
シーケンス構造の逆方向はアセンブリ基礎を非常に試すが,関数呼び出しがある場合はまずcall命令を探し,call命令に基づいて範囲を区分し,コンパイラが自動的に生成する命令をフィルタリングする.