- 🐚作者简介:花神庙码农(专注于Linux、WLAN、TCP/IP、Python等技术方向)
- 🐳博客主页:花神庙码农 ,地址:https://blog.csdn.net/qxhgd
- 🌐系列专栏:Linux技术
- 📰如觉得博主文章写的不错或对你有所帮助的话,还望大家三连支持一下呀!!! 👉关注✨、点赞👍、收藏📂、评论。
- 如需转载请参考转载须知!!
dump_stack导栈信息不准确问题浅析
简介
- dump_stack函数是Linux内核开发调试时用于堆栈回溯的常用手段,但令人苦恼的是,有些情况下,堆栈回溯信息并不准确,打印出来的调用栈和实际的对不上,可能的情况及原因有很多种,本文只分析函数本身的因素。
- 在调试过程中,发现有的static函数会导栈失败,有的static函数不会。static函数导致失败的时候,把static去掉就正常了。这说明不是static直接导致的导栈失败。本文将会通过实验来验证下,到底是什么因素导致的失败,以及规避的方法。
准备基础demo
基础程序如下
- hello.c文件
#include<linux/module.h>
#include<linux/kernel.h>
#include<linux/init.h>
MODULE_LICENSE("GPL");
static int hello_init(void)
{
printk(KERN_ALERT "Hello World\n");
return 0;
}
static void hello_exit(void)
{
printk(KERN_ALERT "Goodbye World\n");
}
module_init(hello_init);
module_exit(hello_exit);
- Makefile文件
obj-m := hello.o
KERNEL_DIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
all:
make -C $(KERNEL_DIR) M=$(PWD) modules
clean:
make -C $(KERNEL_DIR) M=$(PWD) clean
编译命令
- 直接make即可。
操作的命令
sudo insmod hello.ko #加载模块
lsmod #查看hello是否成功加载
dmesg #查看ko执行的日志
sudo rmmod hello #卸载模块
基础代码
代码改造
- 将之前程序改造一下,在hello_init中调用一个普通的func函数:
void func(void)
{
dump_stack();
}
static int hello_init(void)
{
printk(KERN_ALERT "Hello World\n");
func();
return 0;
}
看下导栈情况,正常的
- 导栈如下:
[523017.373263] Call Trace:
[523017.373283] dump_stack+0x66/0x90
[523017.373288] ? func+0xa/0xa [hello]
[523017.373291] hello_init+0x16/0x19 [hello]
[523017.373298] ? do_one_initcall+0x46/0x1c3
[523017.373305] ? free_unref_page_commit+0x9b/0x110
[523017.373309] ? _cond_resched+0x15/0x30
[523017.373314] ? kmem_cache_alloc_trace+0x15f/0x1e0
[523017.373318] ? do_init_module+0x5a/0x210
[523017.373320] ? load_module+0x2064/0x22a0
[523017.373324] ? __do_sys_finit_module+0xa8/0x110
[523017.373326] ? __do_sys_finit_module+0xa8/0x110
[523017.373329] ? do_syscall_64+0x5f/0x190
[523017.373332] ? async_page_fault+0x8/0x30
[523017.373336] ? entry_SYSCALL_64_after_hwframe+0x44/0xa9
简单分析
- 编译出ko之后,发现ko中有func这个符号:
[qxhgd@linux ~]$ readelf -a hello.ko | grep -i func
27: 0000000000000000 10 FUNC LOCAL DEFAULT 4 func
28: 000000000000000a 25 FUNC LOCAL DEFAULT 4 hello_init
29: 0000000000000023 17 FUNC LOCAL DEFAULT 4 hello_exit
32: 0000000000000023 17 FUNC GLOBAL DEFAULT 4 cleanup_module
34: 000000000000000a 25 FUNC GLOBAL DEFAULT 4 init_module
- 反汇编看下:
objdump -D -S ./hello.ko > hello.txt
汇编代码如下:
void func(void)
{
0: e8 00 00 00 00 callq 5 <func+0x5>
dump_stack();
5: e9 00 00 00 00 jmpq a <init_module>
000000000000000a <init_module>:
}
static int hello_init(void)
{
a: e8 00 00 00 00 callq f <init_module+0x5>
printk(KERN_ALERT "Hello World\n");
f: 48 c7 c7 00 00 00 00 mov $0x0,%rdi
16: e8 00 00 00 00 callq 1b <init_module+0x11>
dump_stack();
1b: e8 00 00 00 00 callq 20 <init_module+0x16>
func();
return 0;
}
使用static函数
代码改造
- 将之前程序改造一下,在hello_init中调用一个func函数:
static void func(void)
{
dump_stack();
}
static int hello_init(void)
{
printk(KERN_ALERT "Hello World\n");
func();
return 0;
}
看下导栈情况,有点问题,func被省略了
- 导栈如下:
[474765.242331] Call Trace:
[474765.258744] dump_stack+0x66/0x90
[474765.262210] ? 0xffffffffc0c4b000
[474765.262216] hello_init+0x16/0x19 [hello]
[474765.262241] ? do_one_initcall+0x46/0x1c3
[474765.262260] ? free_unref_page_commit+0x9b/0x110
[474765.262270] ? _cond_resched+0x15/0x30
[474765.262284] ? kmem_cache_alloc_trace+0x15f/0x1e0
[474765.262292] ? do_init_module+0x5a/0x210
[474765.262294] ? load_module+0x2064/0x22a0
[474765.262298] ? __do_sys_finit_module+0xa8/0x110
[474765.262299] ? __do_sys_finit_module+0xa8/0x110
[474765.262302] ? do_syscall_64+0x5f/0x190
[474765.262305] ? async_page_fault+0x8/0x30
[474765.262308] ? entry_SYSCALL_64_after_hwframe+0x44/0xa9
简单分析
- 编译出ko之后,发现ko中并没有func这个符号:
[qxhgd@linux ~]$ readelf -a hello.ko | grep -i func
27: 0000000000000000 25 FUNC LOCAL DEFAULT 4 hello_init
28: 0000000000000019 17 FUNC LOCAL DEFAULT 4 hello_exit
31: 0000000000000019 17 FUNC GLOBAL DEFAULT 4 cleanup_module
33: 0000000000000000 25 FUNC GLOBAL DEFAULT 4 init_module - 反汇编看下:
objdump -D -S ./hello.ko > hello.txt
可以看到,static而非inline的func函数被内联了:
static int hello_init(void)
{
0: e8 00 00 00 00 callq 5 <init_module+0x5>
printk(KERN_ALERT "Hello World\n");
5: 48 c7 c7 00 00 00 00 mov $0x0,%rdi
c: e8 00 00 00 00 callq 11 <init_module+0x11>
dump_stack();
11: e8 00 00 00 00 callq 16 <init_module+0x16>
func();
return 0;
}
此处,当编译器对内联函数作展开处理时,会直接在调用处展开内联函数的代码,不再给 func() 函数本身生成单独的汇编代码。这是因为其它调用该函数的位置都作了内联展开,没必要再去生成。
使用noinline
继续改造
static void noinline func(void)
{
dump_stack();
}
static int hello_init(void)
{
printk(KERN_ALERT "Hello World\n");
func();
return 0;
}
查看导栈信息,已经正常:
[475875.681135] Call Trace:
[475875.681147] dump_stack+0x66/0x90
[475875.681162] ? func+0xa/0xa [hello]
[475875.681164] hello_init+0x16/0x19 [hello]
[475875.681172] ? do_one_initcall+0x46/0x1c3
[475875.681179] ? free_unref_page_commit+0x9b/0x110
[475875.681184] ? _cond_resched+0x15/0x30
[475875.681189] ? kmem_cache_alloc_trace+0x15f/0x1e0
[475875.681193] ? do_init_module+0x5a/0x210
[475875.681194] ? load_module+0x2064/0x22a0
[475875.681198] ? __do_sys_finit_module+0xa8/0x110
[475875.681199] ? __do_sys_finit_module+0xa8/0x110
[475875.681203] ? do_syscall_64+0x5f/0x190
[475875.681205] ? async_page_fault+0x8/0x30
[475875.681208] ? entry_SYSCALL_64_after_hwframe+0x44/0xa9
简单分析
- 查看符号表,其中有func这个符号:
[qxhgd@linux ~]$ readelf -a hello.ko | grep -i func
27: 0000000000000000 10 FUNC LOCAL DEFAULT 4 func
28: 000000000000000a 25 FUNC LOCAL DEFAULT 4 hello_init
29: 0000000000000023 17 FUNC LOCAL DEFAULT 4 hello_exit
32: 0000000000000023 17 FUNC GLOBAL DEFAULT 4 cleanup_module
34: 000000000000000a 25 FUNC GLOBAL DEFAULT 4 init_module
-反汇编看下,
objdump -D -S ./hello.ko > hello.txt
可以看到,func已经不再是内联了:
static void noinline func(void)
{
0: e8 00 00 00 00 callq 5 <func+0x5>
dump_stack();
5: e9 00 00 00 00 jmpq a <init_module>
000000000000000a <init_module>:
}
static int hello_init(void)
{
a: e8 00 00 00 00 callq f <init_module+0x5>
printk(KERN_ALERT "Hello World\n");
f: 48 c7 c7 00 00 00 00 mov $0x0,%rdi
16: e8 00 00 00 00 callq 1b <init_module+0x11>
func();
1b: e8 e0 ff ff ff callq 0 <func>
return 0;
}
小结
- 造成dump_stack函数的原因,是因为gcc将部分static函数优化为inline函数了。如果函数没有使用static修饰,则可能不会被优化为inline函数。
- 通过在函数中增加noinline修饰符,也可保证不被优化。
- 另外,通过调整gcc的优化等级,应该也可以解决这个问题。
如本文对你有些许帮助,欢迎大佬支持我一下(点赞+收藏+关注、关注公众号等),您的支持是我持续创作的竭动力
支持我的方式