文章目录
- 1.内存的平坦模型和分段模型
- (1)平坦模型
- (2)分段模型
- 2.字节序,数据类型的高位与低位,寄存器
- 3.栈
- (1)函数调用和栈的关系
- (2)gdb的backtrace(bt)使用
1.内存的平坦模型和分段模型
- CPU通过内存总线访问到的地址称为物理地址。
- 32位下的最大物理地址是64GB,2^36次方;
- 64位下,Intel的最大物理地址是2^40次方的字节,AMD是 2 ^48次方字节
(1)平坦模型
- LInux采用的内存模型: 内存可看做是单一,平坦的连续地址空间,称之为线性地址空间
(2)分段模型
- 将内存看做被称之为“段segment”的独立地址空间的集合。
- 通过段选择器和偏移量组成的逻辑地址来访问段内地址。首先用段选择器识别:要访问的段,然后通过偏移量找到该段的地址空间的内存。
- 32bit下最多能指定16383个段,各段的最大大小位2^32次方字节
- 64bit模式下,使用了平坦模型,因此可以使用64bit的线性地址。不能使用分段式的内存模型。
2.字节序,数据类型的高位与低位,寄存器
(1)大端字节选和小端字节序
例如:0x12345678
小端字节序:低位数据排在内存低地址,Intel架构
大端字节序:低位数据放在内存的高地址,SPARC,MIPS架构
- Intel的CPU构造
- 小端字节序
(3)32bit的寄存器与64bit的寄存器
- 32bit的主要寄存器用途
- 32bit的主要段寄存器的用途
程序代码放在代码段;
数据放在数据段;
程序的栈放在堆栈段;
- 64bit环境中的寄存器
64bit模式下的通用寄存器在处理32bit操作数时,可通过EAX,EBX,ECX,EDX,EDI,ESI,EBP,ESP,R8D-R15D;
64bit模式下的通用寄存器在处理64bit操作数时,可通过RAX,RBX,RCX,RDX,RDI,RSI,RBP,RSP,R8-R15;
R8-R15/R8D-R15D是8个新的通用寄存器,
RIP寄存器是64bit的指令指针;
3.栈
- 栈stack是程序存放数据的内存区域之一,特点是LIFO。在保存动态分配的局部变量时,要使用栈。此外,在函数调用时,栈还用于传递函数参数,以及用于保存返回地址和返回值。
(1)函数调用和栈的关系
#include<stdio.h>
#include<ctype.h>
#include<stdlib.h>
#define MAX (1UL<<20)
/*
王继编写
GB2312
*/
typedef unsigned long long u64;
typedef unsigned int u32;
u32 max_addend=MAX;
u64 sum_till_MAX(u32 n)
{
u64 sum;
n++;
sum=n;
if (n<max_addend)
sum+=sum_till_MAX(n);
return sum;
}
int main(int argc, char **argv)
{
u64 sum=0;
if ((argc==2)&& isdigit(*(argv[1])))
max_addend=strtol(argv[1], NULL, 0);
if (max_addend>MAX || max_addend==0)
{
fprintf(stderr,"Invalid number is specified\n");
return 1;
}
sum=sum_till_MAX(0);
printf("sum(0..%lu)=%llu\n", max_addend, sum);
return 0;
}
- 执行结果如下:
[root@localhost wangji]# gcc stack.c -o sum
[root@localhost wangji]# ./sum 10
sum(0..10)=55
- (a)是函数调用之前栈的状态
- (b)是调用sum_till_MAX()函数之后栈的状态
- (c)是再次调用sum_till_MAX函数之后,栈的状态
栈上依此(从下至上)保存了传给参数的函数,调用者的返回地址,上层栈帧指针和函数内部使用的自动变量;
每个函数独自拥有:用栈来临时保存寄存器,称之为栈帧stack frame;
帧指针FP:栈帧起始地址;
栈指针SP:永远指向栈的顶端;
(gdb) disassemble main
0x00000000004006eb <+178>: mov $0x0,%edi (1)
0x00000000004006f0 <+183>: callq 0x400604 <sum_till_MAX> (2)
0x00000000004006f5 <+188>: mov %rax,-0x8(%rbp)
0x00000000004006f9 <+192>: mov 0x20041d(%rip),%ecx # 0x600b1c <max_addend>
(1)调用函数时,首先将传递给函数的参数压入栈中
(2)sum_till_MAX()函数的call指令自动把返回地址 0x00000000004006f5压入栈中
(gdb) disass sum_till_MAX
Dump of assembler code for function sum_till_MAX:
0x0000000000400604 <+0>: push %rbp (3)
0x0000000000400605 <+1>: mov %rsp,%rbp (4)
0x0000000000400608 <+4>: sub $0x20,%rsp (5)
0x000000000040060c <+8>: mov %edi,-0x14(%rbp) (6)
0x000000000040060f <+11>: addl $0x1,-0x14(%rbp)
0x0000000000400613 <+15>: mov -0x14(%rbp),%eax
0x0000000000400616 <+18>: mov %rax,-0x8(%rbp) (7)
0x000000000040061a <+22>: mov 0x2004fc(%rip),%eax # 0x600b1c <max_addend>
0x0000000000400620 <+28>: cmp %eax,-0x14(%rbp)
0x0000000000400623 <+31>: jae 0x400633 <sum_till_MAX+47>
0x0000000000400625 <+33>: mov -0x14(%rbp),%eax
0x0000000000400628 <+36>: mov %eax,%edi
0x000000000040062a <+38>: callq 0x400604 <sum_till_MAX>
0x000000000040062f <+43>: add %rax,-0x8(%rbp)
0x0000000000400633 <+47>: mov -0x8(%rbp),%rax
0x0000000000400637 <+51>: leaveq (8)
0x0000000000400638 <+52>: retq (9)
End of assembler dump.
上图(a)(b)(c)中的栈帧准备过程:
(3)在栈上能够,保存上层帧的帧指针FP
(4)将新的栈帧赋给帧指针FP
(5)在栈上分配用于,保存局部变量的空间
(6)开始为sum_till_MAX()函数的处理过程
(7),由于sum是64bit的,这里表示sum的低32bit,%rax中保存的是参数n的值,这里相当于sum=n;
(8)删除栈帧
(9)子程序的返回指令,将栈中保存的返回地址pop到程序计数寄存器中,将控制权返回给调用者
(2)gdb的backtrace(bt)使用
1.首先在这行代码打个断点:sum+=sum_till_MAX(n);
(gdb) bt
#0 sum_till_MAX (n=2) at stack.c:22
#1 0x000000000040062f in sum_till_MAX (n=1) at stack.c:22
#2 0x00000000004006f5 in main (argc=1, argv=0x7fffffffe488) at stack.c:37
2.下面来手动执行一下与gdb的backstrace相同的操作:
当前执行位置可以通过程序计数器pc获取,32bit的机器是eip,64bit的机器是rip!!;
帧指针FP:32bit的机器是ebp,64bit的机器是rbp;
(gdb)i r
rax 0x100000 1048576
rbx 0x0 0
rcx 0x31689894e8 212208227560
rdx 0x7fffffffe498 140737488348312
rsi 0x7fffffffe488 140737488348296
rdi 0x1 1
rbp 0x7fffffffe340 0x7fffffffe340<-帧指针FP
rsp 0x7fffffffe320 0x7fffffffe320
r8 0x400720 4196128
r9 0x316820e1b0 212200382896
r10 0x7fffffffe200 140737488347648
r11 0x3168621150 212204654928
r12 0x400520 4195616
r13 0x7fffffffe480 140737488348288
r14 0x0 0
r15 0x0 0
rip 0x400625 0x400625 <sum_till_MAX+33>
eflags 0x283 [ CF SF IF ]
cs 0x33 51
ss 0x2b 43
ds 0x0 0
es 0x0 0
fs 0x0 0
gs 0x0 0
3.查看栈的内容:从栈顶sp开始显示适当的大小
32bit的机子上使用:
(gdb)x/40w $sp
64bit的机子上使用:
(gdb)x/40g $sp
可以结合上面的图更好的去理解,下面的字节。因为形参n=2(从bt指令可以看出),所以才有栈帧B,C;
如果形参n=1,那么只有栈帧B。
下面的,还是在#0 sum_till_MAX (n=2) at stack.c:22 基础上执行的。
0x7fffffffe320: 0x0000000000f0b2e4 0x00000002000000c2--------------------------
0x7fffffffe330: 0x0000000000000001 0x0000000000000002<-参数n [栈帧C]
0x7fffffffe340:<-帧指针FP 0x00007fffffffe370 <-返回地址 0x000000000040062f 帧指针FP就是上面(gdb) i b所得到的rbp的位置
0x7fffffffe350: 0x0000000000000000 0x00000001004004b3 -------------------------
0x7fffffffe360: 0x00007fffffffe498 0x0000000000000001<-参数n [栈帧B]
0x7fffffffe370: <-帧指针FP 0x00007fffffffe3a0 <-返回地址 0x00000000004006f5
0x7fffffffe380: 0x00007fffffffe488 0x0000000100400520--------------------------
0x7fffffffe390: 0x00007fffffffe480 0x0000000000000000
0x7fffffffe3a0: 0x0000000000000000 0x000000316862123d
0x7fffffffe3b0: 0x0000000000000000 0x00007fffffffe488
0x7fffffffe3c0: 0x0000000100000000 0x0000000000400639
0x7fffffffe3d0: 0x0000000000000000 0xb92bcbaf5ab5f584
0x7fffffffe3e0: 0x0000000000400520 0x00007fffffffe480
0x7fffffffe3f0: 0x0000000000000000 0x0000000000000000 [栈帧A]
0x7fffffffe400: 0x46d434509dd5f584 0xb9491b6b7945f584
0x7fffffffe410: 0x00007fff00000000 0x0000000000000000
0x7fffffffe420: 0x0000000000000000 0x0000000000400730 <-返回地址
0x7fffffffe430: 0x00007fffffffe488 <-参数argv 0x0000000000000001 <-参数argc-------------
0x7fffffffe440: 0x0000000000000000 0x0000000000000000
0x7fffffffe450: 0x0000000000400520 0x00007fffffffe480
3.使用gdb操作栈帧
(gdb) bt
查看现在选择的帧
(gdb)frame
选择上一层的#1帧
(gdb) frame 1
info指令可以看到更详细的栈帧信息
(下面的,还是在#0 sum_till_MAX (n=2) at stack.c:22 基础上执行的。)
(gdb)i frame 1
Stack frame at 0x7fffffffe380:
rip = 0x40062f in sum_till_MAX (stack.c:22); saved rip 0x4006f5
called by frame at 0x7fffffffe3b0, caller of frame at 0x7fffffffe350
source language c.
Arglist at 0x7fffffffe370, args: n=1
Locals at 0x7fffffffe370, Previous frame's sp is 0x7fffffffe380
Saved registers:
rbp at 0x7fffffffe370, rip at 0x7fffffffe378
4.栈溢出stack overflow怎么看?
$ ./sum
Segmentation falut
(gdb) r
Program received signal SIGSEGV, Segmentation fault.
0x000000000040060c in sum_till_MAX (n=Cannot access memory at address 0x7fffff3fefec
) at stack.c:16
16 {
将sum_till_MAX()的参数n push到栈顶端的命令:
(gdb) x/i $pc (关于内存地址的都用x吧) 等于p $pc ,与(gdb) i reg rip的内容一样
0x40060c 0x40060c <sum_till_MAX+8>
栈指针SP的位置
(gdb) p $sp 等于x/i $pc(关于内存地址的都用x吧) 和(gdb)x/i $rsp 结果一样
$2 = (void *) 0x7fffff3fefe0
查看内存映射
(gdb) i proc mapping
process 25903
cmdline = '/home/wangji/sum'
cwd = '/home/wangji'
exe = '/home/wangji/sum'
Mapped address spaces:
Start Addr End Addr Size Offset objfile
0x400000 0x401000 0x1000 0 /home/wangji/sum
0x600000 0x601000 0x1000 0 /home/wangji/sum
0x3168200000 0x3168220000 0x20000 0 /lib64/ld-2.14.1.so
0x3168420000 0x3168421000 0x1000 0x20000 /lib64/ld-2.14.1.so
0x3168421000 0x3168422000 0x1000 0x21000 /lib64/ld-2.14.1.so
0x3168422000 0x3168423000 0x1000 0
0x3168600000 0x3168785000 0x185000 0 /lib64/libc-2.14.1.so
0x3168785000 0x3168985000 0x200000 0x185000 /lib64/libc-2.14.1.so
0x3168985000 0x3168989000 0x4000 0x185000 /lib64/libc-2.14.1.so
0x3168989000 0x316898a000 0x1000 0x189000 /lib64/libc-2.14.1.so
0x316898a000 0x316898f000 0x5000 0
0x7ffff7fdd000 0x7ffff7fe0000 0x3000 0
0x7ffff7ffd000 0x7ffff7ffe000 0x1000 0
0x7ffff7ffe000 0x7ffff7fff000 0x1000 0 [vdso]
0x7fffff400000<-栈开始的位置 0x7ffffffff000 0xbff000 0 [stack]
0xffffffffff600000 0xffffffffff601000 0x1000 0 [vsyscall]
stack表示栈空间。栈空间的顶端是0x7fffff400000,然后栈指针sp的值是0x7fffff3fefe0。
因为0x7fffff3fefe0比0x7fffff400000小,说明在栈空间之外呀!!
说明栈溢出
5.栈大小的限制
$ ulimit -s
10240
将栈的大小扩大到10倍,就不会发生segmentation fault错误了
$ ulimmit -Ss 81920
$ ./sum
sum(0..1048576)=549756338176
- 参考:修改ebp带来的问题一例,ESP和EBP之间是什么?
ESP与EBP
(1)编译器在编译代码时,一边都是以ebp作为基准来定位各个参数以及局部变量的,
在函数开头执行完push ebp; mov ebp, esp;sub esp, xx;这三条典型代码后,ebp作为栈底指针是不会变的
(2)ESP是当前堆栈指针,EBP是当前堆栈帧的基指针。
调用函数时,通常会在堆栈上为局部变量保留空间。此空间通常通过EBP引用(在函数调用期间,所有局部变量和函数参数都是此寄存器的已知常量偏移量)。
另一方面,ESP在函数调用期间会随着其他函数的调用而改变,或者作为临时堆栈空间用于部分操作结果。
- 通过栈上保存的返回地址信息,可以获得与bt结果相同的调用跟踪信息
参考:《GDB/Debug.Hacks中文版:深入调试的技术和工具》