gcc 调试汇编 以及 对函数堆栈 的观察

来源:互联网 发布:js触发radio 事件 编辑:程序博客网 时间:2024/05/20 16:33
每一个函数独占自己的栈帧空间。当前正在运行的函数的栈帧总是在栈顶。Win32系统提供两个特殊的寄存器用于标识位于系统栈顶端的栈帧。

  4.1.4 寄存器与函数栈帧

  每一个函数独占自己的栈帧空间。当前正在运行的函数的栈帧总是在栈顶。Win32系统提供两个特殊的寄存器用于标识位于系统栈顶端的栈帧。

  (1)ESP:栈指针寄存器(extendedstackpointer),其内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的栈顶。

  (2)EBP:基址指针寄存器(extendedbasepointer),其内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的底部。

  注意:EBP指向当前位于系统栈最上边一个栈帧的底部,而不是系统栈的底部。严格说来,“栈帧底部”和“栈底”是不同的概念,本书在叙述中将坚持使用“栈帧底部”这一提法以示区别;ESP所指的栈帧顶部和系统栈的顶部是同一个位置,所以后面叙述中并不严格区分“栈帧顶部”和“栈顶”的概念。请您注意这里的差异,不要产生概念混淆。

  寄存器对栈帧的标识作用如图4.1.5所示。

 
图4.1.5 栈帧寄存器ESP与EBP的作用
  函数栈帧:ESP和EBP之间的内存空间为当前栈帧,EBP标识了当前栈帧的底部,ESP标识了当前栈帧的顶部。

  在函数栈帧中,一般包含以下几类重要信息。

  (1)局部变量:为函数局部变量开辟的内存空间。

  (2)栈帧状态值:保存前栈帧的顶部和底部(实际上只保存前栈帧的底部,前栈帧的顶部可以通过堆栈平衡计算得到),用于在本帧被弹出后恢复出上一个栈帧。

  (3)函数返回地址:保存当前函数调用前的“断点”信息,也就是函数调用前的指令位置,以便在函数返回时能够恢复到函数被调用前的代码区中继续执行指令。

  题外话:函数栈帧的大小并不固定,一般与其对应函数的局部变量多少有关。在后面调试实验中您会发现,函数运行过程中,其栈帧大小也是在不停变化的。

  除了与栈相关的寄存器外,您还需要记住另一个至关重要的寄存器。

  EIP:指令寄存器(extendedinstructionpointer),其内存放着一个指针,该指针永远指向下一条等待执行的指令地址,其作用如图4.1.6所示。

  图4.1.6 指令寄存器EIP的作用

  可以说如果控制了EIP寄存器的内容,就控制了进程——我们让EIP指向哪里,CPU就会去执行哪里的指令。在本章第4节中我们会介绍控制EIP劫持进程的原理及实验。

/**
********************************/display ...

undisplay <编号>

display,设置程序中断后欲显示的数据及其格式。例如,如果希望每次程序中断后可以看到即将被执行的下一条汇编指令,可以使用命令“display /i $pc”其中 $pc 代表当前汇编指令,/i 表示以十六进行显示。当需要关心汇编代码时,此命令相当有用。

undispaly,取消先前的display设置,编号从1开始递增。

(gdb) display /i $pc

(gdb) undisplay 1

下面使用命令“b *main”在 main 函数的 prolog 代码处设置断点(prolog、epilog,分别表示编译器在每个函数的开头和结尾自行插入的代码)
/***PS:作为学生最高兴的事 莫过于发现老师的错误,而这种错误不是口误和笔误,这是件可悲的事,也是件令人兴奋的事。**这是关于汇编 与 编译原理 堆栈帧 布局 的知识  **判断c运行时环境的程序*/int  static_variable = 5;void f(){register int i1, i2, i3, i4, i5, i6, i7, i8, i9, i10;register char *c1, *c2, *c3, *c4, *c5, *c6, *c7, *c8, *c9, *c10;externint a_very_long_name_to_see_how_long_they_can_be;double db1;int    func_retint();double func_ret_double();char *func_ret_char_ptr();/*** 寄存器变量的最大数量*/i1 = 1; i2 = 2; i3 = 3; i4 = 4; i5 = 5; i6 = 6; i7 = 7;i8 = 8; i9 = 9; i10 = 10;c1 = (char *)110; c2 = (char *)120; c3 = (char *)130;c4 = (char *)140; c5 = (char *)150; c6 = (char *)160;c7 = (char *)170; c8 = (char *)180; c9 = (char *)190;c10 = (char *)200;/***外部名字*/a_very_long_name_to_see_how_long_they_can_be = 1;/***函数调用/返回协议,堆栈帧(过程活动记录)*/i2 = func_ret_int(10, i1, i10);}int func_ret_int( int a, int b, register int c){int d;d = b - 6;return a + b + c;}


