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的文件,然后我们就可以 ...
随机推荐
- Python接口测试,Requests模块讲解:GET、POST、Cookies、Session等
文章最下方有对应课程的视频链接哦^_^ 一.安装.GET,公共方法 二.POST 三.Cookies 四.Session 五.认证 六.超时配置.代理.事件钩子 七.错误异常
- 搬砖的也能学Python----if - elif 语句
引入:如果平时执行的过程超过两个分支,则使用if-elif语句 if-elif 语句结构 if 判断条件: 要执行的代码 elif 判断条件: 要执行的代码 -- else: 要执行的代码 判断条件: ...
- KVM虚拟化网络优化技术总结
https://www.intel.com/content/dam/www/public/us/en/documents/technology-briefs/sr-iov-nfv-tech-brief ...
- IDirectDraw接口
创建一个主DirectDraw对象,并使用QueryInterface()方法来得到最新的IDirectDraw7接口,或是直接用DirectDrawCreateEx()函数直接创建一个DirectD ...
- Java之List排序出错
Java之List排序出错 Bound mismatch: The generic method sort(List<T>) of type Collections is not appl ...
- Java之split()方法
Java之split()方法 1.方法介绍 (1)public String[] split(String regex) 根据给定正则表达式的匹配拆分此字符串 (2)public String[] s ...
- Caused by:java.sql.BatchUpdateException:ORA-02291
1.错误描述 Caused by:java.sql.BatchUpdateException:ORA-02291:违反完整约束条件(PEKING.FKA844BA60FCCDD33)-未找到父项关键字 ...
- 完美的js运动框架
//完美运动框架, 对象,json,函数function move(obj,json,funEnd){clearInterval(obj.timer);//清除定时器obj.timer= setInt ...
- .Net Core 1.0升级2.0(xproj项目迁移到.csproj )
vs2015的创建的项目是以*.xproj的项目文件,迁移到vs2017需要如下准备: 1.安装好vs2017(废话) 2.下载最新的SDK和 .NET Core 2.0 Preview 1 Runt ...
- Java序列化机制和原理及自己的理解
Java序列化算法透析 Serialization(序列化)是一种将对象以一连串的字节描述的过程:反序列化deserialization是一种将这些字节重建成一个对象的过程.Java序列化API提供一 ...