刚才测试安装好了skyeye1.2.4,想调试一下手里面2440的2.6.12的一个内核,郁闷,initrd.img 和 skyeye.conf 这两个文件貌似棘手的问题.先放了放,看了一点轻松的东西,关于内核的调试技术,这个比较容易,平日里自己做的时候,也常用,关键是运行的调试平台.先说到这里.
GPIO控制(俗称点灯),主要是bootloader和内核进入C环境之前的调试手段,主要做法是:
1、在ARM Linux里,硬件工程师一般都会在硬件板上设计几个led灯,用于指示系统的运行,硬件的读写等;
2、根据DataSheet,查找GPIO的地址,编写点灯程序,确认点灯程序的正确性;
3、在需要验证代码执行的地方,植入点灯程序,查看GPIO电平的变化,或使用led的闪烁,来判断程序是否运行;
4、点灯程序一般运行在MMU没有开启的时段,如果MMU开启之后还需要使用点灯来调试kernel,需要将点灯程序GPIO的地址进行virt to phys的转换,否则不仅不能正确使用,还会引起内存访问错误segment fault;
printk() 是调试内核代码时最常用的一种技术。在内核代码中的特定位置加入printk() 调试调用,可以直接把所关心的信息打打印到屏幕上,从而可以观察程序的执行路径和所关心的变量、指针等信息。
就本人的经验作以下说明:
1、 printk只能在内核的终端驱动加载之后才能正确输出,终端一般为串口;
2、 在驱动加载之前,printk可以使用,但是并不会有输出,内核会把需要的打印信息保存在一个buffer里,等到driver加载完成,一并输出。
3、 有些微内核如codezero,在driver加载初始化完成之前,使用printk,会使系统阻塞挂起,因此有必要了解系统的特性和启动的过程。
4、 Printk有7个打印级别,缺省是4(warning),0为最高级别(error),高于缺省打印级别的会打印在console上,低于缺省级别的会输出到dmesg等proc文件系统;
5、 Printk在内核里打印的时候,本人一般会重新封装一下,便于控制和打印信息的关闭:
#define CODEZERO_DBG_ON
#ifdef CODEZERO_DBG_ON
#define CODEZERO_DBG(format,s) do{ /
printk("CodeZero DBG<%s>:",__KERNELNAME__); /
printk(format,s); /
printk(" in <%s,%s,%d>./n",__FUNCTION__,__FILE__,__LINE__); /
}while(0)
#else
#define CODEZERO_DBG(format,s) NULL
#endif
Linux 内核调试器(Linux kernel debugger,kdb)是 Linux 内核的补丁,它提供了一种在系统能运行时对内核内存和数据结构进行检查的办法。
个人观点:KGDB作为一个补丁,在使用的时候可以移植到自己的内核中,但是也会改变内核的一些运行结构,甚至运行时序,因此GDB虽然是很常用,很成熟调试技术,但是试想一下为啥Linux内核的那些大神们为啥并没有将其作为内核的一个组件进行发布,维护呢?因此个人认为KGDB并不是内核调试最好的选择。
kgdb提供了一种使用 gdb调试 Linux 内核的机制。使用KGDB可以象调试普通的应用程序那样,在内核中进行设置断点、检查变量值、单步跟踪程序运行等操作。使用KGDB调试时需要两台机器,一台作为开发机(Development Machine),另一台作为目标机(Target Machine),两台机器之间通过串口或者以太网口相连。串口连接线是一根RS-232接口的电缆,在其内部两端的第2脚(TXD)与第3脚(RXD)交叉相连,第7脚(接地脚)直接相连。调试过程中,被调试的内核运行在目标机上,gdb调试器运行在开发机上。
安装kgdb调试环境需要为Linux内核应用kgdb补丁,补丁实现的gdb远程调试所需要的功能包括命令处理、陷阱处理及串口通讯3个主要的部分。
kgdb补丁的主要作用是在Linux内核中添加了一个调试Stub。调试Stub是Linux内核中的一小段代码,提供了运行gdb的开发机和所调试内核之间的一个媒介。
gdb和调试stub之间通过gdb串行协议进行通讯。gdb串行协议是一种基于消息的ASCII码协议,包含了各种调试命令。
当设置断点时,kgdb负责在设置断点的指令前增加一条trap指令,当执行到断点时控制权就转移到调试stub中去。此时,调试stub的任务就是使用远程串行通信协议将当前环境传送给gdb,然后从gdb处接受命令。
gdb命令告诉stub下一步该做什么,当stub收到继续执行的命令时,将恢复程序的运行环境,把对CPU的控制权重新交还给内核。
详细说明参见:http://www.360doc.com/content/07/0928/20/2459_777216.shtml
strace 命令是一种强大的工具,它能够显示所有由用户空间程序发出的系统调用。strace 显示这些调用的参数并返回符号形式的值。strace 从内核接收信息,而且不需要以任何特殊的方式来构建内核。将跟踪信息发送到应用程序及内核开发者都很有用。
在下清单中,分区的一种格式有错误,清单显示了 strace 的开头部分,内容是关于调出创建文件系统操作( mkfs )的。strace 确定哪个调用导致问题出现。
mkfs 上 strace 的开头部分
execve("/sbin/mkfs.jfs", ["mkfs.jfs", "-f", "/dev/test1"], & ... open("/dev/test1", O_RDWR|O_LARGEFILE) = 4 stat64("/dev/test1", {st_mode=&, st_rdev=makedev(63, 255), ...}) = 0 ioctl(4, 0x40041271, 0xbfffe128) = -1 EINVAL (Invalid argument) write(2, "mkfs.jfs: warning - cannot setb" ..., 98mkfs.jfs: warning - cannot set blocksize on block device /dev/test1: Invalid argument ) = 98 stat64("/dev/test1", {st_mode=&, st_rdev=makedev(63, 255), ...}) = 0 open("/dev/test1", O_RDONLY|O_LARGEFILE) = 5 ioctl(5, 0x80041272, 0xbfffe124) = -1 EINVAL (Invalid argument) write(2, "mkfs.jfs: can/'t determine device"..., ..._exit(1) = ? |
清单显示 ioctl 调用导致用来格式化分区的 mkfs 程序失败。 ioctl BLKGETSIZE64 失败。( BLKGET-SIZE64 在调用 ioctl 的源代码中定义。) BLKGETSIZE64 ioctl 将被添加到 Linux 中所有的设备,而在这里,逻辑卷管理器还不支持它。因此,如果 BLKGETSIZE64 ioctl 调用失败,mkfs 代码将改为调用较早的 ioctl 调用;这使得 mkfs 适用于逻辑卷管理器。
详细说明参见:http://blog.sina.com.cn/s/blog_48a44f390100fu7k.html
Oops(也称 panic,慌张)消息包含系统错误的细节,如 CPU 寄存器的内容。在 Linux 中,调试系统崩溃的传统方法是分析在发生崩溃时发送到系统控制台的 Oops 消息。一旦您掌握了细节,就可以将消息发送到 ksymoops 实用程序,它将试图将代码转换为指令并将堆栈值映射到内核符号。在很多情况下,这些信息就足够您确定错误的可能原因是什么了。请注意,Oops 消息并不包括核心文件。
ksymoops 需要几项内容:Oops 消息输出、来自正在运行的内核的 System.map 文件,还有 /proc/ksyms、vmlinux 和 /proc/modules。关于如何使用 ksymoops,内核源代码 /usr/src/linux/Documentation/oops-tracing.txt 中或 ksymoops 手册页上有完整的说明可以参考。Ksymoops 反汇编代码部分,指出发生错误的指令,并显示一个跟踪部分表明代码如何被调用。
首先,将 Oops 消息保存在一个文件中以便通过 ksymoops 实用程序运行它。清单显示了由安装 JFS 文件系统的 mount 命令创建的 Oops 消息。
清单 ksymoops 处理后的 Oops 消息
ksymoops 2.4.0 on i686 2.4.17. Options used ... 15:59:37 sfb1 kernel: Unable to handle kernel NULL pointer dereference at virtual address 0000000 ... 15:59:37 sfb1 kernel: c01588fc ... 15:59:37 sfb1 kernel: *pde = 0000000 ... 15:59:37 sfb1 kernel: Oops: 0000 ... 15:59:37 sfb1 kernel: CPU: 0 ... 15:59:37 sfb1 kernel: EIP: 0010:[jfs_mount+60/704] ... 15:59:37 sfb1 kernel: Call Trace: [jfs_read_super+287/688] [get_sb_bdev+563/736] [do_kern_mount+189/336] [do_add_mount+35/208] [do_page_fault+0/1264] ... 15:59:37 sfb1 kernel: Call Trace: [<c0155d4f>]... ... 15:59:37 sfb1 kernel: [<c0106e04 ... ... 15:59:37 sfb1 kernel: Code: 8b 2d 00 00 00 00 55 ... >>EIP; c01588fc <jfs_mount+3c/2c0> <===== ... Trace; c0106cf3 <system_call+33/40> Code; c01588fc <jfs_mount+3c/2c0> 00000000 <_EIP>: Code; c01588fc <jfs_mount+3c/2c0> <===== 0: 8b 2d 00 00 00 00 mov 0x0,%ebp <===== Code; c0158902 <jfs_mount+42/2c0> 6: 55 push %ebp |
接下来,确定 jfs_mount 中的哪一行代码引起了这个问题。Oops 消息告诉我们问题是由位于偏移地址 3c 的指令引起的。做这件事的办法之一是对 jfs_mount.o 文件使用 objdump 实用程序,然后查看偏移地址 3c。Objdump 用来反汇编模块函数,看看您的 C 源代码会产生什么汇编指令。清单显示了使用 objdump 后您将看到的内容,接着,我们查看 jfs_mount 的 C 代码,可以看到空值是第 109 行引起的。偏移地址 3c 之所以很重要,是因为 Oops 消息将该处标识为引起问题的位置。
清单 jfs_mount 的汇编程序清单
109 printk("%d/n",*ptr); objdump jfs_mount.o jfs_mount.o: file format elf32-i386 Disassembly of section .text: 00000000 <jfs_mount>: 0:55 push %ebp ... 2c: e8 cf 03 00 00 call 400 <chkSuper> 31: 89 c3 mov %eax,%ebx 33: 58 pop %eax 34: 85 db test %ebx,%ebx 36: 0f 85 55 02 00 00 jne 291 <jfs_mount+0x291> 3c: 8b 2d 00 00 00 00 mov 0x0,%ebp << problem line above 42: 55 push %ebp |
详细说明参见:http://blog.sina.com.cn/s/blog_48a44f390100fu7l.html