【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的优化等级,应该也可以解决这个问题。

如本文对你有些许帮助,欢迎大佬支持我一下(点赞+收藏+关注、关注公众号等),您的支持是我持续创作的竭动力
支持我的方式

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

花神庙码农

你的鼓励是我码字的最大动力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值