#include
void usageErr(const char *format, ...)
{
va_list argList;
fflush(stdout);
fprintf(stderr, "Usage: ");
va_start(argList, format);
vfprintf(stderr, format, argList);
va_end(argList);
fflush(stderr);
exit(EXIT_FAILURE);
}
#define DBG_PRINTK printk
#define DBG_PRINTK(x....) // 根据编译的宏,决定调试还是不调试
printk("%s , %s, %d \n", __FILE__ , __func__, __LINE__);
cat /proc/sys/kernel/printk
7 4 1 7
console_loglevel, default_message_loglevel, minimum_console_loglevel, default_console_loglevel 这四个值
static int debug = 1;
module_param(debug, int, 0644);
MODULE_PARM_DESC(debug, "enable debugging information ");
#define dprintk (args...)\
if (debug) { \
printk (KERN_DEBUG args); \
}
asmlinkage __visible int printk(const char *fmt, ...)
{
va_list args;
int r;
va_start(args, fmt);
r = vprintk_func(fmt, args);
va_end(args);
return r;
}
EXPORT_SYMBOL(printk);
---->release_console_sem();
----->call_console_drivers(_con_start, _log_end);
------>_call_console_drivers(start_print, cur_index, msg_level);
/*
* Write out chars from start to end - 1 inclusive
*/
static void _call_console_drivers(unsigned long start,
unsigned long end, int msg_log_level)
{
if ((msg_log_level < console_loglevel || ignore_loglevel) &&
console_drivers && start != end) {
if ((start & LOG_BUF_MASK) > (end & LOG_BUF_MASK)) {
/* wrapped write */
__call_console_drivers(start & LOG_BUF_MASK,
log_buf_len);
__call_console_drivers(0, end & LOG_BUF_MASK);
} else {
__call_console_drivers(start, end);
}
}
}
dmesg 就是把log_buf[] 环形队列的缓冲区的信息 打印出来。 还有一路是 调用硬件的write函数输出
cat /proc/kmsg
地址转换为行号
段错误
[ 1.215000] Unable to handle kernel NULL pointer dereference at virtual address 00000000
[ 1.225000] pgd = c0004000
[ 1.225000] [00000000] *pgd=00000000
[ 1.230000] Internal error: Oops: 805 [#1] PREEMPT SMP ARM
[ 1.235000] Modules linked in:
[ 1.240000] CPU: 2 PID: 1 Comm: swapper/0 Not tainted 3.14.0 #12
[ 1.245000] task: ee8a0000 ti: ee8a4000 task.ti: ee8a4000
[ 1.250000] PC is at dm9000_probe+0x18/0x8fc
[ 1.255000] LR is at platform_drv_probe+0x18/0x48
[ 1.260000] pc : [] lr : [] psr: a0000153
[ 1.260000] sp : ee8a5e48 ip : 00000000 fp : 00000000
[ 1.270000] r10: c057d4fc r9 : 00000000 r8 : c05e6c64
[ 1.275000] r7 : 00000000 r6 : ee978810 r5 : ee978800 r4 : 00000000
[ 1.280000] r3 : 000000ff r2 : 00000000 r1 : ee8a5de8 r0 : ee978800
[ 1.290000] Flags: NzCv IRQs on FIQs off Mode SVC_32 ISA ARM Segment kernel
[ 1.295000] Control: 10c5387d Table: 4000404a DAC: 00000015
[ 1.300000] Process swapper/0 (pid: 1, stack limit = 0xee8a4240)
c029773c
将地址转换为行号
在内核的顶层目录下执行
arm-none-linux-gnueabi-addr2line 0xc029773c -e vmlinux -f
一般碰到NE的异常需要通过addr2line把异常地址转成code文件和行号。
1.进入到prebuilts/gcc/linux-x86/arm/cit-arm-linux-androideabi-4.8目录,
2.把libunwind.so从\out\target\product\XXX\symbols下面去找到该库文件,并把该so拷贝到cit-arm-linux-androideabi-4.8目录。注意:这个so一定要是出问题的版本的so,即使是同个项目不同软件,都不能代替。
3. 执行如下命令addr2line -e libunwind.so 0000e3ac
则可以把
// #00 pc 0000e3ac /system/lib/libunwind.so
// #01 pc 000109f7 /system/lib/libunwind.so (_Uelf32_memory_read+178)
// #02 pc 0000d803 /system/lib/libunwind.so
// #03 pc 000030db /system/lib/libunwind.so (unw_map_local_create+42)
// #04 pc 00009ca9 /system/lib/libbacktrace.so (_ZN14UnwindMapLocal5BuildEv+4)
// #05 pc 00009d8f /system/lib/libbacktrace.so (_ZN12BacktraceMap6CreateEib+138)
// #06 pc 0033b721 /system/lib/libart.so (_ZN3art10ThreadList4DumpERNSt3__113basic_ostreamIcNS1_11char_traitsIcEEEEb+164)
// #07 pc 0033b61f /system/lib/libart.so (_ZN3art10ThreadList14DumpForSigQuitERNSt3__113basic_ostreamIcNS1_11char_traitsIcEEEE+574)
// #08 pc 00320a8d /system/lib/libart.so (_ZN3art7Runtime14DumpForSigQuitERNSt3__113basic_ostreamIcNS1_11char_traitsIcEEEE+228)
// #09 pc 003252f7 /system/lib/libart.so (_ZN3art13SignalCatcher13HandleSigQuitEv+1394)
// #10 pc 00324465 /system/lib/libart.so (_ZN3art13SignalCatcher3RunEPv+336)
// #11 pc 00047fa3 /system/lib/libc.so (_ZL15__pthread_startPv+22)
// #12 pc 00019a0d /system/lib/libc.so (__start_thread+6)
用addr2line后得出:
/proc/self/cwd/external/libunwind/src/arm/Ginit.c:199
/proc/self/cwd/external/libunwind/src/elfxx.c:75
/proc/self/cwd/external/libunwind/src/elfxx.h:106
/proc/self/cwd/external/libunwind/src/mi/Lmap.c:70 (discriminator 1)
/proc/self/cwd/system/core/libbacktrace/UnwindMap.cpp:127
/proc/self/cwd/system/core/libbacktrace/UnwindMap.cpp:157
/proc/self/cwd/art/runtime/thread_list.cc:195 (discriminator 4)
/proc/self/cwd/art/runtime/thread_list.cc:144
/proc/self/cwd/art/runtime/runtime.cc:1411
/proc/self/cwd/art/runtime/signal_catcher.cc:144
/proc/self/cwd/art/runtime/signal_catcher.cc:215
/proc/self/cwd/bionic/libc/bionic/pthread_create.cpp:198 (discriminator 1)
/proc/self/cwd/bionic/libc/bionic/clone.cpp:41 (discriminator 1)
coredump
3.设置Core Dump的核心转储文件目录和命名规则
/proc/sys/kernel/core_uses_pid可以控制产生的core文件的文件名中是否添加pid作为扩展,如果添加则文件内容为1,否则为0
/proc/sys/kernel/core_pattern可以设置格式化的core文件保存位置或文件名,比如原来文件内容是core-%e
可以这样修改:
echo "/corefile/core-%e-%p-%t" > /proc/sys/kernel/core_pattern
将会控制所产生的core文件会存放到/corefile目录下,产生的文件名为core-命令名-pid-时间戳
以下是参数列表:
%p - insert pid into filename 添加pid
%u - insert current uid into filename 添加当前uid
%g - insert current gid into filename 添加当前gid
%s - insert signal that caused the coredump into the filename 添加导致产生core的信号
%t - insert UNIX time that the coredump occurred into filename 添加core文件生成时的unix时间
%h - insert hostname where the coredump happened into filename 添加主机名
%e - insert coredumping executable name into filename 添加文件名 也就是程序的名字
4.core文件的使用
在core文件所在目录下键入:
gdb -c core (-c指定core文件)
它会启动GNU的调试器,来调试core文件,并且会显示生成此core文件的程序名,中止此程序的信号等等
如果你已经知道是由什么程序生成此core文件的,比如MyServer崩溃了生成core.12345,那么用此指令调试:
gdb -c core MyServer
https://blog.csdn.net/zmjames2000/article/details/88209277
1. 需要配置内核 勾选 //CONFIG_FRAME_POINTER,表示帧指针,用fp寄存器表示
能打印栈回溯信息
2.查看 PC= 0xbf000018 它属于什么的地址, 是内核还是通过insmode加载的驱动程序(查看system.map)
如果不属于system.map 里的范围,则它属于 insmod加载的驱动程序。
如何确定是哪一个驱动程序? 一般 栈回溯中会有的 。
vim system.map 是从c0004000开始的,最后也是c开头的。
如果pc指针=0xbf000018 那就不是内核中的, 是外面加载报错的。
3. cat /proc/kallsyms 看内核加载的函数地址。
找到一个相近的地址, 找到 是用的哪一个.ko ,之后反汇编
在pc机上 arm-linux-objdump -D 26th_segmentfault.ko > 26th_segmentfault.dis //反汇编
如果是内核的问题,如下:
然后make uImage装载新内核后,再运行测试程序,便会打印出opps信息
在内核源码的根目录下通过:
# arm-none-linux-gnueabi-objdump -D vmlinux > vmlinux.dis //反汇编内核
将整个内核反汇编, vmlinux:未压缩的内核
vi vmlinux.dis,然后通过oops信息的PC值直接来查找地址即可
根据栈信息,找出函数调用关系 ( 没有栈回溯的 提示,以上可以通过栈回溯查找)
https://blog.csdn.net/zmjames2000/article/details/88209339
如果PC的值不再system.map 中,那就属于模块,不属于内核。
之后 cat /proc/kallsymbs 可以查看是哪个模块
之后反汇编模块, 和上面是一样的。
之后根据栈信息,和反汇编的信息,进行比较。找LR,
在栈信息中, 左边是低地址,后面是高地址, 所以一般反汇编存储stmdb的时候,最后一个是pc,最后第二个是lr
简单的就是看存里多少个数,从左边数倒数第二个就是LR
0x00000000 不算 属于 函数的开头,
PC 高地址存高位 | <-sp_old |
LR 这个就是返回地址 | |
IP | |
FP |
|
<-sp_new |
肯定会用到内核函数,所以需要反汇编内核。
根据刚才找到的LR地址,在内核汇编中找地址,之后再陆续找,就能找到相应的函数。
这个就是根据LR 连接寄存器来找回溯 函数调用的。
寄存器编辑器 也是内核调试的方式。
用来硬件寄存器 写入和读出的值是否正确。
https://blog.csdn.net/zmjames2000/article/details/88406274
当内核或驱动出现僵死bug,导致系统无法正常运行,怎么找到是哪个函数的位置导致的?
答,通过内核的系统时钟,因为它是由定时器中断产生的,每隔一定时间便会触发一次,所以当CPU一直在某个进程中时,我们便在中断函数中打印该进程的信息
https://blog.csdn.net/zmjames2000/article/details/88209427
cat /proc/interrupts 查看中断
Timer_tick 查找这个
主要方式如何: 定时器中断产生的,每隔一定时间便会触发一次,所以当CPU一直在某个进程中时,我们便在中断函数中打印该进程的信息
我们先选择__irq_usr作为下一步跟踪的目标:
4.1其中__irq_usr的实现如下(arch\arm\kernel\entry-armv.S):
__irq_usr:
usr_entry //保存数据到栈里
get_thread_info tsk
irq_handler //调用irq_handler
b ret_to_user
4.2.irq_handler的实现过程,arch\arm\kernel\entry-armv.S
.macro irq_handler
get_irqnr_preamble r5, lr
get_irqnr_and_base r0, r6, r5, lr // get_irqnr_and_base:获取中断号,r0=中断号
movne r1, sp //r1等于sp (发生中断之前的各个寄存器的基地址)
adrne lr, 1b
bne asm_do_IRQ //调用asm_do_IRQ, irq=r0 regs=r1
irq_handler最终调用asm_do_IRQ
4.3 asm_do_IRQ实现过程,arch/arm/kernel/irq.c
该函数和裸板中断处理一样的,完成3件事情:
1).分辨是哪个中断;
2).通过desc_handle_irq(irq, desc)调用对应的中断处理函数;
3).清中断
在其中打印当前current的信息
if(irq==30) //判断irq中断号,是否等于系统时钟 通过 cat /proc/interrupts 来看,或者 看时钟驱动irq.c
if(cnt==10*HZ) //超时10s 前提是 保存的进程号和当前进程号一致,表示卡死的征兆,所以等10s还是,就卡死了
{
cnt=0;
printk("s3c2410_timer_interrupt : pid = %d, task_name = %s\n",current->pid,current->comm);
printk("pc = %08x\n",regs->ARM_pc); // 主要就是为了打印出寄存器的值
}
之后重新编译内核, 卡死的时候,就会打印哪个进程出问题,还有pc指针, 之后通过查找oops方式调试就行了。
cat /proc/kallsymbs 或 cat system.maps 看是否是内核的还是 模块的
之后反汇编模块或者linux-->vmlinux
独立进程崩溃的原因可能是:
1)非法地址访问,用户空间进程不能直接访问高1GB地址空间和0x08048000以下的地址空间,需要通用系统调用进行读写操作,如果非法访问了这些区域,则会被内核线程捕捉到,内核会报segmentation fault段错误,而用户进程则会crash。另外,访问堆和栈之间的空白区域也会导致segmentation fault,因为他们没有经过系统调用通过MMU映射到真正的物理内存,即野指针错误。这一段内存区域只是名义上存在的,而不是物理上实际存在的。
2) 空指针错误,空指针是指向0x00000000的,用户进程读写空指针会引发非法地址访问错误,对于用户进程,系统会报segmentation fault,用户进程crash。对于内核进程,系统会报NULL pointer错误,系统会panic重启。重复删除同一指针节点,如多次free同一段内存空间的情况下,就会引发此类问题。
3) 内存泄露,内存泄露是指动态分配的内存使用完毕之后未及时释放,对于常驻内存的守护进程和服务而言,内存泄露会导致堆大小持续向上增长,进而覆盖栈内存区域,这样就会导致程序跑飞。因此,malloc的内存使用完毕,必须要及时释放。
4) 结构体或者变量字节对齐问题,对于跨平台和网络通讯而言,此类问题是多发问题。如果结构体或者变量不对齐,会导致读取的数据产生位移,取不到正确的信息,导致程序跑飞。
5)栈溢出问题, 一般是没有对函数的入参做检查,如通过入参把超过char型数组长度的字符串填充到函数内部的char型数组局部变量中, 字符串就会冲掉函数栈帧,从而导致程序执行异常。
6)缓冲区溢出,是指往固定大小的内存区域填充超过其长度的内容,从而导致缓冲区越界擦除其他数据导致程序跑飞的情况。常见的是字符串缓冲区无’\0’结束符,这样调用printf等函数时就会溢出访问超过缓冲区的内存进而引发错误。
7)栈溢出,这是一类特殊的缓冲区溢出问题,简单说就是函数的入参占据的内存大小超过了函数内部为保存该参数分配的内存空间,导致栈帧越界覆盖其他栈帧数据,规避这一类问题的方法是在函数入口严格对入参进行检查。
进程间通讯导致进程崩溃的原因可能是:
1)死锁,如果两个进程死锁得不到调度,会导致进程本身处于D状态,但是系统不会崩溃;如果进程死锁,抢占CPU资源,其他进程得不到调度,就会导致看门狗超时,从而引发系统异常重启。(注:个人对进程间通讯机制理解还不深刻,暂时只能总结这么多)
上面大致介绍了导致系统异常的一些原因,那么,针对不同的错误类型,可以通过不同的方法进行调试分析。接下来的部分会由浅入深,分别介绍一些常用的内核调试方法和工具。
备注:关于sysfs, debugfs, procfs, printk之类的调试技巧请自行百度。
https://blog.csdn.net/zmjames2000/article/details/88389392