汇编级Call Stack的构成

原创 2011年01月07日 16:36:00

为什么要学习这么底层的东西,因为能帮助我们分析core文件,从而定位production上的问题。往往我们production或者QA的机器上没有安装GDB,也没有源代码。学会一点基本的汇编知识配合mdb就能帮上大忙。

 

 

先看一个core stack:

-bash-3.00$ g++ -o Test Test.cpp
-bash-3.00$ ./Test
Segmentation Fault (core dumped)
-bash-3.00$ mdb core
Loading modules: [ libc.so.1 ld.so.1 ]
> $C
080476fc _Z9FunctionCPc+0×16(80509dc)
08047718 _Z9FunctionBPc+0×14(80509dc)
08047734 _Z9FunctionAPc+0×14(80509dc)
0804776c main+0x2b(1, 8047790, 8047798)
08047784 _start+0×80(1, 8047870, 0, 8047877, 80478b9, 804795c)

初看一下,_start->main->FunctionA->FunctionB->FunctionC。程序core在FunctionC中。一点都不错,不过而实际上mdb打印出来的call stack的意义是:
_start+0×80是调用main的返回地址,main+0x2b是调用FunctionC的返回地址,_Z9FunctionAPc+0×14是调用FunctionB的返回地址,_Z9FunctionBPc+0×14是调用FunctionC的返回地址,那_Z9FunctionCPc+0×16呢?哈哈,_Z9FunctionCPc+0×16是当前指令地址,在这里表示导致程序core dump的指令地址。

结合反汇编和当前各寄存器的值,可以定位程序崩溃的直接原因:

> _Z9FunctionCPc+0×16::dis
_Z9FunctionCPc+0×16:         movl   $0×64,(%eax)
..
movl $0×64,(%eax)试图把立即数100赋值给%eax所指向的地址空间。而这个时候%eax的值是:

> $r
%cs = 0x003b            %eax = 0×00000000

 


所以造成访问违规而程序崩溃。

为什么mdb能够打印程序调用栈,程序的栈又是张什么样的呢?
先看源代码,在看栈的情况:
int FunctionC(char* str){
    char *my = str;
    int *p   = 0;
    *p = 100;
    return 0;
}

int FunctionB(char* str){
    char *my = str;
    FunctionC(my);
    return 0;
}

int FunctionA(char* str){
    char *my = str;
    FunctionB(my);
    return 0;
}

int main(){
    char *str="arrowpig";
    FunctionA(str);
    return 0;
}

