内核常用调试函数
一、print_hex_dump()
该函数用来在内核打印二进制数据,还是非常好用的,不需要在驱动自己去实现。
/**
* print_hex_dump - print a text hex dump to syslog for a binary blob of data
* @level: kernel log level (e.g. KERN_DEBUG)
* @prefix_str: string to prefix each line with;
* caller supplies trailing spaces for alignment if desired
* @prefix_type: controls whether prefix of an offset, address, or none
* is printed (%DUMP_PREFIX_OFFSET, %DUMP_PREFIX_ADDRESS, %DUMP_PREFIX_NONE)
* @rowsize: number of bytes to print per line; must be 16 or 32
* @groupsize: number of bytes to print at a time (1, 2, 4, 8; default = 1)
* @buf: data blob to dump
* @len: number of bytes in the @buf
* @ascii: include ASCII after the hex output
*
* Given a buffer of u8 data, print_hex_dump() prints a hex + ASCII dump
* to the kernel log at the specified kernel log level, with an optional
* leading prefix.
*
* print_hex_dump() works on one "line" of output at a time, i.e.,
* 16 or 32 bytes of input data converted to hex + ASCII output.
* print_hex_dump() iterates over the entire input @buf, breaking it into
* "line size" chunks to format and print.
*
* E.g.:
* print_hex_dump(KERN_DEBUG, "raw data: ", DUMP_PREFIX_ADDRESS,
* 16, 1, frame->data, frame->len, true);
*
* Example output using %DUMP_PREFIX_OFFSET and 1-byte mode:
* 0009ab42: 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f @ABCDEFGHIJKLMNO
* Example output using %DUMP_PREFIX_ADDRESS and 4-byte mode:
* ffffffff88089af0: 73727170 77767574 7b7a7978 7f7e7d7c pqrstuvwxyz{|}~.
*/
void print_hex_dump(const char *level, const char *prefix_str, int prefix_type,
int rowsize, int groupsize,
const void *buf, size_t len, bool ascii)
{
const u8 *ptr = buf;
int i, linelen, remaining = len;
unsigned char linebuf[32 * 3 + 2 + 32 + 1];
if (rowsize != 16 && rowsize != 32)
rowsize = 16;
for (i = 0; i < len; i += rowsize) {
linelen = min(remaining, rowsize);
remaining -= rowsize;
hex_dump_to_buffer(ptr + i, linelen, rowsize, groupsize,
linebuf, sizeof(linebuf), ascii);
switch (prefix_type) {
case DUMP_PREFIX_ADDRESS:
printk("%s%s%p: %s\n",
level, prefix_str, ptr + i, linebuf);
break;
case DUMP_PREFIX_OFFSET:
printk("%s%s%.8x: %s\n", level, prefix_str, i, linebuf);
break;
default:
printk("%s%s%s\n", level, prefix_str, linebuf);
break;
}
}
}
EXPORT_SYMBOL(print_hex_dump);
使用方法参照上述函数示例说明
//将frame->data换成需要打印的二进制数据,frame->len修改成需要打印的长度即可。
print_hex_dump(KERN_DEBUG, "raw data: ", DUMP_PREFIX_ADDRESS,
16, 1, frame->data, frame->len, true);
//打印样例
[ 2096.915479] raw data: 00000000f12cdbe2: 0f 1f 44 00 00 55 4c 8b 4f 30 48 8b 8e 80 00 00 ..D..UL.O0H.....
[ 2096.915484] raw data: 000000003d4c7653: 00 48 8b 57 28 48 c7 c7 a8 f0 10 c1 4c 8b 86 90 .H.W(H......L...
[ 2096.915488] raw data: 000000002f261c08: 00 00 00 4c 89 ce 48 89 e5 e8 0f e6 08 e4 e8 17 ...L..H.........
[ 2096.915491] raw data: 00000000f2913d8f: 12 0a ..
二、dump_stack()
该函数用于在内核函数执行过程中打印函数的调用栈信息,类似于内核OOPS打印的信息。
[ 961.675379] <vfs_read> pre_handler: p->addr = 0x000000001efd71f0, ip = ffffffffa4911e41, flags = 0x293
[ 961.675382] CPU: 1 PID: 263 Comm: systemd-journal Tainted: G OE 5.11.0-43-generic #47~20.04.2-Ubuntu
[ 961.675386] Hardware name: Acer Aspire E5-471G/EA40_HB, BIOS V1.01 04/14/2014
[ 961.675390] Call Trace:
[ 961.675392] dump_stack+0x74/0x92
[ 961.675398] handler_pre+0x33/0x37 [kprobe_example]
[ 961.675403] kprobe_ftrace_handler+0x11e/0x1f0
[ 961.675407] ? vfs_read+0x5/0x1b0
[ 961.675412] ? vfs_read+0x1/0x1b0
[ 961.675417] ? vfs_read+0x5/0x1b0
[ 961.675420] ? ksys_read+0x67/0xe0
[ 961.675425] ? vfs_read+0x5/0x1b0
[ 961.675429] ? ksys_read+0x67/0xe0
[ 961.675433] ? __x64_sys_read+0x1a/0x20
[ 961.675438] ? do_syscall_64+0x38/0x90
[ 961.675441] ? entry_SYSCALL_64_after_hwframe+0x44/0xa9
[ 961.675448] <vfs_read> post_handler: p->addr = 0x000000001efd71f0, flags = 0x293
[ 961.690382] kprobe at 000000001efd71f0 unregistered
三、printk输出函数
printk是内核格式化输出函数,可以选择不同的打印等级。
#define KERN_EMERG KERN_SOH "0" /* system is unusable */
#define KERN_ALERT KERN_SOH "1" /* action must be taken immediately */
#define KERN_CRIT KERN_SOH "2" /* critical conditions */
#define KERN_ERR KERN_SOH "3" /* error conditions */
#define KERN_WARNING KERN_SOH "4" /* warning conditions */
#define KERN_NOTICE KERN_SOH "5" /* normal but significant condition */
#define KERN_INFO KERN_SOH "6" /* informational */
#define KERN_DEBUG KERN_SOH "7" /* debug-level messages */
编译内核时通过定义CONFIG_MESSAGE_LOGLEVEL_DEFAULT=8设置默认的打印等级。
KVM需虚拟机可以通过添加 loglevel=8参数自定义打印信息输出等级。
# 动态调整内核信息的打印等级
cat /proc/sys/kernel/printk
7 4 1 7
echo 8 > /proc/sys/kernel/printk
//打印当前执行到的函数所在行数和函数名
printk(KERN_EMERG "fox: %s %d\n", __func__, __LINE__);
四、动态输出(pr_debug&dev_dbg)
前提条件:内核编译时打开CONFIG_DYNAMIC_DEBUG宏,挂载debugfs文件系统。
# 查看动态输出打印的信息
root@rlk:/home/rlk# cat /sys/kernel/debug/dynamic_debug/control
# filename:lineno [module]function flags format
init/main.c:855 [main]initcall_blacklisted =p "initcall %s blacklisted\012"
init/main.c:816 [main]initcall_blacklist =p "blacklisting initcall %s\012"
# 文件名路径 + 行号 + 函数名
五、show_regs
函数原型,当采用kprobe
跟踪函数调用时,可以获取到regs
指针,该函数不仅打印寄存器信息,同样也可以打印函数调用栈信息。
arch/ia64/kernel/process.c
void
show_regs (struct pt_regs *regs)
{
unsigned long ip = regs->cr_iip + ia64_psr(regs)->ri;
print_modules();
printk("\n");
show_regs_print_info(KERN_DEFAULT);
printk("psr : %016lx ifs : %016lx ip : [<%016lx>] %s (%s)\n",
regs->cr_ipsr, regs->cr_ifs, ip, print_tainted(),
init_utsname()->release);
printk("ip is at %pS\n", (void *)ip);
printk("unat: %016lx pfs : %016lx rsc : %016lx\n",
regs->ar_unat, regs->ar_pfs, regs->ar_rsc);
printk("rnat: %016lx bsps: %016lx pr : %016lx\n",
regs->ar_rnat, regs->ar_bspstore, regs->pr);
printk("ldrs: %016lx ccv : %016lx fpsr: %016lx\n",
regs->loadrs, regs->ar_ccv, regs->ar_fpsr);
printk("csd : %016lx ssd : %016lx\n", regs->ar_csd, regs->ar_ssd);
printk("b0 : %016lx b6 : %016lx b7 : %016lx\n", regs->b0, regs->b6, regs->b7);
printk("f6 : %05lx%016lx f7 : %05lx%016lx\n",
regs->f6.u.bits[1], regs->f6.u.bits[0],
regs->f7.u.bits[1], regs->f7.u.bits[0]);
printk("f8 : %05lx%016lx f9 : %05lx%016lx\n",
regs->f8.u.bits[1], regs->f8.u.bits[0],
regs->f9.u.bits[1], regs->f9.u.bits[0]);
printk("f10 : %05lx%016lx f11 : %05lx%016lx\n",
regs->f10.u.bits[1], regs->f10.u.bits[0],
regs->f11.u.bits[1], regs->f11.u.bits[0]);
printk("r1 : %016lx r2 : %016lx r3 : %016lx\n", regs->r1, regs->r2, regs->r3);
printk("r8 : %016lx r9 : %016lx r10 : %016lx\n", regs->r8, regs->r9, regs->r10);
printk("r11 : %016lx r12 : %016lx r13 : %016lx\n", regs->r11, regs->r12, regs->r13);
printk("r14 : %016lx r15 : %016lx r16 : %016lx\n", regs->r14, regs->r15, regs->r16);
printk("r17 : %016lx r18 : %016lx r19 : %016lx\n", regs->r17, regs->r18, regs->r19);
printk("r20 : %016lx r21 : %016lx r22 : %016lx\n", regs->r20, regs->r21, regs->r22);
printk("r23 : %016lx r24 : %016lx r25 : %016lx\n", regs->r23, regs->r24, regs->r25);
printk("r26 : %016lx r27 : %016lx r28 : %016lx\n", regs->r26, regs->r27, regs->r28);
printk("r29 : %016lx r30 : %016lx r31 : %016lx\n", regs->r29, regs->r30, regs->r31);
if (user_mode(regs)) {
/* print the stacked registers */
unsigned long val, *bsp, ndirty;
int i, sof, is_nat = 0;
sof = regs->cr_ifs & 0x7f; /* size of frame */
ndirty = (regs->loadrs >> 19);
bsp = ia64_rse_skip_regs((unsigned long *) regs->ar_bspstore, ndirty);
for (i = 0; i < sof; ++i) {
get_user(val, (unsigned long __user *) ia64_rse_skip_regs(bsp, i));
printk("r%-3u:%c%016lx%s", 32 + i, is_nat ? '*' : ' ', val,
((i == sof - 1) || (i % 3) == 2) ? "\n" : " ");
}
} else
show_stack(NULL, NULL, KERN_DEFAULT);
}
curtis@curtis-Aspire-E5-471G:~/code/linux$ sudo cat /proc/kallsyms | grep show_regs
ffffffff8de3ca40 T __show_regs
ffffffff8de42b10 T show_regs
该函数虽然不是导出函数,但是依然有方式使用该函数,参考[Linux内核未导出符号使用方法_Configure-Handler的博客-优快云博客]