『coredump問題原理探究』Linux 86版4.2節関数の逆方向の順序構造
8690 ワード
まず例を見てみましょう.
main関数のアセンブリを見てみましょう.
10行以上のコードが、非常に多くのアセンブリ文になり、非常に怖いです.実際、そんなに恐れる必要はありません.
まずcall命令の場所を見てみると,call命令は呼び出し関数であるため,このような範囲を大まかに定めることができる.
まず、3つのcallコマンドのアドレスを見てみましょう.
わかりますが、
このセグメントのアセンブリのアドレスは、最初のcall命令アドレス0 x 080485 b 0よりも少ないので、コードにほぼ対応します.
そして
このセグメントのアセンブリは、アドレスが0 x 080485 b 0より大きく、0 x 080485 d 2より小さいため、コードにほぼ対応する.
また
このセグメントのアセンブリアドレスは0 x 080485 d 2より大きく、0 x 080485 f 4より小さいので、それらはほぼコードに対応する.
そして
このセグメントのアセンブリアドレスは0 x 080485 f 4より大きいので、コードに対応します.
ほら、分析アセンブリは、そんなに怖いわけではありません.しかし、上にはコンパイラによって生成された命令や、関数呼び出し時にパラメータをスタックに入れる命令があるため、これらの命令をフィルタリングするには、最初のセグメントアセンブリのみを例に挙げる(すなわち、最初のscanf呼び出し前のアセンブリ):
第3章から分かるように、
関数の先頭に属するフィーチャーコマンドなので、コンパイラによって自動的に生成されます.
そして
espを調整し、ローカル変数空間を割り当てるために使用され、コンパイラが自動的に生成します.だから、最初のセグメントのアセンブリは残ります.
はい、そうです.
対応する.によって
3つのパラメータがあり、3章の内容によると、
ちょうどscanfの3つのパラメータをスタックに入れる操作で、実行時に0 x 80486 b 4が指す内容を得ることができます.
つまり、ただ
こそ和
対応する.
まとめ:
シーケンス構造の逆方向はアセンブリ基礎を非常に試すが,関数呼び出しがある場合はまずcall命令を探し,call命令に基づいて範囲を区分し,コンパイラが自動的に生成する命令をフィルタリングする.
#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命令に基づいて範囲を区分し,コンパイラが自動的に生成する命令をフィルタリングする.