;形如 %ebp 是伪指令 取值的意思;ebp esp  2个指针寄存器;esi edi 2 个变址和指针寄存器;4个数据寄存器 EAX EBX ECX EDX;6个段寄存器 ES CS SS DS FS GS;1个指令指针寄存器 EIP  ;1个标志寄存器(EFLAGS).file"main.c"; 静态初始化.globl static_variable.data.align 4.typestatic_variable, @object.sizestatic_variable, 4static_variable:.long5;代码段.text.globl f.typef, @functionf:;函数序pushl%ebpmovl%esp, %ebppushl%esipushl%ebxsubl$32, %esp;留一个栈空间大小32字节  用于放参数 以及 返回值;函数体;由于下面要使用 i1 和 i10 固这里才将其放入 寄存器中movl$1, %ebx ;放入通用寄存器中movl$10, %esi;同上movl$1, a_very_long_name_to_see_how_long_they_can_be;三个参数  10 , i1 , i10  入堆栈 这里是倒序入栈movl%esi, 8(%esp);第3个参数movl%ebx, 4(%esp);第2个参数movl$10, (%esp);第1个参数callfunc_ret_intaddl$32, %esp;堆栈指针 还原 到 原sp值(上面-32处时的值) 在调用函数中开始和结束的时候 没有改变;还原寄存器 popl%ebx;什么还原?popl%esi;什么还原?popl%ebp;帧指针还原ret.sizef, .-f.globl func_ret_int.typefunc_ret_int, @functionfunc_ret_int:;保护现场 和 开辟自己的帧空间pushl%ebp;帧指针入栈 esp 减 8 即是 移动8个字节  这里低4字节存ebp 高4字节存返回地址movl%esp, %ebp;堆栈指针的当前值 被 复制到桢指针subl$16, %esp ;堆栈指针-16 ,这将创建空间用于保存局部变量 和 保存的寄存器值 最低位放旧的ebp 次底位放0 。这个0是怎么放入的?哪个寄存器的值?movl16(%ebp), %ecx ;第3个参数放入寄存器 虽然声明为 register 变量但是 还是要入堆栈的,只不过以后访问的时候是访问寄存器,但是 这时 变量的值有3个备份?如果这个变量不是经常使用,这既占用寄存器 又 多处备份 的操作是花时间和空间的。反而会增加 时间空间的开销。movl12(%ebp), %eax ;第2个参数入寄存器subl$6, %eax ; 这里eax变成了中间变量 。 没有变量d的出现movl%eax, -4(%ebp);将计算的中间结果 放入局部变量区域中movl12(%ebp), %eax;将第2个参数放入中间寄存器中movl8(%ebp), %edx;将第1个参数放入另一个零时数据寄存器中leal(%edx,%eax), %eax;第一个参数,第2个参数,第2个参数addl%ecx, %eax;第3个参数,第2个参数;函数跛leave ;移动 esp 函数栈里的内容并没有被清除ret;做完后esp 移动4字节.sizefunc_ret_int, .-func_ret_int.ident"GCC: (Ubuntu/Linaro 4.4.4-14ubuntu5) 4.4.5".section.note.GNU-stack,"",@progbits

