写个dump_stack【转】

转自:http://blog.chinaunix.net/uid-27714502-id-3434761.html

简单实现dump_stack

0.首先确保你能写个内核模块:打印"hello kernel"

  如果熟悉dump_stack的话,完全可以绕开此文,或者自己去看dump_stack代码实现之。

 

1.dump_stack是什么

经常调试内核一定对这个函数不陌生,因为我们大多数人调试内核的时候都受这个函数的

折磨,不信,那么我们调用下这个函数看看(随意写个内核模块调用dump_stack(),插入内核),

我们来看看输出:

Pid: 9982, comm: insmod Not tainted 2.6.31.5-127.fc12.i686.PAE #1

Call Trace:

 [<f7e98008>] init+0x8/0xc [hello]

 [<c040305b>] do_one_initcall+0x51/0x13f

 [<c0462e2f>] sys_init_module+0xac/0x1bd

 [<c0408f7b>] sysenter_do_call+0x12/0x28

看到输出,大家一定很熟悉, 没见过类似输出的,一定没把kernel搞崩过.

(来我教你:*(int *)NULL = 0xdead;)

其实不见得每次内核崩溃都会调用dump_stack,但是看到dump_stack的我们不应该被吓到,

反而应该高兴:内核在临挂前还喘口气给我们提示了宝贵的调试信息

 

2.构造dump_stack第一句

有的人已经不耐烦我这唠叨,自己开始查看代码了,但是为了满足我们小小的虚荣,看懂还不行,

自己也要来写个玩玩,不能老被dump_stack欺负阿。

我们看看dump_stack里面第一句代码:

 printk("Pid: %d, comm: %.20s %s %s %.*s\n",

                 current->pid, current->comm, print_tainted(),

                 init_utsname()->release,

                 (int)strcspn(init_utsname()->version, " "),

                 init_utsname()->version);

这里就不解释printk每个参数了,代码本身就自解释了,剩下的请google,

对于不太理解print_tainted,请看下此函数实现的源码上方的注释:)

 

3.构造dump_stack的call trace

1)先来句printk("Call Trace:\n"),

 

2)接下来就神奇了,当初我就觉得能将函数执行流打印出来实在是很神奇,内核到底用了什么方法呢?

先不说,我们来看一句简单的代码:

printk("[<%p>] %pS\n", &printk, &printk);

观察输出:

[<c0776cf4>] printk+0x0/0x1c

你发现,这个输出结果和dump_stack输出的部分惊人的相似,但是我们传给printk的参数是确确实实的

地址值,原来prink自己能转换地址到相应的函数名,只要用参数"%pS"就可以了。

我相信看到这里的人,估计已经走开自己去实现dump_stack玩了。

但是输出也可能是:

[<c0776cf4>] c0776cf4S

如果你不幸看到这个,那么你还是升级下内核吧,或者仔细阅读下dump_stack代码,完全靠自己去实现

下,那么你收获一定会远超出这篇文章。

printk之所以能够识别函数地址,靠的是kallsyms子系统的帮助,

用过类似grep -w "printk" /proc/kallsyms命令的人,一定要好好谢谢这个子系统,

多亏它我们才能从内核导出symbol

深入研究kallsyms就靠大家了。

 

3)在刚刚的惊喜后,我们回到正题,怎么用printk把当前的执行流打印出来?

这里用到x86中堆栈对函数调用的帮助,详细信息请google,我们要知道一点:每次函数调用时候,

都会将函数的返回地址(调用函数指令的下一句指令的地址)压入堆栈,已备函数返回时。

我们就可以靠这个返回地址来帮助打印函数执行流。

但是这个地址并不是一个函数的准确地址呀?

%pS需要的参数不一定是准确的函数地址,在函数内部任意指令地址都可以,这就解释了输出形式是

"printk+0x0/0x1c",0x0表示参数地址相对于printk地址的偏移,0x1c表示printk函数大小。

你可以尝试下:printk("%pS\n", &printk + 1);

 

并且如果函数属于某个模块,还会在输出后面加上模块名称,类似:" [<f8cd40a5>] exit+0xd/0xf [hello]"

 

这里知道地址在堆栈里,那么怎么取堆栈呢?

其实很简单:

int stack_pointer;

我们只要取临时变量地址值 &stack_pointer 就可以了(也可以用内联汇编取esp值),然后只要循环遍历堆栈上所有值,然后判断该值是否在

