一. 打印: prink, 自制proc文件
UBOOT传入console=ttySAC0 console=tty1
1. 内核处理UBOOT传入的参数
console_setup
add_preferred_console // 我想用名为"ttySAC0"的控制台,先记录下来
2. 硬件驱动的入口函数里:
drivers/serial/s3c2410.c
register_console(&s3c24xx_serial_console);
3. printk
vprintk
/* Emit the output into the temporary buffer */
// 先把输出信息放入临时BUFFER**************处理前的buffer
vscnprintf
// Copy the output into log_buf.
// 把临时BUFFER里的数据稍作处理,再写入log_buf***********处理后的buffer
// 比如printk("abc")会得到"<4>abc", 再写入log_buf
// 可以用dmesg命令把log_buf里的数据打印出来重现内核的输出信息
// 调用硬件的write函数输出
release_console_sem();
call_console_drivers(_con_start, _log_end);
// 从log_buf得到数据,算出打印级别
_call_console_drivers(start_print, cur_index, msg_level);
// 如果可以级别够格打印
if ((msg_log_level < console_loglevel
__call_console_drivers
con->write(con, &LOG_BUF(start), end - start);
dmesg如何将printk缓冲区内的数据拿出来呢:肯定是打开了某个文件
ll /proc/kmsg proc是一个虚拟的文件系统
在系统启动的时候会执行/etc/init.d/rcS中的一句:mount -a
mount -a 会根据文件/etc/fstab中的内容来挂在文件系统
可以用cat /proc/mounts看挂在了那些文件系统
dmesg 命令取出/proc/kmsg中的内容 等同于:cat /proc/kmsg中也会显示跟dmesg一样的内容
写一个自己的命令和缓冲区来模仿dmesg的执行过程 //见 附录一
linux/fs/proc目录下放着proc的各种操作函数
proc_misc.c的入口函数
ifdef CONFIG_PRINTK
entry=create_proc_entry("kmsg",S_IRUSER ,&proc_root); //在proc'目录下创建一个文件
entry->proc_fops= 此处的read write 都是操作一个环形缓冲区二. 根据内核打印的段错误信息分析
a. 作为模块:
1. 根据pc值确定该指令属于内核还是外加的模块
pc=0xbf000018 它属于什么的地址?是内核还是通过insmod加载的驱动程序?
先判断是否属于内核的地址: 看System.map确定内核的函数的地址范围:c0004000~c03265a4
如果不属于System.map里的范围,则它属于insmod加载的驱动程序
2. 假设它是加载的驱动程序引入的错误,怎么确定是哪一个驱动程序?
先看看加载的驱动程序的函数的地址范围
cat /proc/kallsyms (内核函数、加载的函数的地址)
从这些信息里找到一个相近的地址, 这个地址<=0xbf000018
比如找到了:
bf000000 t first_drv_open [first_drv]
3. 找到了first_drv.ko
在PC上反汇编它: arm-linux-objdump -D first_drv.ko > frist_drv.dis
在dis文件里找到first_drv_open
first_drv.dis文件里 insmod后
00000000 <first_drv_open>: bf000000 t first_drv_open[first_drv]
00000018 pc = bf000018
./firstdrvtest on
Unable to handle kernel paging request at virtual address 56000050
内核使用56000050来访问时发生了错误
pgd = c3eb0000
[56000050] *pgd=00000000
Internal error: Oops: 5 [#1]
Modules linked in: first_drv
CPU: 0 Not tainted (2.6.22.6 #1)
PC is at first_drv_open+0x18(该指令的偏移)/0x3c(该函数的总大小) [first_drv]
PC就是发生错误的指令的地址
大多时候,PC值只会给出一个地址,不到指示说是在哪个函数里
LR is at chrdev_open+0x14c/0x164
LR寄存器的值
pc = 0xbf000018
pc : [<bf000018>] lr : [<c008d888>] psr: a0000013
sp : c3c7be88 ip : c3c7be98 fp : c3c7be94
r10: 00000000 r9 : c3c7a000 r8 : c049abc0
r7 : 00000000 r6 : 00000000 r5 : c3e740c0 r4 : c06d41e0
r3 : bf000000 r2 : 56000050 r1 : bf000964 r0 : 00000000
执行这条导致错误的指令时各个寄存器的值
Flags: NzCv IRQs on FIQs on Mode SVC_32 Segment user
Control: c000717f Table: 33eb0000 DAC: 00000015
Process firstdrvtest (pid: 777, stack limit = 0xc3c7a258)
发生错误时当前进程的名称是firstdrvtest
栈
Stack: (0xc3c7be88 to 0xc3c7c000)
be80: c3c7bebc c3c7be98 c008d888 bf000010 00000000 c049abc0
bea0: c3e740c0 c008d73c c0474e20 c3e766a8 c3c7bee4 c3c7bec0 c0089e48 c008d74c
bec0: c049abc0 c3c7bf04 00000003 ffffff9c c002c044 c3d10000 c3c7befc c3c7bee8
bee0: c0089f64 c0089d58 00000000 00000002 c3c7bf68 c3c7bf00 c0089fb8 c0089f40
bf00: c3c7bf04 c3e766a8 c0474e20 00000000 00000000 c3eb1000 00000101 00000001
bf20: 00000000 c3c7a000 c04a7468 c04a7460 ffffffe8 c3d10000 c3c7bf68 c3c7bf48
bf40: c008a16c c009fc70 00000003 00000000 c049abc0 00000002 bec1fee0 c3c7bf94
bf60: c3c7bf6c c008a2f4 c0089f88 00008520 bec1fed4 0000860c 00008670 00000005
bf80: c002c044 4013365c c3c7bfa4 c3c7bf98 c008a3a8 c008a2b0 00000000 c3c7bfa8
bfa0: c002bea0 c008a394 bec1fed4 0000860c 00008720 00000002 bec1fee0 00000001
bfc0: bec1fed4 0000860c 00008670 00000002 00008520 00000000 4013365c bec1fea8
bfe0: 00000000 bec1fe84 0000266c 400c98e0 60000010 00008720 00000000 00000000
Backtrace: (回溯)
[<bf000000>] (first_drv_open+0x0/0x3c [first_drv]) from [<c008d888>] (chrdev_open+0x14c/0x164)
[<c008d73c>] (chrdev_open+0x0/0x164) from [<c0089e48>] (__dentry_open+0x100/0x1e8)
r8:c3e766a8 r7:c0474e20 r6:c008d73c r5:c3e740c0 r4:c049abc0
[<c0089d48>] (__dentry_open+0x0/0x1e8) from [<c0089f64>] (nameidata_to_filp+0x34/0x48)
[<c0089f30>] (nameidata_to_filp+0x0/0x48) from [<c0089fb8>] (do_filp_open+0x40/0x48)
r4:00000002
[<c0089f78>] (do_filp_open+0x0/0x48) from [<c008a2f4>] (do_sys_open+0x54/0xe4)
r5:bec1fee0 r4:00000002
[<c008a2a0>] (do_sys_open+0x0/0xe4) from [<c008a3a8>] (sys_open+0x24/0x28)
[<c008a384>] (sys_open+0x0/0x28) from [<c002bea0>] (ret_fast_syscall+0x0/0x2c)
Code: e24cb004 e59f1024 e3a00000 e5912000 (e5923000)
Segmentation fault
#
b. 编入内核
Modules linked in:
CPU: 0 Not tainted (2.6.22.6 #2)
PC is at first_drv_open+0x18/0x3c
LR is at chrdev_open+0x14c/0x164
pc : [<c014e6c0>] lr : [<c008638c>] psr: a0000013
sp : c3a03e88 ip : c3a03e98 fp : c3a03e94
r10: 00000000 r9 : c3a02000 r8 : c03f3c60
r7 : 00000000 r6 : 00000000 r5 : c38a0c50 r4 : c3c1e780
r3 : c014e6a8 r2 : 56000050 r1 : c031a47c r0 : 00000000
Flags: NzCv IRQs on FIQs on Mode SVC_32 Segment user
Control: c000717f Table: 339f0000 DAC: 00000015
Process firstdrvtest (pid: 750, stack limit = 0xc3a02258)
1. 根据pc值确定该指令属于内核还是外加的模块
pc=c014e6c0 属于内核(看System.map)
2. 反汇编内核: arm-linux-objdump -D vmlinux > vmlinux.dis
在dis文件里搜c014e6c0
c014e6a8 <first_drv_open>:
c014e6a8: e1a0c00d mov ip, sp
c014e6ac: e92dd800 stmdb sp!, {fp, ip, lr, pc}
c014e6b0: e24cb004 sub fp, ip, #4 ; 0x4
c014e6b4: e59f1024 ldr r1, [pc, #36] ; c014e6e0 <.text+0x1276e0>
c014e6b8: e3a00000 mov r0, #0 ; 0x0
c014e6bc: e5912000 ldr r2, [r1]
c014e6c0: e5923000 ldr r3, [r2] // 在此出错 r2=56000050
3. 根据栈信息分析函数调用过程
# ./firstdrvtest on
Unable to handle kernel paging request at virtual address 56000050
pgd = c3e78000
[56000050] *pgd=00000000
Internal error: Oops: 5 [#1]
Modules linked in: first_drv
CPU: 0 Not tainted (2.6.22.6 #48)
PC is at first_drv_open+0x18/0x3c [first_drv]
LR is at chrdev_open+0x14c/0x164
pc : [<bf000018>] lr : [<c008c888>] psr: a0000013
3.1 根据PC确定出错位置
bf000018 属于 insmod的模块
bf000000 t first_drv_open [first_drv]
3.2 确定它属于哪个函数
反汇编first_drv.ko
sp : c3e69e88 ip : c3e69e98 fp : c3e69e94
r10: 00000000 r9 : c3e68000 r8 : c0490620
r7 : 00000000 r6 : 00000000 r5 : c3e320a0 r4 : c06a8300
r3 : bf000000 r2 : 56000050 r1 : bf000964 r0 : 00000000
Flags: NzCv IRQs on FIQs on Mode SVC_32 Segment user
Control: c000717f Table: 33e78000 DAC: 00000015
Process firstdrvtest (pid: 752, stack limit = 0xc3e68258)
Stack: (0xc3e69e88 to 0xc3e6a000)
9e80: c3e69ebc c3e69e98 c008c888 bf000010 00000000 c0490620
first_drv_open'sp lr chrdev_open'sp
9ea0: c3e320a0 c008c73c c0465e20 c3e36cb4 c3e69ee4 c3e69ec0 c0088e48 c008c74c
lr
9ec0: c0490620 c3e69f04 00000003 ffffff9c c002b044 c06e0000 c3e69efc c3e69ee8
__dentry_open'sp
9ee0: c0088f64 c0088d58 00000000 00000002 c3e69f68 c3e69f00 c0088fb8 c0088f40
lr nameidata_to_filp'sp lr
9f00: c3e69f04 c3e36cb4 c0465e20 00000000 00000000 c3e79000 00000101 00000001
do_filp_open'sp
9f20: 00000000 c3e68000 c04c1468 c04c1460 ffffffe8 c06e0000 c3e69f68 c3e69f48
9f40: c008916c c009ec70 00000003 00000000 c0490620 00000002 be94eee0 c3e69f94
9f60: c3e69f6c c00892f4 c0088f88 00008520 be94eed4 0000860c 00008670 00000005
lr do_sys_open'sp
9f80: c002b044 4013365c c3e69fa4 c3e69f98 c00893a8 c00892b0 00000000 c3e69fa8
lr sys_open'sp
9fa0: c002aea0 c0089394 be94eed4 0000860c 00008720 00000002 be94eee0 00000001
lr ret_fast_syscall'sp
9fc0: be94eed4 0000860c 00008670 00000002 00008520 00000000 4013365c be94eea8
9fe0: 00000000 be94ee84 0000266c 400c98e0 60000010 00008720 00000000 00000000
三. 自制工具
寄存器编辑器
四. 修改内核来定位系统僵死问题
./firstdrvtest on
asm_do_IRQ => s3c2410_timer_interrupt : pid = 752, task name = firstdrvtest
pc = bf000084
asm_do_IRQ => s3c2410_timer_interrupt : pid = 752, task name = firstdrvtest
pc = bf000084 // 对于中断, pc-4才是发生中断瞬间的地址
看/proc/kallsyms
first_drv.dis
00000000 <first_drv_open>: bf000000 t first_drv_open[first_drv]
0000003c <first_drv_write>:
3c: e1a0c00d mov ip, sp
40: e92dd800 stmdb sp!, {fp, ip, lr, pc}
44: e24cb004 sub fp, ip, #4; 0x4
48: e24dd004 sub sp, sp, #4; 0x4
4c: e3cd3d7f bic r3, sp, #8128; 0x1fc0
50: e3c3303f bic r3, r3, #63; 0x3f
54: e5933008 ldr r3, [r3, #8]
58: e0910002 adds r0, r1, r2
5c: 30d00003 sbcccs r0, r0, r3
60: 33a03000 movcc r3, #0; 0x0
64: e3530000 cmp r3, #0; 0x0
68: e24b0010 sub r0, fp, #16; 0x10
6c: 1a00001c bne e4 <init_module+0x5c>
70: ebfffffe bl 70 <first_drv_write+0x34>
74: ea00001f b f8 <init_module+0x70>
78: e3520000 cmp r2, #0; 0x0
7c: 11a01002 movne r1, r2
80: 1bfffffe blne 80 <first_drv_write+0x44> // 卡死的地方
84: ea00001f b 108 <init_module+0x80>
附录一
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
#include <linux/proc_fs.h>
#define MYLOG_BUF_LEN 1024
struct proc_dir_entry *myentry;
static char mylog_buf[MYLOG_BUF_LEN];
static char tmp_buf[MYLOG_BUF_LEN];
static int mylog_r = 0; //保存重新cat /proc/mymsg 时的位置,重新cat时把它赋值给mylog_r_for_read
static int mylog_r_for_read = 0; // cat /proc/mymsg 时读的位置
static int mylog_w = 0;
static DECLARE_WAIT_QUEUE_HEAD(mymsg_waitq);
static int is_mylog_empty(void)
{
return (mylog_r == mylog_w);
}
static int is_mylog_empty_for_read(void)
{
return (mylog_r_for_read == mylog_w);
}
static int is_mylog_full(void)
{
return ((mylog_w + 1)% MYLOG_BUF_LEN == mylog_r);
}
static void mylog_putc(char c)
{
if (is_mylog_full())
{
/* 丢弃一个数据 */
mylog_r = (mylog_r + 1) % MYLOG_BUF_LEN;
if ((mylog_r_for_read + 1) % MYLOG_BUF_LEN == mylog_r)
{
mylog_r_for_read = mylog_r;
}
}
mylog_buf[mylog_w] = c;
mylog_w = (mylog_w + 1) % MYLOG_BUF_LEN;
/* 唤醒等待数据的进程 */
wake_up_interruptible(&mymsg_waitq); /* 唤醒休眠的进程 */
}
static int mylog_getc(char *p)
{
if (is_mylog_empty())
{
return 0;
}
*p = mylog_buf[mylog_r];
mylog_r = (mylog_r + 1) % MYLOG_BUF_LEN;
return 1;
}
static int mylog_getc_for_read(char *p)
{
if (is_mylog_empty_for_read())
{
return 0;
}
*p = mylog_buf[mylog_r_for_read];
mylog_r_for_read = (mylog_r_for_read + 1) % MYLOG_BUF_LEN;
return 1;
}
int myprintk(const char *fmt, ...)
{
va_list args;
int i;
int j;
va_start(args, fmt);
i = vsnprintf(tmp_buf, INT_MAX, fmt, args);
va_end(args);
for (j = 0; j < i; j++)
mylog_putc(tmp_buf[j]);
return i;
}
static ssize_t mymsg_read(struct file *file, char __user *buf,
size_t count, loff_t *ppos)
{
int error = 0;
int i = 0;
char c;
/* 把mylog_buf的数据copy_to_user, return */
if ((file->f_flags & O_NONBLOCK) && is_mylog_empty_for_read())
return -EAGAIN;
//printk("%s %d\n", __FUNCTION__, __LINE__);
//printk("count = %d\n", count);
//printk("mylog_r = %d\n", mylog_r);
//printk("mylog_w = %d\n", mylog_w);
error = wait_event_interruptible(mymsg_waitq, !is_mylog_empty_for_read());
//printk("%s %d\n", __FUNCTION__, __LINE__);
//printk("count = %d\n", count);
//printk("mylog_r = %d\n", mylog_r);
//printk("mylog_w = %d\n", mylog_w);
/* copy_to_user */
while (!error && (mylog_getc_for_read(&c)) && i < count) {
error = __put_user(c, buf);
buf++;
i++;
}
if (!error)
error = i;
return error;
}
static int mymsg_open(struct inode *inode, struct file *file)
{
mylog_r_for_read = mylog_r;
return 0;
}
const struct file_operations proc_mymsg_operations = {
.open = mymsg_open,
.read = mymsg_read,
};
static int mymsg_init(void)
{
myentry = create_proc_entry("mymsg", S_IRUSR, &proc_root);
if (myentry)
myentry->proc_fops = &proc_mymsg_operations;
return 0;
}
static void mymsg_exit(void)
{
remove_proc_entry("mymsg", &proc_root);
}
module_init(mymsg_init);
module_exit(mymsg_exit);
EXPORT_SYMBOL(myprintk);
MODULE_LICENSE("GPL");
//debugfs-----
/drivers/video/omap2/dss/core.c中:
#if defined(CONFIG_DEBUG_FS) && defined(CONFIG_OMAP2_DSS_DEBUG_SUPPORT)
……
int dss_debugfs_create_file(const char *name, void (*write)(struct seq_file *))
{
struct dentry *d;
d = debugfs_create_file(name, S_IRUGO, dss_debugfs_dir,write, &dss_debug_fops);
第一个参数是目录的名称,第二个参数用来指定这个目录的上级目录,如果是NULL,则表示是放在debugfs的根目录里
mount -t debugfs none /sys/kernel/debug
if (IS_ERR(d))
return PTR_ERR(d);
return 0;
}
……
#else
……
int dss_debugfs_create_file(const char *name, void (*write)(struct seq_file *))
{
return 0;
}
……
#endif