我们发现不管你是否声明为寄存器变量 ,参数还是要入函数堆栈的。
TITLEmain.c.386Pinclude listing.incif @Version gt 510.model FLATelse_TEXTSEGMENT PARA USE32 PUBLIC 'CODE'_TEXTENDS_DATASEGMENT DWORD USE32 PUBLIC 'DATA'_DATAENDSCONSTSEGMENT DWORD USE32 PUBLIC 'CONST'CONSTENDS_BSSSEGMENT DWORD USE32 PUBLIC 'BSS'_BSSENDS$$SYMBOLSSEGMENT BYTE USE32 'DEBSYM'$$SYMBOLSENDS$$TYPESSEGMENT BYTE USE32 'DEBTYP'$$TYPESENDS_TLSSEGMENT DWORD USE32 PUBLIC 'TLS'_TLSENDS;COMDAT _main_TEXTSEGMENT PARA USE32 PUBLIC 'CODE'_TEXTENDS;COMDAT _func_ret_int_TEXTSEGMENT PARA USE32 PUBLIC 'CODE'_TEXTENDSFLATGROUP _DATA, CONST, _BSSASSUMECS: FLAT, DS: FLAT, SS: FLATendifPUBLIC_static_variable_DATASEGMENT_static_variable DD 05H_DATAENDSPUBLIC_mainPUBLIC_func_ret_intEXTRN__chkesp:NEAR;COMDAT _main_TEXTSEGMENT_i1$ = -4_i2$ = -8_i3$ = -12_i4$ = -16_i5$ = -20_i6$ = -24_i7$ = -28_i8$ = -32_i9$ = -36_i10$ = -40_c1$ = -44_c2$ = -48_c3$ = -52_c4$ = -56_c5$ = -60_c6$ = -64_c7$ = -68_c8$ = -72_c9$ = -76_c10$ = -80_mainPROC NEAR; COMDAT; sp: 堆栈指针 bp:帧指针 ip:指令地址指针 ; 指令地址(虚拟地址)     指令; 10   : {  0000055 push ebp;保存栈帧指针  000018b ec mov ebp, esp  0000381 ec 98 00 0000 sub esp, 152; 00000098H ;创建空间用于保存局部变量和被保存的寄存器值  0000953 push ebx  0000a56 push esi  0000b57 push edi  0000c8d bd 68 ff ffff lea edi, DWORD PTR [ebp-152]  00012b9 26 00 00 00 mov ecx, 38; 00000026H ;?????为什么是26H  00017b8 cc cc cc cc mov eax, -858993460; ccccccccH;????  0001cf3 ab rep stosd                              ;  将初始化为ccccccccH; 范围为26H 做完之后CX为0; 11   : register int i1, i2, i3, i4, i5, i6, i7, i8, i9, i10;; 12   : register char *c1, *c2, *c3, *c4, *c5, *c6, *c7, *c8, *c9, *c10;; 13   : ; 14   : //externint a_very_long_name_to_see_how_long_they_can_be;; 15   : ; 16   : double db1;; 17   : ; 18   : int    func_ret_int();; 19   : double func_ret_double();; 20   : char *func_ret_char_ptr();; 21   : ; 22   : /*; 23   : ** 寄存器变量的最大数量; 24   : */; 25   : ; 26   : i1 = 1; i2 = 2; i3 = 3; i4 = 4; i5 = 5; i6 = 6; i7 = 7;此时bp:0x12ff80  0001ec7 45 fc 01 0000 00 mov DWORD PTR _i1$[ebp], 1  00025c7 45 f8 02 0000 00 mov DWORD PTR _i2$[ebp], 2  0002cc7 45 f4 03 0000 00 mov DWORD PTR _i3$[ebp], 3  00033c7 45 f0 04 0000 00 mov DWORD PTR _i4$[ebp], 4  0003ac7 45 ec 05 0000 00 mov DWORD PTR _i5$[ebp], 5  00041c7 45 e8 06 0000 00 mov DWORD PTR _i6$[ebp], 6  00048c7 45 e4 07 0000 00 mov DWORD PTR _i7$[ebp], 7; 27   : i8 = 8; i9 = 9; i10 = 10;  0004fc7 45 e0 08 0000 00 mov DWORD PTR _i8$[ebp], 8  00056c7 45 dc 09 0000 00 mov DWORD PTR _i9$[ebp], 9  0005dc7 45 d8 0a 0000 00 mov DWORD PTR _i10$[ebp], 10 ; 0000000aH; 28   :    ;此时0012FF58; 29   : c1 = (char *)110; c2 = (char *)120; c3 = (char *)130;  00064c7 45 d4 6e 0000 00 mov DWORD PTR _c1$[ebp], 110 ; 0000006eH  0006bc7 45 d0 78 0000 00 mov DWORD PTR _c2$[ebp], 120 ; 00000078H  00072c7 45 cc 82 0000 00 mov DWORD PTR _c3$[ebp], 130 ; 00000082H; 30   : c4 = (char *)140; c5 = (char *)150; c6 = (char *)160;  00079c7 45 c8 8c 0000 00 mov DWORD PTR _c4$[ebp], 140 ; 0000008cH  00080c7 45 c4 96 0000 00 mov DWORD PTR _c5$[ebp], 150 ; 00000096H  00087c7 45 c0 a0 0000 00 mov DWORD PTR _c6$[ebp], 160 ; 000000a0H; 31   : c7 = (char *)170; c8 = (char *)180; c9 = (char *)190;  0008ec7 45 bc aa 0000 00 mov DWORD PTR _c7$[ebp], 170 ; 000000aaH  00095c7 45 b8 b4 0000 00 mov DWORD PTR _c8$[ebp], 180 ; 000000b4H  0009cc7 45 b4 be 0000 00 mov DWORD PTR _c9$[ebp], 190 ; 000000beH; 32   : c10 = (char *)200;  000a3c7 45 b0 c8 0000 00 mov DWORD PTR _c10$[ebp], 200 ; 000000c8H;  ;此时0012FF30; 33   : ; 34   : /*; 35   : **外部名字; 36   : */; 37   : //a_very_long_name_to_see_how_long_they_can_be = 1;; 38   : ; 39   : /*; 40   : **函数调用/返回协议,堆栈帧(过程活动记录); 41   : */; 42   : ; 43   : i2 = func_ret_int(10, i1, i10);  000aa8b 45 d8 mov eax, DWORD PTR _i10$[ebp];第3个参数入  000ad50 push eax  000ae8b 4d fc mov ecx, DWORD PTR _i1$[ebp]  000b151 push ecx  000b26a 0a push 10; 0000000aH  000b4e8 00 00 00 00 call _func_ret_int  000b983 c4 0c add esp, 12; 0000000cH  000bc89 45 f8 mov DWORD PTR _i2$[ebp], eax; 44   : }  000bf5f pop edi  000c05e pop esi  000c15b pop ebx  000c281 c4 98 00 0000 add esp, 152; 00000098H  000c83b ec cmp ebp, esp  000cae8 00 00 00 00 call __chkesp  000cf8b e5 mov esp, ebp  000d15d pop ebp  000d2c3 ret 0_mainENDP_TEXTENDS;COMDAT _func_ret_int_TEXTSEGMENT_a$ = 8_b$ = 12_c$ = 16_d$ = -4_func_ret_int PROC NEAR; COMDAT; 47   : {  0000055 push ebp  000018b ec mov ebp, esp  0000383 ec 44 sub esp, 68; 00000044H;创建空间用于保存局部变量和被保存的寄存器值  0000653 push ebx  0000756 push esi  0000857 push edi  000098d 7d bc lea edi, DWORD PTR [ebp-68]  0000cb9 11 00 00 00 mov ecx, 17; 00000011H  00011b8 cc cc cc cc mov eax, -858993460; ccccccccH  同上fe84---fec8 初始化为 cccc..  00016f3 ab rep stosd; 48   : int d;; 49   : ; 50   : d = b - 6;  000188b 45 0c mov eax, DWORD PTR _b$[ebp]  0001b83 e8 06 sub eax, 6  0001e89 45 fc mov DWORD PTR _d$[ebp], eax; 51   : return a + b + c;  000218b 45 08 mov eax, DWORD PTR _a$[ebp]  0002403 45 0c add eax, DWORD PTR _b$[ebp]  0002703 45 10 add eax, DWORD PTR _c$[ebp]; 52   : ; 53   : }  0002a5f pop edi  0002b5e pop esi  0002c5b pop ebx  0002d8b e5 mov esp, ebp  0002f5d pop ebp  00030c3 ret 0_func_ret_int ENDP_TEXTENDSEND

发现这里与GCC不同的是 对所有的变量进行了初始化。但是并没有看到寄存器变量 与寄存器变量 存储的区别
于是又加了个 
int noreg;
 noreg = 0xffff;
 i1 = noreg;
 noreg = i1;
发现赋值的方式 和 存储的位置还是与上面的寄存器的一样,就感觉vc把register关键字 定义为空字符串 。 
	
				
		
原创粉丝点击