每一个函数独占自己的栈帧空间。当前正在运行的函数的栈帧总是在栈顶。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关键字 定义为空字符串 。