您的位置:首页 > 运维架构 > Linux

linux内核中打印栈回溯信息 - dump_stack()函数分析

2016-11-25 17:41 381 查看

简介

当内核出现比较严重的错误时,例如发生Oops错误或者内核认为系统运行状态异常,内核就会打印出当前进程的栈回溯信息,其中包含当前执行代码的位置以及相邻的指令、产生错误的原因、关键寄存器的值以及函数调用关系等信息,这些信息对于调试内核错误非常有用。

打印函数调用关系的函数就是dump_stack(),该函数不仅可以用在系统出问题的时候,我们在调试内核的时候,可以通过dump_stack()函数的打印信息更方便的了解内核代码执行流程。

dump_stack()函数的实现和系统结构紧密相关,本文介绍ARM体系中dump_stack()函数的实现。该函数定义在arch/arm/kernel/traps.c文件中,调用dump_stack()函数不需要添加头文件,基本上在内核代码任何地方都可以直接使用该函数。

相关基本知识

读者需要了解一些ARM汇编的基本知识。在讲代码之前,我先简单说说内核中函数调用的一般过程。

关键寄存器介绍:

寄存器含义
r0-r3用作函数传参,例如函数A调用函数B,如果A需要向B传递参数,则将参数放到寄存器r0-r3中,如果参数个数大于4,则需要借用函数的栈空间。
r4-r11变量寄存器,在函数中可以用来保存临时变量。
r9(SB)静态基址寄存器。
r10(SL)栈界限寄存器。
r11(FP)帧指针寄存器,通常用来访问函数栈,帧指针指向函数栈中的某个位置。
r12(IP)内部过程调用暂存寄存器。
r13(SP)栈指针寄存器,用来指向函数栈的栈顶。
r14(LR)链接寄存器,通常用来保存函数的返回地址。
r15(PC)程序计数器,指向代码段中下一条将要执行的指令,不过由于流水线的作用,PC会指向将要执行的指令的下一条指令。

内核中的函数栈

内核中,一个函数的代码最开始的指令都是如下形式:

mov   ip, sp
stmfd sp!, {r0 - r3} (可选的)
stmfd sp!, {..., fp, ip, lr, pc}
……

从其中两条stmfd(压栈)指令可以看出,一个函数的函数栈的栈底(高地址)的结构基本是固定的,如下图:



首先我们约定被调用的函数称为callee函数,而调用者函数称为caller函数。

在进行函数调用的回溯时,内核中的dump_stack()函数需要做以下尝试:

首先我们约定被调用的函数称为callee函数,而调用者函数称为caller函数。

在进行函数调用的回溯时,内核中的dump_stack()函数需要做以下尝试:

首先读取系统中的FP寄存器的值,我们知道帧指针是指向函数栈的某个位置的,所以通过FP的值可以直接找到当前函数的函数栈的地址。
得到当前函数的代码段地址,这个很容易,因为当前正在执行的代码(可通过PC寄存器获得)就处在函数的代码段中。在函数栈中保存了一个PC寄存器的备份,通过这个PC寄存器的值可以定位到函数的第一条指令,即函数的入口地址。
得到当前函数的入口地址后,内核中保存了所有函数地址和函数名的对应关系,所以可以打印出函数名(详见另一篇博客:内核符号表的查找过程)。
在当前函数的函数栈中还保存了caller函数的帧指针(FP寄存器的值),所以我们就可以找到caller函数的函数栈的位置。
继续执行2-4步,直到某个函数的函数栈中保存的帧指针(FP寄存器的值)为0或非法。

发生函数调用时,函数栈和代码段的关系如下图所示:



dump_stack()函数

接下来我们就来看一下dump_stack()函数的实现。

dump_stack()主要是调用了下面的函数

c_backtrace(fp, mode);
1
1
两个参数的含义为:
fp: current进程栈的fp寄存器。
mode: ptrace用到的PSR模式,在这里我们不关心。dump_stack传入的值为0x10。

这两个参数分别赋值给r0, r1寄存器传给c_backtrace()函数。

c_backtrace函数定义如下(arch/arm/lib/backtrace.S):
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: