每一个函数独占自己的栈帧空间。当前正在运行的函数的栈帧总是在栈顶。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; extern int 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 .type static_variable, @object .size static_variable, 4 static_variable: .long 5 ;代码段 .text .globl f .type f, @function f: ;函数序 pushl %ebp movl %esp, %ebp pushl %esi pushl %ebx subl $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个参数 call func_ret_int addl $32, %esp;堆栈指针 还原 到 原sp值(上面-32处时的值) 在调用函数中开始和结束的时候 没有改变 ;还原寄存器 popl %ebx;什么还原? popl %esi;什么还原? popl %ebp;帧指针还原 ret .size f, .-f .globl func_ret_int .type func_ret_int, @function func_ret_int:;保护现场 和 开辟自己的帧空间 pushl %ebp;帧指针入栈 esp 减 8 即是 移动8个字节 这里低4字节存ebp 高4字节存返回地址 movl %esp, %ebp;堆栈指针的当前值 被 复制到桢指针 subl $16, %esp ;堆栈指针-16 ,这将创建空间用于保存局部变量 和 保存的寄存器值 最低位放旧的ebp 次底位放0 。这个0是怎么放入的?哪个寄存器的值? movl 16(%ebp), %ecx ;第3个参数放入寄存器 虽然声明为 register 变量但是 还是要入堆栈的,只不过以后访问的时候是访问寄存器,但是 这时 变量的值有3个备份?如果这个变量不是经常使用,这既占用寄存器 又 多处备份 的操作是花时间和空间的。反而会增加 时间空间的开销。 movl 12(%ebp), %eax ;第2个参数入寄存器 subl $6, %eax ; 这里eax变成了中间变量 。 没有变量d的出现 movl %eax, -4(%ebp);将计算的中间结果 放入局部变量区域中 movl 12(%ebp), %eax;将第2个参数放入中间寄存器中 movl 8(%ebp), %edx;将第1个参数放入另一个零时数据寄存器中 leal (%edx,%eax), %eax;第一个参数,第2个参数,第2个参数 addl %ecx, %eax;第3个参数,第2个参数 ;函数跛 leave ;移动 esp 函数栈里的内容并没有被清除 ret;做完后esp 移动4字节 .size func_ret_int, .-func_ret_int .ident "GCC: (Ubuntu/Linaro 4.4.4-14ubuntu5) 4.4.5" .section .note.GNU-stack,"",@progbits
我们发现不管你是否声明为寄存器变量 ,参数还是要入函数堆栈的。
TITLE main.c .386P include listing.inc if @Version gt 510 .model FLAT else _TEXT SEGMENT PARA USE32 PUBLIC 'CODE' _TEXT ENDS _DATA SEGMENT DWORD USE32 PUBLIC 'DATA' _DATA ENDS CONST SEGMENT DWORD USE32 PUBLIC 'CONST' CONST ENDS _BSS SEGMENT DWORD USE32 PUBLIC 'BSS' _BSS ENDS $$SYMBOLS SEGMENT BYTE USE32 'DEBSYM' $$SYMBOLS ENDS $$TYPES SEGMENT BYTE USE32 'DEBTYP' $$TYPES ENDS _TLS SEGMENT DWORD USE32 PUBLIC 'TLS' _TLS ENDS ; COMDAT _main _TEXT SEGMENT PARA USE32 PUBLIC 'CODE' _TEXT ENDS ; COMDAT _func_ret_int _TEXT SEGMENT PARA USE32 PUBLIC 'CODE' _TEXT ENDS FLAT GROUP _DATA, CONST, _BSS ASSUME CS: FLAT, DS: FLAT, SS: FLAT endif PUBLIC _static_variable _DATA SEGMENT _static_variable DD 05H _DATA ENDS PUBLIC _main PUBLIC _func_ret_int EXTRN __chkesp:NEAR ; COMDAT _main _TEXT SEGMENT _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 _main PROC NEAR ; COMDAT ; sp: 堆栈指针 bp:帧指针 ip:指令地址指针 ; 指令地址(虚拟地址) 指令 ; 10 : { 00000 55 push ebp ;保存栈帧指针 00001 8b ec mov ebp, esp 00003 81 ec 98 00 00 00 sub esp, 152 ; 00000098H ;创建空间用于保存局部变量和被保存 的寄存器值 00009 53 push ebx 0000a 56 push esi 0000b 57 push edi 0000c 8d bd 68 ff ff ff lea edi, DWORD PTR [ebp-152] 00012 b9 26 00 00 00 mov ecx, 38 ; 00000026H ;?????为什么是26H 00017 b8 cc cc cc cc mov eax, -858993460 ; ccccccccH;???? 0001c f3 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 : //extern int 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 0001e c7 45 fc 01 00 00 00 mov DWORD PTR _i1$[ebp], 1 00025 c7 45 f8 02 00 00 00 mov DWORD PTR _i2$[ebp], 2 0002c c7 45 f4 03 00 00 00 mov DWORD PTR _i3$[ebp], 3 00033 c7 45 f0 04 00 00 00 mov DWORD PTR _i4$[ebp], 4 0003a c7 45 ec 05 00 00 00 mov DWORD PTR _i5$[ebp], 5 00041 c7 45 e8 06 00 00 00 mov DWORD PTR _i6$[ebp], 6 00048 c7 45 e4 07 00 00 00 mov DWORD PTR _i7$[ebp], 7 ; 27 : i8 = 8; i9 = 9; i10 = 10; 0004f c7 45 e0 08 00 00 00 mov DWORD PTR _i8$[ebp], 8 00056 c7 45 dc 09 00 00 00 mov DWORD PTR _i9$[ebp], 9 0005d c7 45 d8 0a 00 00 00 mov DWORD PTR _i10$[ebp], 10 ; 0000000aH ; 28 : ;此时0012FF58 ; 29 : c1 = (char *)110; c2 = (char *)120; c3 = (char *)130; 00064 c7 45 d4 6e 00 00 00 mov DWORD PTR _c1$[ebp], 110 ; 0000006eH 0006b c7 45 d0 78 00 00 00 mov DWORD PTR _c2$[ebp], 120 ; 00000078H 00072 c7 45 cc 82 00 00 00 mov DWORD PTR _c3$[ebp], 130 ; 00000082H ; 30 : c4 = (char *)140; c5 = (char *)150; c6 = (char *)160; 00079 c7 45 c8 8c 00 00 00 mov DWORD PTR _c4$[ebp], 140 ; 0000008cH 00080 c7 45 c4 96 00 00 00 mov DWORD PTR _c5$[ebp], 150 ; 00000096H 00087 c7 45 c0 a0 00 00 00 mov DWORD PTR _c6$[ebp], 160 ; 000000a0H ; 31 : c7 = (char *)170; c8 = (char *)180; c9 = (char *)190; 0008e c7 45 bc aa 00 00 00 mov DWORD PTR _c7$[ebp], 170 ; 000000aaH 00095 c7 45 b8 b4 00 00 00 mov DWORD PTR _c8$[ebp], 180 ; 000000b4H 0009c c7 45 b4 be 00 00 00 mov DWORD PTR _c9$[ebp], 190 ; 000000beH ; 32 : c10 = (char *)200; 000a3 c7 45 b0 c8 00 00 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); 000aa 8b 45 d8 mov eax, DWORD PTR _i10$[ebp] ;第3个参数入 000ad 50 push eax 000ae 8b 4d fc mov ecx, DWORD PTR _i1$[ebp] 000b1 51 push ecx 000b2 6a 0a push 10 ; 0000000aH 000b4 e8 00 00 00 00 call _func_ret_int 000b9 83 c4 0c add esp, 12 ; 0000000cH 000bc 89 45 f8 mov DWORD PTR _i2$[ebp], eax ; 44 : } 000bf 5f pop edi 000c0 5e pop esi 000c1 5b pop ebx 000c2 81 c4 98 00 00 00 add esp, 152 ; 00000098H 000c8 3b ec cmp ebp, esp 000ca e8 00 00 00 00 call __chkesp 000cf 8b e5 mov esp, ebp 000d1 5d pop ebp 000d2 c3 ret 0 _main ENDP _TEXT ENDS ; COMDAT _func_ret_int _TEXT SEGMENT _a$ = 8 _b$ = 12 _c$ = 16 _d$ = -4 _func_ret_int PROC NEAR ; COMDAT ; 47 : { 00000 55 push ebp 00001 8b ec mov ebp, esp 00003 83 ec 44 sub esp, 68 ; 00000044H;创建空间用于保存局部变量和 被保存的寄存器值 00006 53 push ebx 00007 56 push esi 00008 57 push edi 00009 8d 7d bc lea edi, DWORD PTR [ebp-68] 0000c b9 11 00 00 00 mov ecx, 17 ; 00000011H 00011 b8 cc cc cc cc mov eax, -858993460 ; ccccccccH 同上fe84---fec8 初始化为 cccc.. 00016 f3 ab rep stosd ; 48 : int d; ; 49 : ; 50 : d = b - 6; 00018 8b 45 0c mov eax, DWORD PTR _b$[ebp] 0001b 83 e8 06 sub eax, 6 0001e 89 45 fc mov DWORD PTR _d$[ebp], eax ; 51 : return a + b + c; 00021 8b 45 08 mov eax, DWORD PTR _a$[ebp] 00024 03 45 0c add eax, DWORD PTR _b$[ebp] 00027 03 45 10 add eax, DWORD PTR _c$[ebp] ; 52 : ; 53 : } 0002a 5f pop edi 0002b 5e pop esi 0002c 5b pop ebx 0002d 8b e5 mov esp, ebp 0002f 5d pop ebp 00030 c3 ret 0 _func_ret_int ENDP _TEXT ENDS END
发现这里与GCC不同的是 对所有的变量进行了初始化。但是并没有看到寄存器变量 与寄存器变量 存储的区别于是又加了个
int noreg;
noreg = 0xffff;
i1 = noreg;
noreg = i1;
发现赋值的方式 和 存储的位置还是与上面的寄存器的一样,就感觉vc把register关键字 定义为空字符串 。