当前位置: 首页 > 科技观察

你知道Linux内核的妙用Oops

时间:2023-03-21 11:22:59 科技观察

什么是Oops?从语言学的角度来看,Oops应该是一个象声词。遇到一点小意外,或者做了什么丢人的事后,可以说“哎呀”,中文翻译过来就是“哦友”。“糟糕,对不起,对不起,我真的不是故意打碎你的玻璃的。”看,这就是Oops的意思。Linux内核开发中的Oops是什么?其实它和上面的解释没有本质区别,只是谈话的主角变成了Linux。当一些致命的问题出现时,我们的Linux内核也会对我们说对不起:“哎呀(Oops),对不起,我搞砸了。”Linux内核在发生kernelpanic时会打印出Oops信息,向我们展示当前的寄存器状态、堆栈内容以及完整的Calltr??ace,可以帮助我们定位错误。接下来,让我们看一个例子。为了突出本文的主角——Oops,这个例子的唯一作用就是创建一个空指针引用错误。#include#includestaticint__inithello_init(void){int*p=0;*p=1;return0;}staticvoid__exithello_exit(void){return;}module_init(hello_init);module_exit(hello_exit);MODULE_LICENSE("GPL");很明显,错误的地方是第8行。接下来,我们编译这个模块,使用insmod将其插入到内核空间。正如我们所料,Oops出现了。[100.243737]BUG:unabletohandlekernelNULLpointerdereferenceat(null)[100.244985]IP:[]hello_init+0x5/0x11[hello][100.262266]*pde=00000000[100.288395]Oops:0002[#40]Sfiles5s[062266]*pde=00000000/sys/devices/virtual/sound/timer/uevent[100.325955]Moduleslinkedin:hello(+)vmblockvsockvmmemctlvmhgfsacpiphpsnd_ens1371gameportsnd_ac97_codecac97_bussnd_pcm_osssnd_mixer_osssnd_pcmsnd_seq_dummysnd_seq_osssnd_seq_midisnd_rawmidisnd_seq_midi_eventsnd_seqsnd_timersnd_seq_deviceppdevpsmouseserio_rawfbcontileblitfontbitblitsoftcursorsndparport_pcsoundcoresnd_page_allocvmcii2c_piix4vga16fbvgastateintel_agpagpgartshpchplpparportfloppypcnet32miimptspimptscsihmptbasescsi_transport_spivmxnet[100.472178][100.494931]Pid:1586,comm:insmodNottainted(2.6.32-21-generic#32-Ubuntu)VMwareVirtualPlatform[100.540018]EIP:0060:[]EFLAGS:00010246CPU:0[100.562844]EIPisathello_init+0x5/0x11[你好][100.584351]EAX:00000000EBX:fffffffcECX:f82cf01040EDX0[00.609358]ESI:f82cf040EDI:00000000EBP:f1b9ff5cESP:f1b9ff5c[100.631467]DS:007bES:007bFS:00d8GS:00e0SS:0068[100.657664]Processinsmod(pid:1586,ti=f1b9e000task=f137b340task.ti=f1b9e000)[100.706083]Stack:[100.731783]f1b9ff88c0101131f82cf040c076d240fffffffcf82cf0400072cff4f82d2000[100.759324]<0>fffffffcf82cf0400072cff4f1b9ffacc0182340f19638f8f137b340f19638c0[100.811396]<0>0000000409cc901809cc901800020000f1b9e000c01033ec09cc901800015324[100.891922]CallTrace:[100.916257][]?do_one_initcall+0x31/0x190[100.943670][]?hello_init+0x0/0x11[Hello][100.970905][]?SYS_INIT_MODULE+0xB0/0x210[100.995542][>]SYSCALL_CALL_CALL_CALL+0x7/0XB[101.024087]code:101.02401000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000Y00000000来hello_init+0x5/0x11[hello]SS:ESP0068:f1b9ff5c[101.134682]CR2:0000000000000000[101.158929]---[endtracee294b69a66d752cb]---Oops先描述一下是什么bug,然后指出bug出现在哪里,也就是说,“IP:[]hello_init+0x5/0x11[hello]”这里需要借助一个辅助工具objdump来帮助分析问题。objdump可以用来反汇编,命令格式如下:objdump-Shell.o下面是hello。o反汇编结果混入了C代码,非常直观。hello.o:fileformatelf32-i386Disassemblyofsection.init.text:00000000:#include#includestaticint__inithello_init(void){0:55push%ebpint*p=0;*p=1;return0;}1:31c0xor%eax,%eax#include#includestaticint__inithello_init(void){3:89e5mov%esp,%ebpint*p=0;*p=1;5:c7050000000001movl$0x1,0x0c:000000return0;}f:5dpop%ebp10:c3retDisassemblyofsection.exit.text:00000000:staticvoid__exithello_exit(void){0:55push%ebp1:89e5mov%esp,%ebp3:e8fcffffffcall4return;}8:5dpop%ebp9:c3ret对比Oops的提示,我们可以清楚的看到,错误位置hello_init+0x5的汇编代码为:5:c7050000000001movl$0x1,0x0这段代码的作用是将值1存入地址0,这个操作当然是非法的。我们还可以看到它对应的c代码是:*p=1;Bingo!我们在Oops的帮助下很快解决了这个问题。让我们回过头来查看上面的Oops,看看Linux内核是否给我们留下了其他有用的信息。Oops:0002[#1]这里0002表示Oops错误码(写错误,发生在内核空间),#1表示这个错误发生过一次。Oops错误代码根据错误原因有不同的定义。本文中的例子可以参考下面的定义(如果你发现遇到的Oops不能对应下面的,***去内核代码里找):*error_code:*bit0==0meansnopagefound,1meansprotectionfault*bit1==0meansread,1meanswrite*bit2==0meanskernel,1meansuser-mode*bit3==0meansdata,1meansinstruction有时候Oops会打印Tainted信息。该信息用于指出内核被污染的原因(直译为“被污染”)。具体的定义如下:1:如果加载的所有模块都具有GPL或兼容许可证,则为“G”,如果已加载任何专有模块,则为“P”。没有MODULE_LICENSE的模块或带有MODULE_LICENSE的模块未被insmod识别为GPL兼容模块被认为是专有的。2:'F'如果任何模块被“insmod-f”强制加载,''如果所有模块都正常加载。3:'S'如果oops发生在SMP内核上,该内核运行在未经认证可安全运行多处理器的硬件上。目前,这仅发生在不支持SMP的各种Athlon上。4:如果模块被“rmmod-f”强制卸载,则为“R”,如果所有模块都正常卸载,则为“”。5:如果任何处理器报告了机器检查异常,则为“M”,如果没有发生机器检查异常,则为“”。6:如果页面释放函数发现错误的页面引用或一些意外的页面标志,则为“B”。7:'U',如果用户或用户应用程序明确要求Tainted设置标志,否则为''。8:'D'如果内核最近死了,即有OOPS或BUG。9:如果ACPI表已被覆盖,则为“A”。10:'W'如果内核先前已发出警告。(虽然一些警告可能会设置更具体的污点标志。)11:'C'如果已加载暂存驱动程序。12:如果内核正在解决平台固件(BIOS或类似)中的严重错误,则为“I”。基本上,这些Tainted信息是为内核开发人员保留的。如果用户在使用Linux的过程中遇到Oops,可以将Oops的内容发送给内核开发者进行调试。内核开发人员利用这些Tainted信息大概可以确定内核panic时内核运行的环境。如果我们只是在调试自己的驱动程序,那么这些信息是没有意义的。本文中的示例非常简单。Oops发生后并没有造成宕机,所以我们可以从dmesg中查看到完整的信息。但更多情况下,系统也会在Oops发生时宕机。这时,这些错误信息来不及存储在文件中,断电后就看不到了。我们只能用其他方式记录:手抄或拍照。还有更糟糕的情况,如果Oops信息太多,一页屏幕显示不全,怎么查看完整的内容呢?第一种方法是在grub中用vga参数指定更高的分辨率,让屏幕可以显示更多的内容。显然,这种方法解决不了太多的问题;第二种方法使用两台机器通过串口将调试机的Oops信息打印到宿主机的屏幕上。但是现在大多数笔记本电脑都没有串口,这种方案也有很大的局限性;第三种方法是使用核心转储工具kdump,将发生Oops时内存和CPU寄存器的内容转储到文件中,然后使用gdb分析问题。在开发内核驱动的过程中可能会遇到各种各样的问题,调试的方法也多种多样。Oops是Linux内核给我们的提醒,一定要好好利用。