网站公司建站,温州谷歌优化排名公司,网站建设与管理是学什么,中国做投资的网站gdb 和 addr2line 调试内核模块内核模块插入内核链表的时候#xff0c;会调用 init 里面的程序#xff0c;我们上面给的那个例程的程序因为是经过多年风吹雨打的#xff0c;但是如果你是一个萌新的码农#xff0c;你能保证自己写的内核模块没有问题吗#xff1f;所以就需要… gdb 和 addr2line 调试内核模块内核模块插入内核链表的时候会调用 init 里面的程序我们上面给的那个例程的程序因为是经过多年风吹雨打的但是如果你是一个萌新的码农你能保证自己写的内核模块没有问题吗所以就需要调试方法如果你写了一个内核模块加载不成功这时候就会产生 oops 内核不会有影响就好像你拿了一个伪造的车票想上高铁结果被列车员发现了把你踢下车了列车不受影响继续运行。我们用个示例来说明下调试 oops 的问题调试的最终目的就是要找到出现问题的地方。oops.c 源代码相比HelloWorld的代码这个代码增加了一些东西一个是增加了模块描述 MODULE_DESCRIPTION 和模块作者 MODULE_AUTHOR 这些都不是必须的但是作为一个标准的内核开发者把自己的模块写得越规范是越好的不仅代码看起来比较美观而且别人也可以从这些代码里面看到一些有用的信息。#include linux/init.h#include linux/module.hMODULE_LICENSE(Dual BSD/GPL);MODULE_DESCRIPTION (Oops);MODULE_AUTHOR(weiqifa);static int my_oops_init(void){ int *a; a (int *)0x00003333; *a 3; printk(KERN_ALERT oops %d\n,a); return 0;}static void my_oops_exit(void){ printk(KERN_ALERT Goodbye, oops\n);}module_init(my_oops_init);module_exit(my_oops_exit)Makefile 文件这个文件跟上面的文件有些不同加入了一个 FLAG 这个FLAG 为了方便我们调试内核。ifneq ($(KERNELRELEASE),)EXTRA_CFLAGS -Wall -gobj-m : oops.oelsePWD : $(shell pwd)KVER : $(shell uname -r)KDIR : /lib/modules/$(KVER)/buildall: $(MAKE) -C $(KDIR) M$(PWD) modulesclean: rm -rf .*.cmd *.o *.mod.c *.ko .tmp_versionsendif我们执行 insmod oops.ko 的时候会出现 kernel panic再通过 dmesg 来查看 panic 内容其中看到的 kernel 日志如下rootubuntu:~/linuxBook/oopsmodules# dmesg |tail -20[ 815.844634] RSP: 0018:ffff88003b933de8 EFLAGS: 00010246[ 815.844635] RAX: 0000000000000000 RBX: 0000000000000000 RCX: 0000000000000001[ 815.844637] RDX: 0000000000040770 RSI: 0000000000003333 RDI: ffffffffa0359024[ 815.844638] RBP: ffff88003b933e68 R08: 00000000000030d4 R09: 0000000000000100[ 815.844639] R10: 8000000000000000 R11: 0000000000000000 R12: ffffffffa0358000[ 815.844640] R13: 0000000000000000 R14: 0000000001005010 R15: 0000000000000000[ 815.844641] FS: 00007fb80a1f6700(0000) GS:ffff88003d600000(0000) knlGS:0000000000000000[ 815.844643] CS: 0010 DS: 0000 ES: 0000 CR0: 000000008005003b[ 815.844643] CR2: 0000000000003333 CR3: 0000000036198000 CR4: 00000000000407f0[ 815.844687] Stack:[ 815.844689] ffff88003b933e68 ffffffff8100215a 0000000001005010 0000000000000000[ 815.844691] ffff88003b933e38 ffffffff8105ec93 0000000000000000 0000000000000000[ 815.844693] 0000000001005010 ffffffffa0020000 ffff88003b933e68 00000000181edb8b[ 815.844695] Call Trace:[ 815.844734] [ffffffff8100215a] ? do_one_initcall0xfa/0x1b0[ 815.844740] [ffffffff8105ec93] ? set_memory_nx0x43/0x50[ 815.844752] [ffffffff8175a462] do_init_module0x80/0x1d1[ 815.844759] [ffffffff810ec66d] load_module0x4ed/0x620[ 815.844761] [ffffffff810e9f10] ? show_initstate0x50/0x50[ 815.844763] [ffffffff810ec854] SyS_init_module0xb4/0x100[ 815.844798] [ffffffff8177b55d] system_call_fastpath0x1a/0x1f[ 815.844800] Code: c7 04 25 33 33 00 00 03 00 00 00 48 89 e5 e8 3c 16 40 e1 31 c0 5d[ 815.844809] RIP [ffffffffa0358014] my_oops_init0x14/0x30 [oops][ 815.844812] RSP ffff88003b933de8[ 815.844813] CR2: 0000000000003333[ 815.844818] ---[ end trace f8f9b64af5078acc ]---看日志也是一个比较考验程序员的事情从上面的日志看我们可以看到 oops 发生的关键日志如下其中还有一些函数的堆栈调用但是这个不是重点。[ 815.844809] RIP [ffffffffa0358014] my_oops_init0x14/0x30 [oops][ 815.844812] RSP ffff88003b933de8查看模块的加载地址上面出现 oops 的是从模块的基地址偏移出来的地址我们要找到基地址然后再用基地址和偏移地址运算就可以知道出现问题的偏移量了。rootubuntu:~/linuxBook/oopsmodules# cat /proc/modules |grep oopsoops 13418 1 - Loading 0xffffffffa0358000 (OX)rootubuntu:~/linuxBook/oopsmodules#使用 addr2line 找到 oops 位置知道了基地址和偏移地址我们就可以知道偏移量了 offset 0xffffffffa0358014 - 0xffffffffa0358000 0x14rootubuntu:~/linuxBook/oopsmodules# addr2line -e oops.o 0x14/home/linux/linuxBook/oopsmodules/oops.c:12rootubuntu:~/linuxBook/oopsmodules#这样知道代码导致 oops 的位置是第12 行。通过objdump 来查找oops 位置rootubuntu:~/linuxBook/oopsmodules# objdump -dS --adjust-vma0xffffffffa0358000 oops.kooops.ko: file format elf64-x86-64Disassembly of section .text:ffffffffa0358000 init_module:MODULE_LICENSE(Dual BSD/GPL);MODULE_DESCRIPTION (Oops);MODULE_AUTHOR(weiqifa);static int my_oops_init(void){ffffffffa0358000: e8 00 00 00 00 callq ffffffffa0358005 init_module0x5ffffffffa0358005: 55 push %rbp int *a; a (int *)0x00003333; *a 3; printk(KERN_ALERT oops %d\n,a);ffffffffa0358006: be 33 33 00 00 mov $0x3333,%esiffffffffa035800b: 48 c7 c7 00 00 00 00 mov $0x0,%rdiffffffffa0358012: 31 c0 xor %eax,%eaxstatic int my_oops_init(void){ int *a; a (int *)0x00003333; *a 3;ffffffffa0358014: c7 04 25 33 33 00 00 movl $0x3,0x3333ffffffffa035801b: 03 00 00 00MODULE_LICENSE(Dual BSD/GPL);MODULE_DESCRIPTION (Oops);MODULE_AUTHOR(weiqifa);static int my_oops_init(void){ffffffffa035801f: 48 89 e5 mov %rsp,%rbp int *a; a (int *)0x00003333; *a 3; printk(KERN_ALERT oops %d\n,a);ffffffffa0358022: e8 00 00 00 00 callq ffffffffa0358027 init_module0x27 return 0;}ffffffffa0358027: 31 c0 xor %eax,%eaxffffffffa0358029: 5d pop %rbpffffffffa035802a: c3 retq ffffffffa035802b: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)ffffffffa0358030 cleanup_module:static void my_oops_exit(void){ffffffffa0358030: e8 00 00 00 00 callq ffffffffa0358035 cleanup_module0x5ffffffffa0358035: 55 push %rbp printk(KERN_ALERT Goodbye, oops\n);ffffffffa0358036: 48 c7 c7 00 00 00 00 mov $0x0,%rdiffffffffa035803d: 31 c0 xor %eax,%eax *a 3; printk(KERN_ALERT oops %d\n,a); return 0;}static void my_oops_exit(void){ffffffffa035803f: 48 89 e5 mov %rsp,%rbp printk(KERN_ALERT Goodbye, oops\n);ffffffffa0358042: e8 00 00 00 00 callq ffffffffa0358047 cleanup_module0x17}ffffffffa0358047: 5d pop %rbpffffffffa0358048: c3 retq ffffffffa0358049: 00 00 add %al,(%rax) ...rootubuntu:~/linuxBook/oopsmodules#这样看就更加清晰了我们知道出现 oops 的位置是 ffffffffa0358014 直接在上面找就可以看到了。 *a 3;ffffffffa0358014: c7 04 25 33 33 00 00 movl $0x3,0x3333使用函数dump_stack()调试内核不知道大家有没有跟我一样的困惑Linux 代码非常多也非常大有时候为了找到这个函数是从哪里调用的需要花费非常多的时间去查找代码而且很多宏函数还会蒙蔽你的双眼让你沦陷在Linux 内核里面这时候就需要这么一个函数让你拨开层层乌云看清它的真面目这时候你就会有一个感觉我从哪里来我要到哪里去都是一清二楚了。废话不多说dump_stack()这个函数可以打印当前函数的上下文调用让你更加直观的知道你的函数调用关系这样你就更清楚的知道你的爸爸的妈妈的妹妹的姑姑的姐姐的儿子的弟弟是你的什么关系了。代码#include linux/init.h#include linux/module.hMODULE_LICENSE(Dual BSD/GPL);static int hello_init(void){ dump_stack(); printk(KERN_ALERT Hello, world\n); return 0;}static void hello_exit(void){ printk(KERN_ALERT Goodbye, cruel world\n);}module_init(hello_init);module_exit(hello_exit);执行sudo insmod hello.ko后的kernel日志如下[176360.807755] [ffffffff81765bf5] dump_stack0x64/0x82[176360.807776] [ffffffffa0275000] ? 0xffffffffa0274fff[176360.807779] [ffffffffa027500e] hello_init0xe/0x20 [hello][176360.807931] [ffffffff8100215a] do_one_initcall0xfa/0x1b0[176360.808709] [ffffffff8105ec93] ? set_memory_nx0x43/0x50[176360.808717] [ffffffff8175a462] do_init_module0x80/0x1d1[176360.809093] [ffffffff810ec66d] load_module0x4ed/0x620[176360.809097] [ffffffff810e9f10] ? show_initstate0x50/0x50[176360.809100] [ffffffff810ec854] SyS_init_module0xb4/0x100[176360.809187] [ffffffff8177b55d] system_call_fastpath0x1a/0x1f[176360.809308] Hello, world可以看到里面的地址还有堆栈的调用使用dump_stack函数不用外加什么头文件使用起来非常方便也不用指定说只有在oops的时候才调用正常的时候我们想看函数调用关系的时候也可以调用就像我上面那样。实现源码位置/lib/dump_stack.c几个比较关键的函数调用关系主要想研究的同学可以去看看dump_stack_print_info 和show_stack里面的实现。dump_stack总结我们知道CPU工作的时候有不同的工作模式不同的工作模式代表有不同的权限就比如我是老板我才可能有保险柜的钥匙。这里有一个用户模式超级用户模式和中断模式我们知道每个进程分配的内存空间是不一样的他们的堆栈有一个task_struct来维护每个进程不能互相访问相互的内存进程切换的时候SP指针去执行要执行进程的堆栈地址所以我们可以知道一个事情SP这个东东啊是可以知道所有进程的东西的。但是呢操作系统在运行的时候CPU肯定有需要从用户模式跳转到中断模式运行进入中断的时候SP指针这个东东就体现出当前的堆栈了所以dump_stack就是从这些不断的切换中把堆栈地址给保存打印出来得到一个上下文的调用关系。使用vmlinux调试内核我们知道使用objdump反编译调试的是动态加载的内核模块但是我们需要调试那些静态编译进入内核的那些代码不能使用这个方法这时候就需要使用vmlinuxvmlinux是每次编译内核的时候生成的内核符号表里面包含了所有编译到内核的函数名还有偏移地址。使用方法高通平台arm-eabi-gdb out/target/product/msm8625/obj/KERNEL_OBJ/vmlinux在内核的.config里面要打开 DEBUG_INFO和DEBUG_VM定位故障方法(gdb) l * qrd7627a_add_io_devices0x1000xc07cd05c is in qrd7627a_add_io_devices (/home/yejialong/GH700C/kernel/arch/arm/mach-msm/msm8x25/goso-msm7627a-io.c:1851).1846 } else if (machine_is_msm8625q_skud() || machine_is_msm8625q_evbd()) {1847 #ifndef CONFIG_CALA021848 platform_device_register(pmic_mpp_leds_pdev_skud);1849 #endif1850 /* enable the skud flash and torch by gpio leds driver */1851 platform_device_register(gpio_flash_skud);1852 } else if (machine_is_msm8625q_skue()) {1853 /* enable the skue flashlight by gpio leds driver */1854 platform_device_register(gpio_flash_skue);1855 }使用方法MTK平台weiqifaweiqifa-Inspiron-3847:~/weiqifa/tm100$ ./prebuilts/gcc/linux-x86/arm/arm-eabi-4.7/bin/arm-eabi-gdb ./out/target/product/tm100/obj/KERNEL_OBJ/vmlinuxGNU gdb (GDB) 7.3.1-gg2Copyright (C) 2011 Free Software Foundation, Inc.License GPLv3: GNU GPL version 3 or later http://gnu.org/licenses/gpl.htmlThis is free software: you are free to change and redistribute it.There is NO WARRANTY, to the extent permitted by law. Type show copyingand show warranty for details.This GDB was configured as --hostx86_64-linux-gnu --targetarm-linux-android.For bug reporting instructions, please see:http://source.android.com/source/report-bugs.html...Reading symbols from /home/weiqifa/weiqifa/tm100/out/target/product/tm100/obj/KERNEL_OBJ/vmlinux...done.(gdb)使用方法rockchip平台./prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.9/bin/aarch64-linux-android-addr2line -f -e kernel/vmlinuxweiqifadev:~/rk3399_7in1$ ./prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.9/bin/aarch64-linux-android-addr2line -f -e kernel/vmlinux ffffff8008459f3crk_iommu_domain_free/data/weiqifa/rk3399_7in1/kernel/drivers/iommu/rockchip-iommu.c:1005 (discriminator 2)weiqifadev:~/rk3399_7in1$一些调试相关的命令查看中断linuxubuntu:/usr/src/linux-headers-3.13.0-117/kernel$ cat /proc/interrupts CPU0 0: 26 IO-APIC-edge timer 1: 12200 IO-APIC-edge i8042 8: 1 IO-APIC-edge rtc0 9: 0 IO-APIC-fasteoi acpi 12: 110704 IO-APIC-edge i8042 14: 0 IO-APIC-edge ata_piix 15: 0 IO-APIC-edge ata_piix 16: 21165 IO-APIC-fasteoi vmwgfx, snd_ens1371 17: 76243 IO-APIC-fasteoi ehci_hcd:usb1, ioc0 18: 657 IO-APIC-fasteoi uhci_hcd:usb2 19: 197583 IO-APIC-fasteoi eth0 40: 0 PCI-MSI-edge PCIe PME, pciehp这个可以在嵌入式调试的时候查看中断是否被触发非常有作用。查看工作队列cat /proc/sched_debug查看内核定时器cat /proc/timer_listproc应该重点关注proc下面的文件系统应该重点关注内核的调试信息很多都在这里面特别是初学者把下面的每个文件夹都看看作用还是非常明显的。下周开始我们可能进去封闭开发了时间会更加紧张可能会分享一些心得连载的话我还是会写只会迟到不会缺席这样也是对自己的一种鞭策。共勉加油~