内核代码段空间内,如果是那么就用%pS输出。

 

那么堆栈的结束地址是什么呢?

就是当前进程的内核态堆栈段,不懂的话请google,一定要搞清除这个。

这里我们记堆栈底为:

bottom = (unsigned int)current_thread_info() + THREAD_SIZE;

 

但是怎么判断地址值是否在内核代码段呢?

我们可以用kernel_text_address这个函数就可以了,但是很不幸的是此函数内核没有导出,我们不能使用,

那么我们就自己实现个kernel_text_address吧,但是更不幸的是此函数内部实现所依赖的变量_etext等也没有

被内核导出,其实我也没想到很好的方法,索性就用个笨办法:

手动找出此函数内核中的地址,

# grep kernel_text_address /proc/kallsyms

c044f107 T kernel_text_address

在代码中通过地址值调用kernel_text_address

int (*kernel_text_addressp)(unsigned int) = (int (*)(unsigned int))0xc044f107;

 

4)给出个较为完整的代码(在本机上写的:2.6.31.5-127.fc12.i686.PAE, 虚拟机上文档写的麻烦)

#include <linux/init.h>

#include <linux/module.h>

#include <linux/kernel.h>

#include <linux/utsname.h>

 

/* 

 * change the value to real addr of kernel_text_address :

 * grep -w kernel_text_address /proc/kallsyms

 */

static int (*kernel_text_addressp)(unsigned int) = (int (*)(unsigned int))0xc044f107;

 

void my_dump_stack(void)

{

unsigned int stack;

unsigned int bottom;

unsigned int addr;

printk("Pid: %d, comm: %.20s %s %s %.*s\n",

current->pid, current->comm, print_tainted(),

init_utsname()->release,

(int)strcspn(init_utsname()->version, " "),

init_utsname()->version); 

printk("Call Trace:\n");

 

/* get stack point */

stack = (unsigned int)&stack;

 

/* get stack bottom point */

bottom = (unsigned int)current_thread_info() + THREAD_SIZE;

 

for (; stack < bottom; stack += 4) {

addr = *(unsigned int *)stack;

if (kernel_text_addressp(addr))

printk(" [<%p>] %pS\n", (void *)addr, (void *)addr);

}

}

 

static int __init init(void)

{

/* test */

my_dump_stack();

return 0;

}

 

static void __exit exit(void)

{

/* test */

my_dump_stack();

}

 

MODULE_LICENSE("GPL");

module_init(init);

module_exit(exit);

 

3.改进

1)

如果你还没厌烦的话,这里有个改进的地方。

你会发现内核中的dump_stack会又类似如下输出:

 [<c04cf4e6>] ? path_put+0x1a/0x1d

这里有个问号:这个表示堆栈中有确有此值(某个函数内部地址),但是并不代表此函数被

执行,也许这个值是个临时变量寄存在堆栈中。

要区分这个很容易,只要比较地址值所在堆栈的位置是否紧贴当前函数栈空间底的上方,

可以利用ebp(记录当前堆栈底)指针值来作比较,代码就留给大家写了。

 

2)

x86 64 实现要比x86 32复杂(见内核注释):

/*

 * x86-64 can have up to three kernel stacks:

 * process stack

 * interrupt stack

 * severe exception (double fault, nmi, stack fault, debug, mce) hardware stack

 */

 

4.后记

你可能认为作者在忽悠你,这就整一个dump_stack注释的文章呀,贯上了写dump_stack的头衔!

我只有一句话:

Just for fun!

时间: 2024-10-03 18:48:57

写个dump_stack【转】的相关文章

源代码-怎么样再linux下查看dump_stack()函数打印出来的信息?

问题描述 怎么样再linux下查看dump_stack()函数打印出来的信息? 本人Linux小白,刚学没多久,最近我想要研究下linux中打开文件操作的流程,于是我就在内核的filp open()函数的源代码中插入了dump_stack_()函数. 我重新编译内核之后,直接在终端调用cd 命令行,然后想在系统日志里面看看有没有 函数调用打印出来,但是翻来翻去好像什么都没有的样子. 但是我自己写一个简单的模块,里面有dump_stack_函数,在编译模块,再运行这个模块, 这样的话又可以在日志里

我在写串口通信程序遇到的坑

