【内核配置】CONFIG_DEBUG_USER 配置项原理分析
背景
应用反馈设备运行过程中,应用APP出现运行报错,但是没有堆栈信息。
排查
1、尝试复现问题
针对这个现象,首先是做了对比测试,简单做了一个应用踩空地址的oops_demo:
#include <stdio.h>
#include <stdlib.h>int main() {
int *null_ptr = NULL;
int value;printf("空指针测试程序启动\n");// 故意解引用空指针,触发段错误
value = *null_ptr;printf("这行永远不会被执行: %d\n", value);return 0;
}
然后如果是交查编译环境,则通过交叉编译工具链编译:
.../arm-ca9-linux-uclibcgnueabihf-gcc oops_demo.c -o oops_demo
将编译成功后的成果物,拷贝到设备上运行。
出现现象如下:
# echo 7 4 1 7 > /proc/sys/kernel/printk
# ./oops_demo
空指针测试程序启动
Segmentation fault
确实没有出现堆栈信息。
2、对比其他平台,进一步确认问题
又找了另一个平台设备,做对比测试,发现如下现象:
# echo 7 4 1 7 > /proc/sys/kernel/printk
# ./oops_demo
空指针测试程序启动
<--- cut here ---oops: unhandled page fault (11) at 0x00000000, code 0x017
pgd = 385e9cea
[00000000] *pgd=013ca831, *pte=00000000, *ppte=00000000
CPU: 0 PID: 448 Comm: oops Tainted: P O 5.10.168 #6
Hardware name: Novatek Video Platform
PC is at 0x103dc
LR is at 0x76f34717
pc : [<000103dc>] lr : [<76f34717>] psr: 00000030
sp : 7e97dad0 ip : 92e9878c fp : 00000000
r10: 00000000 r9 : 00000000 r8 : 00000000
r7 : 7e97dad0 r6 : 00010305 r5 : 00000000 r4 : 76fcbc0c
r3 : 00000000 r2 : 00000001 r1 : 00000001 r0 : 0000001c
Flags: nzcv IRQs on FIQs on Mode USER_32 ISA Thumb Segment user
Control: 10c53c7d Table: 02134059 DAC: 00000055
CPU: 0 PID: 448 Comm: oops Tainted: P O 5.10.168 #6
Hardware name: Novatek Video Platform
[<80011cc4>] (unwind_backtrace) from [<80010200>] (show_stack+0x10/0x14)
[<80010200>] (show_stack) from [<800122e4>] (__do_user_fault+0x90/0x110)
[<800122e4>] (__do_user_fault) from [<8001265c>] (do_page_fault+0x260/0x26c)
[<8001265c>] (do_page_fault) from [<8001279c>] (do_DataAbort+0x34/0xb4)
[<8001279c>] (do_DataAbort) from [<8000907c>] (__dabt_usr+0x3c/0x40)
Exception stack(0x809bffb0 to 0x809bfff8)
ffa0: 0000001c 00000001 00000001 00000000
ffc0: 76fcbc0c 00000000 00010305 7e97dad0 00000000 00000000 00000000 00000000
ffe0: 92e9878c 7e97dad0 76f34717 000103dc 00000030 ffffffff
Segmentation fault
尝试换了一个其他设备测试,发现是能正常打印堆栈信息的。
3、对比内核配置
对比内核配置项,找到关键内核配置:
CONFIG_DEBUG_USER=y
开启该配置项后,应用oops触发时,会打印堆栈信息。
分析
CONFIG_DEBUG_USER 的作用
CONFIG_DEBUG_USER 是一个内核配置选项,专门用于控制用户空间程序发生错误时是否显示详细的调试信息。当你启用这个选项时,内核会在用户空间程序发生段错误等异常时打印详细的调试信息,包括寄存器状态、堆栈跟踪、页表信息等。
原理分析:
在ARM架构上,当用户空间程序尝试访问无效内存地址(如NULL指针)时,会发生以下流程:
硬件异常触发:CPU尝试访问地址0x00000000,触发数据中止异常(Data Abort)
异常处理:CPU跳转到内核的异常向量表,执行__dabt_usr处理函数
内核处理:内核通过do_DataAbort函数处理异常
页面错误处理:调用do_page_fault函数确定错误类型
用户错误处理:最终调用__do_user_fault函数处理用户空间的错误
CONFIG_DEBUG_USER 的影响
CONFIG_DEBUG_USER
配置项直接影响__do_user_fault
函数的行为。让我们看看内核源码中的实现:
// 在 arch/arm/mm/fault.c 中
#ifdef CONFIG_DEBUG_USER
void __do_user_fault(struct task_struct *tsk, unsigned long addr,unsigned int fsr, unsigned int sig, int code,struct pt_regs *regs)
{struct siginfo si;// 打印详细的调试信息printk(KERN_DEBUG "%s: unhandled page fault (%d) at 0x%08lx, code 0x%03x\n",tsk->comm, sig, addr, fsr);show_pte(tsk->mm, addr);show_regs(regs);// ... 其他调试信息
}
#else
void __do_user_fault(struct task_struct *tsk, unsigned long addr,unsigned int fsr, unsigned int sig, int code,struct pt_regs *regs)
{// 不打印调试信息,直接发送信号struct siginfo si;// 只设置信号信息,不打印调试信息
}
#endif
为什么会有两种不同的输出?
当你启用
CONFIG_DEBUG_USER=y
时:
- 内核会调用详细的
__do_user_fault
实现- 打印包括寄存器状态、堆栈跟踪、页表信息等详细调试信息
- 这就是你看到的第一种情况,有完整的堆栈信息
当你禁用
CONFIG_DEBUG_USER
时(或者未设置):
- 内核调用简化版的
__do_user_fault
实现- 不打印任何调试信息
- 直接向进程发送SIGSEGV信号
- 这就是你看到的第二种情况,只有简单的"Segmentation fault"
结论
CONFIG_DEBUG_USER
配置项直接控制内核在处理用户空间程序错误时的详细程度。启用时,内核会打印包括寄存器状态、堆栈跟踪等详细调试信息;禁用时,内核只会简单地终止进程而不打印额外信息。这个设计允许系统管理员根据使用场景(开发或生产)来选择适当的调试级别。
建议:
- 生产环境:通常禁用此选项,减少不必要的日志输出,提高性能
- 开发环境:启用此选项,便于调试用户空间程序的问题