> 080476f8,10/nap
–>低地址 [栈是从高地址往低地址长的,所以实际的调用顺序应该从下往上看]
0x80476f8:
0x80476f8:      0                                           –>FunctionB的局部变量: int *p   = 0;
0x80476fc:      0×8047718                          –>FunctionC的上级函数EBP       
0×8047700:      _Z9FunctionBPc+0×14   –>调用FunctionC的返回地址
0×8047704:      0x80509dc                        –>FunctionC的参数
0×8047708:      0                                  – >斜体部分似乎是gcc添加的函数调用
0x804770c:      0xfefb0cb8
0×8047710:      1
0×8047714:      0x80509dc                        –>FunctionB的局部变量: char* my=str;
0×8047718 :      0×8047734                         –>FunctionB的上级函数(也就是FunctionA)的EBP
0x804771c:      _Z9FunctionAPc+0×14   –>调用FunctionB的返回地址
0×8047720:      0x80509dc                        –>FunctionB的参数
0×8047724:      libgcc_s.so.1`__register_frame_info_bases+0xe  –>斜体部分似乎是gcc添加的函数调用
0×8047728:      0x80508a1
0x804772c:      0
0×8047730:      0x80509dc     –>FunctionA的局部变量
0×8047734 :      0x804776c      –>FunctionA的上级函数(也就是main)的EBP
0×8047738:      main+0x2b     –>调用FunctionA的返回地址
0x804773c:      0x80509dc     –>FunctionA的参数
–>高地址

 

 

我们先回忆一下大学时代学汇编语言时的几个基本概念:

  1. 栈是用来存放临时数据的,后进先出。栈的增长方式是从高地址向低地址增长。
  2. EBP是栈基址指针,永远指向栈底(高地址);ESP是栈指针,永远指向栈顶(低地址)。
  3. CALL指令用来调用一个函数。CALL将下一条指令地址压栈,这样函数返回时才能执行下一条指令。
  4. RET用来从函数返回,之前CALL压栈的返回地址从栈中弹出赋给EIP,程序转到CALL前的下一条指令继续运行。
  5. ENTER建立当前函数的栈框架stack frame。
  6. LEAVE释放当前函数的栈框架。

Enter 相当于:
pushl  %ebp
movl   %esp,%ebp

Leave相当于:
movl   %ebp,%esp
popl    %ebp

好,现在我们看一下FunctionA的反汇编代码:

> _Z9FunctionAPc::dis
_Z9FunctionAPc:                  pushl  %ebp            –>保存上级函数的栈基址EBP
_Z9FunctionAPc+1:               movl   %esp,%ebp   –>完成当前函数的Stack Frame的建立
_Z9FunctionAPc+3:               subl   $0×10,%esp     –>分配16字节用于存放局部变量
_Z9FunctionAPc+6:               movl   0×8(%ebp ),%eax  –> 使用%ebp的正偏移量访问函数参数
_Z9FunctionAPc+9:               movl   %eax,-0×4(%ebp)  –> 使用%ebp的负偏移量访问局部变量
_Z9FunctionAPc+0xc:            pushl  -0×4(%ebp)          –> 下级函数的参数压栈
_Z9FunctionAPc+0xf:             call   -0x2d    <_Z9FunctionBPc>  –>调用下级函数
_Z9FunctionAPc+0×14:           addl   $0×4,%esp           –>C的调用规范由主调函数负责从堆栈清理参数
_Z9FunctionAPc+0×17:           movl   $0×0,%eax   –>%eax存放函数返回值: return 0;
_Z9FunctionAPc+0x1c:           leave                   –> 释放 stack frame
_Z9FunctionAPc+0x1d:           ret                      –> 弹出返回地址,从而继续执行

  1. 使用EBP我们可以找到上级函数的栈基址,调试器就是通过它打印call stack的。
  2. 使用subl $0xXX, %esp 分配局部变量空间。
  3. 使用%ebp的正偏移量访问函数参数。
  4. 使用%ebp的负偏移量访问局部变量。
  5. 函数返回值存在%eax中。

比如我们想看一下FunctionA的局部变量的值,我们知道这是一个字符串:
> 0x80509dc/S
0x80509dc:      arrowpig

最后,我摘抄一下网上一篇名为:《x86汇编语言学习手记》里面的一张图作为参考:

假如函数A调用函数B,函数B调用函数C ,则函数栈框架及调用关系如下图所示:

       +———————-+—-> 高地址
       | EIP (上级函数返回地址)  |  
       +———————-+  
  +–> | EBP (上级函数的EBP)  | –+     <—- 当前函数A的EBP (即STP框架指针)
  |    +———————-+   +–>偏移量A   
  |    | Local Variables           |   |
  |    |    ……….                  | –+<—ESP指向函数A新分配的局部变量,局部变量可以通过A的ebp-偏移量A访问  
  | f  +———————-+
  | r  | Arg n(函数B的第n个参数)|
  | a  +———————-+
  | m | Arg .(函数B的第.个参数) |
  | e  +———————-+
  |    | Arg 1(函数B的第1个参数) |
  | o  +———————-+
  | f  | Arg 0(函数B的第0个参数) |   –+   <—— B函数的参数可以由B的ebp+偏移量B访问
  |    +———————-+     +–> 偏移量B
  | A  | EIP (A函数的返回地址)   |     |
  |    +———————-+   –+
  +—| EBP (A函数的EBP)      |<–+  <—— 当前函数B的EBP (即STP框架指针)
       +———————-+   |
       | Local Variables      |   |
       |    ……….        |   |  <—— ESP指向函数B新分配的局部变量
       +———————-+   |
       | Arg n(函数C的第n个参数) |   |
       +———————-+   |
       | Arg .(函数C的第.个参数) |   |
       +———————-+   +–> frame of B
       | Arg 1(函数C的第1个参数) |   |
       +———————-+   |
       | Arg 0(函数C的第0个参数) |   |
       +———————-+   |
       | EIP (B函数的返回地址)   |   |
       +———————-+   |
+–> | EBP (B函数的EBP)      |—+  <—— 当前函数C的EBP (即STP框架指针)
|     +———————-+
|     | Local Variables      |
|     |    ……….        |      <—— ESP指向函数C新分配的局部变量
|     +———————-+—-> 低地址

 

 

Collecting the call stack in real time

  • 2008年10月04日 15:11
  • 27KB
  • 下载

trace call stack demo

  • 2008年08月05日 16:42
  • 3KB
  • 下载

iOS中线程Call Stack的捕获和解析(一)

这里对上个月做的一个技术项目做部分技术小结,这篇文章描述的功能和我们在使用Xcode进行调试时点击暂停的效果类似。一、获取任意一个线程的Call Stack如果要获取当前线程的调用栈,可以直接使用现有...

How to print PHP call stack to generate a debug log

欢迎转载,请注明作者及出处 1st Way:Use PHP methoddebug_backtrace and/ordebug_print_backtrace to generate...
  • flex4
  • flex4
  • 2011年12月07日 16:04
  • 1042

function-call stack

Other resource about function-call conventions. http://www.codeproject.com/KB/cpp/calling_conventio...

Xcode debug时如何查看崩溃堆栈:First throw call stack不打印方法名

在Xcode调试程序时,程序出现bug崩溃后,偏偏在控制台显示的崩溃堆栈看不到具体的函数调用信息以及函数行号等,这怎么办?...

CALL STACK TRACE GENERATION

转载自http://www.acsu.buffalo.edu/~charngda/backtrace.html(链接已失效)

x86 function call and return --- stack save the return address.

x86 function call stack

gdb 调用栈 (call stack)

分享一篇不错的文章讲述调用栈; 如果得到调用栈 http://www.yosefk.com/blog/getting-the-call-stack-without-a-frame-pointer.h...
  • sylin
  • sylin
  • 2012年06月19日 15:23
  • 2943

linux 打印call stack 方法

主要有四种,加入backtrace的方法: 1. WARN_ON(cond)   //比如  WARN_ON(!host->claimed); 条件满足时,输出如下log:   WARNING:...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:汇编级Call Stack的构成
举报原因:
原因补充:

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