我在做基于HC6800的51单片机上,写串口通信程序遇到了许多坑. 一个大坑:为什么HC6800上U转串口不能发送数据. 因为我们向HC6800烧程序都是使用图1所示的软件,所以我想当然地认为可以直接通过HC6800上的U转串进行串口通信,而我的同学信誓旦旦地告诉我可以.坑爹.并且这个软件好像也在提示可以用直接用U转串,进行串口通信(见图2).但是当我打开串口调试助手的时候(如图3),却发现单片机掉电了(如图4).其实HC6800上有另一个串口的,我们只要把连接图5黄框处串口.并且在图4中的6处

如何用C#写代码批量下载网页上提供的附件。

问题描述 如何用C#写代码批量下载网页上提供的附件. 在一个网页上有一个导出PDF文件的功能,是当点击这个按钮后,执行一个脚本,然后弹出文件下载另存为的对话框进文件的下载功能. 现在希望用C#实现自动批量的下载并保存这些PDF文件. 求实现方法,谢谢! 解决方案 用webclient.downloadfile或者httpwebrequest去下载. 解决方案二: 建议压缩成ZIP包后再下载. 解决方案三: 执行的是一个脚本,现在我可以实现通过代码模拟这个单击操作,执行这个脚本,但是如何能得到下载

用java写的一个文件操作类包

前几天仔细看了看java的I/O操作,呵呵.就写了一个操作文件的类包,功能有创建文件或目录,删除文件或目录,复制文件或目录,移动文件或目录,设置文件或目录属性,查看文件或目录大小.呵呵,功能比较简单,源代码为: 创建: Java代码 package fileOperation; import java.io.File; import java.io.FileOutputStream; /** * @author wakin * */ public class Create { /**根据字符串生

请教 自己写的mysqli 操作数据库的类 DB.class.php

问题描述 请教 自己写的mysqli 操作数据库的类 DB.class.php 类是这样写的: <?php class DB{ //属性 private $host; private $port; private $name; private $pass; private $dbname; private $prefix; //设置表前缀 private $charset;//设置字符集 private $mysqli; //设置mysqli类对象 //设置构造函数 public functio

返回时C++内存问题opencv写的

问题描述 返回时C++内存问题opencv写的 //读取摄像头并保存为AVI文件 #include "cv.h" #include "highgui.h" int main(int argc,char**argv) { CvCapture* capture=0; IplImage *bgr_frame; int fps=25; int i; CvSize size; CvVideoWriter *writer; cvNamedWindow("Jimmy&q

hql语句查询实体类News的属性category为“生活类新闻”的LIST,HQL语句怎么写?

问题描述 hql语句查询实体类News的属性category为"生活类新闻"的LIST,HQL语句怎么写? 用SSH框架,写一个实现类,查询实体类News的属性category为"生活类新闻"的LIST 返回一个LIST,该怎么写???????????????????? 实体类为News 属性为category 数据库中表名为t_news category为属性 import java.util.List; import org.springframework.st

java-新手,如何为一个swing程序写一个按某键暂停功能?

问题描述 新手,如何为一个swing程序写一个按某键暂停功能? 比如一个贪吃蛇游戏,现在想在键盘上按一个键暂停游戏,比如游戏时按F5暂停游戏,此时我不知道应该把这个键盘事件注册到哪个事件源上呢?也就是在程序运行中由谁来监听这个键盘事件呢?还请大家指点一下! 解决方案 我觉得应该用暂停线程来控制,没用过,你可以试试

小程序 求解-这里有一题ACM的小题目,求众神解答。帮写个程序。小弟冰天雪地裸奔哭嚎以示感谢!

问题描述 这里有一题ACM的小题目,求众神解答.帮写个程序.小弟冰天雪地裸奔哭嚎以示感谢! 邮局选址: 在一个按照东西和南北方向划分成规整街区的城市里,n 个居民点散乱的分布在不同的街区中.用X坐标表示东西向,用Y坐标表示南北向,各居民点的位置可以有坐标(XY)表示.街区中任意2点(X1,Y1)和(X2,Y2)质检的距离可以用数值丨X1-X2丨+丨Y1-Y2丨度量.居民们希望在城市中选择建立邮局的最佳位置,使n 个居民点到邮局的距离总和最小. 编程任务: 给定n 个居民点的位置,计算n个居民点到