Linux内核调试方法总结之栈帧
栈帧
栈帧和指针可以说是C语言的精髓。栈帧是一种特殊的数据结构,在C语言函数调用时,栈帧用来保存当前函数的父一级函数的栈底指针,当前函数的局部变量以及被调用函数返回后下一条汇编指令的地址。如下图所示:

栈帧位于栈内存中,接下里我们用一个实例展示一下栈帧的入栈和退栈过程。
stackframe.c
#include <stdio.h>
int func(int m, int n)
{
return m+n;
}
int main()
{
int m = 8;
int n = 6;
func(m, n);
return 0;
}
#gcc –g stackframe.c –o stackframe (编译)
#objdump –dS stackframe > stackframe.S (反汇编)

从133~139行我们可以看到main函数栈帧的形成过程(入栈操作):
1) push %rbp 将上一级函数栈帧的栈底指针压栈
2) mov %rsp, %rbp 将BP指针指向SP,因为上一级函数的栈顶指针是下一级函数的栈底指针,证明栈帧是依次向下增长的
3) sub $0x10, %rsp SP栈顶指针向下位移16个字节,即创建main函数栈帧。这个地方为什么是16个字节呢?是因为上一级函数栈底指针和当前函数返回时下一条指令地址各占4个字节,m和n两个整形变量各占4个字节,加起来就是16个字节。
4) movl $0x8, -0x4(%rbp) 将变量m压栈
5) movl $0x6, -0x8(%rbp) 将变量n压栈
6) mov -0x8(%rbp), %edx 将m变量值加载到edx寄存器
mov -0x4(%rbp), %eax 将n变量值加载到eax寄存器
mov %edx, %esi
mov %eax, %edi
7)callq 4004c4 <func> 调用callq指令跳转到func函数段,同时压栈EIP+4,即返回func函数时下一条可执行指令的地址

