segment fault异常及常见定位手段
问题背景
最近boot中遇到个用户态程序的segment fault异常,除了一句“Segment fault”打印外无其他任何打印。该问题复现概率较低,定位起来比较棘手。我们的boot是个经过裁剪的最小linux系统,由于bootflash大小的限制,加上在boot阶段也没有挂载其他储存设备,所以没有没法放gdb、动态库等体积较大的调试工具。本文以linux 3.10内核和mips cpu小系统为基础,记录下对这个问题的研究总结。
segment fault 异常处理流程
用户态程序由于系统调用或异常等原因,系统陷入内核,并伴随着CPU特权的切换和从用户态栈到内核态栈的切换,内核调用SAVE_ALL保存陷入内核前的现场(即pt_regs结构)到内核栈上,然后内核通过查找异常跳转表或系统调用跳转表获得相应的处理程序入口,处理完成后,给用户态程序发送SIGSEGV信号,并通过pt_regs恢复现场返回到用户态程序,用户态程序收到SIGSEGV信号并进行处理。至此,完成全部处理流程。
可见异常前的现场信息,即pt_regs是个很重要的信息,其具体定义如下,包括CPU通用寄存器、error pc、error cause、bad address等信息。
在linux kernel中,如下场景都会触发pt_regs压栈动作:
- tlb异常
- NMI中断
- 中断
- 异常
- 系统调用
struct pt_regs {
#ifdef CONFIG_32BIT
/* Pad bytes for argument save space on the stack. */
unsigned long pad0[];
#endif
/* Saved main processor registers. */
unsigned long regs[];
/* Saved special registers. */
unsigned long cp0_status;
unsigned long hi;
unsigned long lo;
#ifdef CONFIG_CPU_HAS_SMARTMIPS
unsigned long acx;
#endif
unsigned long cp0_badvaddr;
unsigned long cp0_cause;
unsigned long cp0_epc;
#ifdef CONFIG_MIPS_MT_SMTC
unsigned long cp0_tcstatus;
#endif /* CONFIG_MIPS_MT_SMTC */
} __attribute__ ((aligned ()));
segment fault 常见触发源
内核会依据下列条件来判断是否发生了用户态段错误,并上报SIGSEGV信息给用户态task:
- 用户态数据段的地址越界
- 用户态代码段的指令读取异常
- 访问操作与所访问的内存页面权限不匹配
- 非对齐访问(一般是上报SIGBUS,但mips会上报SIGSEGV)
导致段错误的常见编程范式有:
- 使用未初始化变量
- 使用已释放的内存
- 数组越界
- 多进程下使用不可重入函数
- 内存被踩(如栈被踩导致pc或数据寻址错误等)
segment fault 常用定位手段
最佳的定位手段是能直接定位到产生异常的代码,差一点的,至少能提供相关信息,通过分析能间接定位到异常代码。segment fault的定位手段还是比较丰富的,但也各有优缺点,需要根据具体场景进行选用。
gdb
gdb的优点是调试手段丰富,可以逐步跟踪调试,适用于稳定复现的故障。缺点是故障必须能必现。
coredump
coredump的优点是对于偶现的段错误故障,内核会导出一个coredump文件,然后可以用gdb离线调试coredump文件来定位。缺点是如果环境对段错误等异常有重启保护,coredump文件需要有地方存储。
用户态backtrace
glibc的execinfo库提供一套接口:backtrace、backtrace_symbols,可以通过这套接口,捕获到SIGSEGV异常后打印异常发生时的backtrace。缺点是依赖glibc的excinfo,而各CPU对其实现支持情况不一。
内核态backtrace
内核态call trace打印一般通过stack_dump来打印,由于linux的内核态栈和用户态栈是独立分开了,所以stack_dump并不支持用户态call trace打印。但内核提供了save_stack_trace_user/print_stack_trace接口,可以在异常处理程序中打印用户态进程的调用链。缺点是这套接口在arch下实现,而各CPU对其实现支持情况不一。
catchsegv
catchsegv是libc提供的支持段错误back trace打印脚本,可以在发生SIGSEGV时直接打印出异常点的backtrace。缺点是依赖libc的libSegFault.so和addr2line工具。
pt_regs
如前文所说,pt_regs对象提供了异常发生时的error pc、error cause、bad address等信息,反汇编用户程序后,通过error pc等信息可以找到具体的异常汇编指令和函数,分析汇编代码找到对应的C代码。缺点是需要人工分析汇编代码。
解决方案
回到本文一开始的问题,由于bootflash大小的限制,加上在boot阶段也没有挂载其他储存设备,gdb、coredump、catchsegv都没法用;libc对mipc arch下的backtrace实现有问题,用户态backtrace也没法用;mips arch内核没有实现内核态backtrace的接口,所以也没法用。所以只剩下打印pt_regs这一条路了,在上报SIGSEGV前,调用打印即可。虽然mips arch也没有实现打印方法,不过实现很简单,具体实现如下:
static inline void
show_signal_msg(struct pt_regs *regs, unsigned long error_code,
unsigned long address, struct task_struct *tsk)
{
unsigned long sp = regs->regs[];
unsigned long pc = regs->cp0_epc; if (!unhandled_signal(tsk, SIGSEGV))
return; if (!printk_ratelimit())
return; printk("%s%s[%d]: segfault at %lx ip %p sp %p error %lx",
task_pid_nr(tsk) > ? KERN_INFO : KERN_EMERG,
tsk->comm, task_pid_nr(tsk), address,
(void *)pc, (void *)sp, error_code); print_vma_addr(KERN_CONT " in ", pc); printk(KERN_CONT "\n");
}
实例演示:
模拟segv:
const char* p = "abcd";
*(char*)p = 'a';
内核打印:
4:<6>lxImage[1813]: segfault at 1200e5e98 ip 0000000120012628 sp 000000ffffb08140 error 1 4:<c> in lxImage[120000000+124000] 4:<c>反汇编获知在 BSP_RstReason_Print 函数中sb v1,0(v0) 指令对地址1200e5e98写操作引起segment fault异常
00000001200125a0 <BSP_RstReason_Print>:
...
120012628: a0430000 sb v1,0(v0)
segment fault异常及常见定位手段的更多相关文章
- GDB调试之core文件(如何定位到Segment fault)
core dump又叫核心转储,当程序运行过程中发生异常,程序异常退出时,由操作系统把程序当前的内存状况存储在一个core文件中,叫core dump.(内部实现是:linux系统中内存越界会收到SI ...
- 【Z】段错误Segment Fault定位,即core dump文件与gdb定位
使用C++开发系统有时会出现段错误,即Segment Fault.此类错误程序直接崩溃,通常没有任何有用信息输出,很难定位bug,因而无从解决问题.今天我们介绍core dump文件,并使用gdb进行 ...
- segment fault 定位 与 远程 gdb
远程 GDB 首先 ,Target 为 ARM开发板 (IP = 192.168.1.200),HOST 为 Ubuntu 14.04 虚拟机 (IP = 192.168.1.4) 1. 下载 ...
- STM32 F4xx Fault 异常错误定位指南
STM32 F407 采用 Cortex-M4 的内核,该内核的 Fault 异常可以捕获非法的内存访问和非法的编程行为.Fault异常能够检测到以下几类非法行为: 总线 Fault: 在取址.数据读 ...
- Segment fault及LINUX core dump详解 (zz)
C 程序在进行中发生segment fault(core dump)错误,通常与内存操作不当有关,主要有以下几种情况: (1)数组越界. (2)修改了只读内存. (3)scanf("%d&q ...
- 出现segment fault 错误的几种原因
segment fault 段异常各种原因www.MyException.Cn 发布于:2012-11-26 11:48:50 浏览:24次 0 segment fault 段错误各种原因一 造成se ...
- Segment fault及LINUX core dump详解
源自:http://andyniu.iteye.com/blog/1965571 core dump的概念: A core dump is the recorded state of the work ...
- Cortex-M3和Cortex-M4 Fault异常应用之二 ----- Fault处理函数的实现
在项目处于调试期间,Fault处理程序可能只是一个断点指令,调试器遇到这个指令后停止程序的运行.默认情况下,由于非硬Fault被禁能,所有发生的非Fault都会上访成硬Fault,因此只要在硬Faul ...
- Linux下如何生成core dump 文件(解决segment fault段错误的问题)
Linux下的C程序常常会因为内存访问等原因造成segment fault(段错误),如果此时core dump 的功能是打开的,在运行我们的可执行程序时就会生成一个名为core的文件,然后我们就可以 ...
随机推荐
- CodeForces-748B
关键在于判断是否能够得到解决办法,我的思路就是用一个数组来记录每个小写字母对应的按键,如果它出现对应两个级以上不同的按键那么就说明不能得出解决办法,直接打印'-1'.如果能够得出解决办法,就扫描一下数 ...
- HDU - 1430 魔板 (bfs预处理 + 康托)
对于该题可以直接预处理初始状态[0, 1, 2, 3, 4, 5, 6, 7]所有可以到达的状态,保存到达的路径,直接打印答案即可. 关于此处的状态转换:假设有初始状态为2,3,4,5,0,6,7,1 ...
- hbase存储优化
1.上面的2张图主要说明hbase的存储特点 (1).每个值(每条记录的每一个列的值)的存储,都完整的存储了rowkey.column family.column.版本(时间戳),以及该列的值. 这样 ...
- 一个逼格很低的appium自动化测试框架
Github地址: https://github.com/wuranxu 使用说明 1. 安装配置Mongo数据库 下载地址 mongo是用来存放元素定位的,截图如下: 通过case_id区分每个ca ...
- Dropout
参数正则化方法 - Dropout 受人类繁衍后代时男女各一半基因进行组合产生下一代的启发,论文(paper.pdf)提出了Dropout. Dropout是一种在深度学习环境中应用的正规化手段.它是 ...
- probabilistic robotics_Kalman filter(一)
码农生活告一段落,继续.... 多元正态分布 协方差矩阵,为正定对称矩阵.det表示行列式 协方差反应随机样本变量各分量之间的相关性. 当变量的假设模型不一致时,不适合用高斯滤波. 叠加高斯噪声的线性 ...
- PHP 常量dirname(__file__)
PHP 常量dirname(__FILE__)取得当前文件的绝对路径. define('ROOT_PATH', str_replace('includes/2.php', '', str_replac ...
- Windows下的Memcache安装:
Windows下的Memcache安装:1. 下载memcache的windows稳定版,解压放某个盘下面,比如在c:\memcached2. 在终端(也即cmd命令界面)下输入 'c:\memcac ...
- AM335x(TQ335x)学习笔记——USB驱动移植
对于AM335x来讲,TI维护的USB驱动已经非常完善了,本文称之为移植,实际上仅仅是配置内核选项使能USB HOST/OTG功能.废话少说,直接动手开启AM335x的USB驱动配置项. Step1. ...
- Linux显示2015年日历表
Linux显示2015年日历表 youhaidong@youhaidong-ThinkPad-Edge-E545:~$ cal 2015 2015 一月 二月 三月 日 一 二 三 四 五 六 日 一 ...