栈回溯技术及uClibc的堆实现原理



《栈回溯技术及uClibc的堆实现原理》由会员分享,可在线阅读,更多相关《栈回溯技术及uClibc的堆实现原理(21页珍藏版)》请在文档大全上搜索。
1、栈回溯技术及uClibc的堆实现原理【摘要】本文描述栈的作用、uClibc上堆的实现,利用栈回溯技术查找编程中经常发生的段错误问题,理解栈、堆的作用,通过几个例子分析越界访问导致的错误。【关键词】堆 栈 回溯 堆实现 栈作用一、问题的提出段错误、非法地址访问等问题导致程序崩溃的现象屡屡发生,如果能找到发生错误的函数,往往一眼就能看出BUG所在对于这类比较简单的问题,比如使用空指针进行读写等,利用栈回溯技术可以很快定位。但是对于数组溢出、内存泄漏等问题导致的程序错误,往往隐藏很深,它们并不当场发作,即使我们一步一步跟踪到发生错误的语句时,也经常会让人觉得“这个地方根本不可能出错啊”错误在很早以前
2、就隐藏下来了,只不过是这个“不可能出错的语句”触发了它。了解栈的作用、堆的实现,可以让我们脑中对程序的运行、函数的调用、变量的操作有个感官的了解,对解决这类问题会有所帮助。二、解决思路了解了栈,就可以通过栈回溯技术分析程序的调用关系,从而得出程序出错的流程;了解了堆,就可以对各类动态分配、释放内存导致的错误有个指导思想。(1)、栈的作用一个程序包含代码段、数据段、BSS段、堆、栈;其中数据段用来中存储初始值不为0的全局数据,BSS段用来存储初始值为0的全局数据,堆用于动态内存分配,栈用于实现函数调用、存储局部变量。比如对于如下程序:程序1 section.c01 #include 02 #in
3、clude 03 #include 04 05 int *g_pBuf;06 int g_iCount = 10;07 08 int main(int argc, char *argv)09 10 char str2;11 g_pBuf = malloc(g_iCount);12 printf(Address of main = 0x%08xn, (unsigned int)main);13 printf(Address of g_pBuf = 0x%08xn, (unsigned int)&g_pBuf);14 printf(Address of g_iCount = 0x%08xn, (u
4、nsigned int)&g_iCount);15 printf(Address of malloc buf = 0x%08xn, (unsigned int)g_pBuf);16 printf(Address of local buf str = 0x%08xn, (unsigned int)str);17 18 return 0;19 使用如下命令编译得到可执行文件section,反汇编文件section.dis:mips-uclibc-gcc -o section section.c staticmips-uclibc-objdump -D section section.dis在T50
5、0上的linux环境下,这个程序的输出结果为:Address of main = 0x004000b0Address of g_pBuf = 0x100002d0Address of g_iCount = 0x10000000Address of malloc buf = 0x10002660Address of local buf = 0x7fff7e50其中main函数的地址为0x004000b0,它处于代码段中;全局变量g_pBuf位于BSS段;全局变量g_iCount位于数据段;使用malloc分配出来的内存地址为0x10002660,它位于堆中;局部变量str数组的开始地址为0x7f
6、ff7e50,位于栈中。它们的分布图示如下:图1 程序各段示意图栈的作用有二: 保存调用者的环境某些寄存器的值、返回地址 存储局部变量现在通过一个简单的例子来说明栈的作用:程序2 stack.c01 #include 02 #include 03 #include 0405 void A(int a);06 void B(int b);07 void C(int c);0809 void A(int a)10 11 printf(%d: A call Bn, a);12 B(2);13 1415 void B(int b)16 17 printf(%d: B call Cn, b);18 C(
7、3);19 2021 void C(int c)22 23 char *p = (char *)c;24 *p = A;25 printf(%d: function Cn, c);26 2728 int main(int argc, char *argv)29 30 char a;31 A(1);32 C(&a);33 return 0;34 使用如下命令编译得到可执行文件stack,反汇编文件stack.dis:mips-uclibc-gcc -o stack stack.c staticmips-uclibc-objdump -D stack stack .dis此程序的调用关系为main
8、 A B C,现在来看看栈如何变化:注意:1. 图中“SP (32) = RA_after_xxx”表示SP+32的地方存放函数xxx执行完后的返回地址2. 栈中不仅仅存储返回地址,其它内容没标出来图2 函数调用中栈的变化上图中,main、A、B、C四个函数的栈大小都是40字节,返回地址都存在栈偏移地址为32的地方。我们是如何知道这点的呢?需要阅读反汇编代码:004000b0 : 4000b0:3c1c0fc1 luigp,0xfc1 4000b4:279c8090 addiugp,gp,-32624 4000b8:0399e021 addugp,gp,t9 4000bc:27bdffd8 a
9、ddiusp,sp,-40 4000c0:afbc0010 swgp,16(sp) 4000c4:afbf0020 swra,32(sp)00400128 : 400128:3c1c0fc1 luigp,0xfc1 40012c:279c8018 addiugp,gp,-32744 400130:0399e021 addugp,gp,t9 400134:27bdffd8 addiusp,sp,-40 400138:afbc0010 swgp,16(sp) 40013c:afbf0020 swra,32(sp)004001a0 : 4001a0:3c1c0fc0 luigp,0xfc0/ gp全
10、局指针,用来访问全局变量、函数 4001a4:279c7fa0 addiugp,gp,32672 4001a8:0399e021 addugp,gp,t9 4001ac:27bdffd0 addiusp,sp,-48 / 栈指针减48,这48字节的空间就是函数C的栈 4001b0:afbc0010 swgp,16(sp) / 在栈中保存gp 4001b4:afbf0028 swra,40(sp) / 在栈中保存返回地址ra 4001b8:afbe0024 sws8,36(sp) / 在栈中保存s8,此寄存器用来保存堆栈指针sp 4001bc:afbc0020 swgp,32(sp) / 又保存
11、一次gp,冗余 4001c0:03a0f021 moves8,sp / s8=sp,可见s8会被修改,所以先在上面保存原值 4001c4:afc40030 swa0,48(s8)/ a0用于传递参数,对应C语言,就是参数int c/ 把它保存在上一个函数的栈中 4001c8:8fc20030 lwv0,48(s8)/ v0=a0= int c 4001cc:00000000 nop 4001d0:afc20018 swv0,24(s8)/ 局部变量p 4001d4:8fc20018 lwv0,24(s8)/ 4001d8:24030041 liv1,65/ v1 = 65 = A 4001dc