从func函数的反汇编代码可以看到,0x4004c4地址就是func函数开始处,和前面的callq对应。在进入func函数段之后,就是func函数压栈的动作,基本顺序和前面的main函数压栈过程一致。这个地方需要注意的是,首先是mov %edi, -0x4(rbp),从前面的汇编代码可以看到%edi保存的是n变量的值,其次才执行mov %esi, -0x8(rbp)压栈m变量值,证明函数参数的传递顺序是从右往左。另外,这个操作过程演示了实参传递的过程,那么形参传递和指针传递又是怎么样的呢?有兴趣可以试一下。
前面的描述详细介绍了函数栈帧的形成过程,也就是函数调用的底层实现。之所以要重点介绍这一部分内容,是因为在Linux系统出现死机或者异常重启情况时,我们通常会获取死机时的backtrace信息,即函数调用顺序和函数入参,这样我们就可以精准地定位到导致死机的代码段进一步分析。而backtrace就是通过对函数栈帧进行逆推得到的。
前面介绍的是Intel X86架构栈帧的实现原理,和ARM平台上的栈帧实现略有差异,ARM平台栈帧会依次压栈PC指针寄存器值,LR返回指针寄存器值,SP栈顶指针寄存器值,FP栈底指针寄存器值,函数入参,局部变量,即将调用的函数的参数。从本质上讲,栈帧是为了记录函数调用过程,不同平台会压栈不同的数据,但是基本上都是大同小异的。
之所以要重点介绍栈帧这一部分内容,是因为代码调试的最有效方法是获取系统崩溃时的系统运行上下文,包括寄存器组值、函数调用栈,这样我们就能缩小定位范围,详细地知道函数的入参,执行到哪一条指令出的问题。这种backtrace的思路贯穿Linux内核调试、Native层应用调试(用户空间C/C++可执行文件、静态库、动态库)、Java层调试,因此衍生出了ramdump, coredump, Exception.printStack()等技术,其目的就是为了获取函数调用栈信息。
Linux内核调试方法总结之栈帧的更多相关文章
- Linux内核调试方法总结
Linux内核调试方法总结 一 调试前的准备 二 内核中的bug 三 内核调试配置选项 1 内核配置 2 调试原子操作 四 引发bug并打印信息 1 BUG()和BUG_ON() 2 ...
- Linux内核调试方法总结【转】
转自:http://my.oschina.net/fgq611/blog/113249 内核开发比用户空间开发更难的一个因素就是内核调试艰难.内核错误往往会导致系统宕机,很难保留出错时的现场.调试内核 ...
- 【转】Linux内核调试方法总结
目录[-] 一 调试前的准备 二 内核中的bug 三 内核调试配置选项 1 内核配置 2 调试原子操作 四 引发bug并打印信息 1 BUG()和BUG_ON() 2 dump_sta ...
- Linux内核调试方法总结之反汇编
Linux反汇编调试方法 Linux内核模块或者应用程序经常因为各种各样的原因而崩溃,一般情况下都会打印函数调用栈信息,那么,这种情况下,我们怎么去定位问题呢?本文档介绍了一种反汇编的方法辅助定位此类 ...
- Linux内核调试方法总结之序言
本系列主要介绍Linux内核死机.异常重启类稳定性问题的调试方法. 在Linux系统中,一切皆为文件,而系统运行的载体,是一类特殊的文件,即进程.因此,我尝试从进程的角度分析Linux内核的死机.异常 ...
- Linux内核调试方法【转】
转自:http://www.cnblogs.com/shineshqw/articles/2359114.html kdb:只能在汇编代码级进行调试: 优点是不需要两台机器进行调试. gdb:在调试模 ...
- Linux内核调试方法总结之调试宏
本文介绍的内核调试宏属于静态调试方法,通过调试宏主动触发oops从而打印出函数调用栈信息. 1) BUG_ON 查看bug处堆栈内容,主动制造oops Linux中BUG_ON,WARN_ON用于调试 ...
- 转载:Linux内核调试方法
转载文章请注明作者和二维码及全文信息. 转自:http://blog.csdn.net/swingwang/article/details/72331196 不会编程的程序员,不是好的架构师,编程和内 ...
- Linux内核调试方法总结之coredump
什么是core dump? 分析core dump是Linux应用程序调试的一种有效方式,像内核调试抓取ram dump一样,core dump主要是获取应用程序崩溃时的现场信息,如程序运行时的内存. ...
随机推荐
- Bootstrap字体图标Font-Awesome 使用教程
转载自:https://blog.csdn.net/crper/article/details/46293295 以备记录使用.
- 【SSL1786】麻将游戏
题目大意: 给出一个矩阵,查询其中两个点连通线段数 正文: 看这题好眼熟... 实质和这道题是一模一样的,只不过由一条询问升级到多条询问.
- Python入门之 函数
Python入门之 函数 1.初识函数 1.1 什么是函数? <1> 将某个功能封装到一个空间中就是一个函数 <2> 减少重复代码 1.2 定义函数 def -- python ...
- 洛谷 - P1462 - 通往奥格瑞玛的道路 - 二分 - Dijkstra
https://www.luogu.org/problem/P1462 感觉,要二分最大收费权的城市,把小于等于它的全部插进去,Dijkstra一下求出最小的血量.这样感觉太暴力了. 考虑只有1000 ...
- 细说SQL Server数据类型
1.字符型 char 定长,查询速度快,最大8000字符(非unicode编码) ) '小明' 前四个字符存放‘小明’,后添6个空格补全 varchar 变长,最大8000字符(非unicode编码) ...
- 使用GET与POST方式获取html数据
抓取网站数据解析的工作,其中,使用到GET和POST方法获取html数据. 使用GET方式: /** * 使用get方式获取html数据 * * @param strURL(需要访问的网站) * @r ...
- Vue 数据持久化
方法一:使用 localStorage 存储数据 window.localStorage.setItem(key,value) 方法二:使用 vuex-persistedstate插件 vuex 存在 ...
- [七月挑选]使用idea创建spring boot 项目
title: 使用idea创建spring boot 项目 参考lindaZ的IntelliJ IDEA 创建spring boot 的Hello World 项目 1.Open IDEA,choos ...
- Python numpy插入、读取至postgreSQL数据库中bytea类型字段
安装psycopg2模块,此模块用于连接PostgreSQL数据库 pip install psycopg2 # -*- coding: utf-8 -*- import psycopg2 impo ...
- 34. Find First and Last Position of Element in Sorted Array (JAVA)
Given an array of integers nums sorted in ascending order, find the starting and ending position of ...