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

原创 2012年03月30日 23:50:04
每一个函数独占自己的栈帧空间。当前正在运行的函数的栈帧总是在栈顶。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关键字 定义为空字符串 。 























                    

相关文章推荐

Linux gcc 利用反汇编来研究C语言函数堆栈的分配方式

越来越感觉学习C和汇编才是最能接近计算机本质的途径。所以,今天开始研究汇编了,先从gcc反汇编开始。     首先是下面的C代码: #include int sum(int a,int b) { ...

深入剖析GCC函数调用堆栈变化过程

from:http://stackoverflow.com/questions/2515598/push-ebp-movlesp-ebp          大家在通过反汇编去分析gcc生成的AT&...

gdb反汇编详解C函数底层实现笔记(程序堆栈、内存分配)

以下是在读《深入理解计算机系统》前面的章节“程序的机器级表示”时,自己动手在linux上使用了gdb对一个简单的C程序进行反汇编,通过不懈的努力终于查清楚弄明白了绝大多数的语句。且均以注释的形式列在汇...

gdb反汇编详解C函数底层实现笔记(程序堆栈、内存分配)

以下是在读《深入理解计算机系统》前面的章节“程序的机器级表示”时,自己动手在linux上使用了gdb对一个简单的C程序进行反汇编,通过不懈的努力终于查清楚弄明白了绝大多数的语句。且均以注释的形式列在汇...

使用DbgHelp获取函数调用堆栈之inline assembly(内联汇编)法

如果想自己获取应用程序的Call Stack,就需要查看Stack的内容。Stack Walker,在最近查看SSCLI源码的时候发现这个东西是和Stack Frame紧密联系在一起的。 Walki...
  • weiqubo
  • weiqubo
  • 2013年12月04日 16:35
  • 1182

函数调用堆栈的汇编解析

大家可能都会做过这个的gcc编译过程:gcc -S test.c -o test.s ,通过这样的编译得到的是我们的汇编代码,打开test.s文件会发现都是我们看不懂的汇编指令。也许我们都想过去看看这...

汇编学习笔记:函数调用过程中的堆栈分析

原创作品:陈晓爽(cxsmarkchan) 转载请注明出处 《Linux操作系统分析》MOOC课程 学习笔记 本文通过汇编一段含有简单函数调用的C程序,说明在函数调用过程中堆栈的变化。...

汇编调用C函数时的堆栈变化

先分析《自己动手写操作系统》中的部分程序   //初始化中断向量表(见protect.c文件) init_idt_desc(INT_VECTOR_COPROC_ERR,DA_386IGate, ...

献给汇编初学者-函数调用堆栈变化分析

跟一个朋友谈堆栈的时候 就写下了这段文字,顺便发到这里给需要的看看吧汇编初学者比较头痛的一个问题////////////////////////////////////////////////////...
  • yatere
  • yatere
  • 2011年05月14日 03:40
  • 392

从汇编角度来理解linux下多层函数调用堆栈运行状态

http://blog.csdn.net/jnu_simba/article/details/25158661 注:在linux下开发常用的辅助小工具: readelf 、...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:gcc 调试汇编 以及 对函数堆栈 的观察
举报原因:
原因补充:

(最多只允许输入30个字)