月末最后一晚,总结简单实用的Linux调试工具使用技巧两则。
为了测试或者修改Linux内核的一个特性,我们通常会写一个模块,比如打印一些内核的信息,比如修改一个物理页面之类。
以打印hello world以及传入的两个参数为例,我们编写如下最简单的内核模块:
// simple_mod.c
// make -C /lib/modules/`uname -r`/build SUBDIRS=`pwd` modules
#include
static unsigned long param1 = 0;
module_param(param1, long, 0644);
static unsigned long param2 = 0;
module_param(param2, long, 0644);
static int simple_init(void)
{
printk("hello world %lx %lx\n", param1, param2);
return -1;
}
static void simple_exit(void)
{
}
module_init(simple_init);
module_exit(simple_exit);
MODULE_LICENSE("GPL");
为了让这个内核模块起作用,必须编译加载它,首先我们要有个Makefile:
[root@localhost mod]# cat Makefile
obj-m = simple_mod.o
然后按照下面的命令编译之:
[root@localhost mod]# make -C /lib/modules/`uname -r`/build SUBDIRS=`pwd` modules
最后,我们加载它,然后用dmesg查看结果:
[root@localhost mod]# insmod ./simple_mod.ko
insmod: ERROR: could not insert module ./simple_mod.ko: Operation not permitted
[root@localhost mod]# dmesg
[ 9807.368562] hello world 0 0
[root@localhost mod]#
我们梳理一下这些步骤:
好麻烦!使用systemtap会简单很多。
systemtap不仅仅可以动态探测debug内核,它可以进入Guru模式嵌入C代码实现一个完整的内核模块,只需要stap加上-g参数:
-g Guru mode. Enable parsing of unsafe expert-level constructs like embedded C.
我们看看这是多么简单:
我们用一个小例子展示一下,该例子除了完全实现上述hello world模块的功能之外,还能刷新系统TLB:
// hello_module.stp
%{
#include
%}
function flush_tlb_print_test:long(pid:long, addr:long)
%{
void (*pflush_tlb_all)(void);
// 从/proc/kallsyms查询到的flush_tlb_all的地址。
pflush_tlb_all = (void (*))0xffffffff81066090;
// 以STAP_PRINTF替代printk,可以不必再dmesg展示结果,这样结果可以直接打stdout。
// 当然,你也可以继续使用printk,然后用dmesg来看结果
STAP_PRINTF("hello world %llx %llx\n", STAP_ARG_pid, STAP_ARG_addr);
pflush_tlb_all();
STAP_RETVALUE = 0;
%}
probe begin
{
// $开头为数字参数,@开头为字符串参数
flush_tlb_print_test($1, $2);
exit();
}
就这么简单,不再需要Linux内核模块的例行编写方法,这些systemtap都替你做了。
下面我们只需要一步就能看到效果,执行stap命令即可:
[root@localhost stap]# stap -g hello_module.stp 4439 0x10d7010
hello world 1157 10d7010
[root@localhost stap]#
简单的两步即可:
systemtap嵌入C代码比编写独立的内核模块效率要高很多。
昨天,Windows安全超级专家咨询了问题:
windbg有这个功能,bp 0x12345678 “dt xxx; g;”,意思是下个断点,到了断点处print一个东西然后自动继续运行。gdb有类似的功能吗?b *0x12345678 "p xxx; c"这样
其实gdb是有这个功能的,下面简单演示一下。
先看一个测试代码:
// gcc -g test.c -o test
#include
#include
int global = 0;
void func()
{
printf("Process in [%s]\n", __FUNCTION__);
}
int main()
{
global ++;
func();
return 0;
}
编译它:
[root@localhost gdb]# gcc -g test.c -o test
然后gdb调试test,我们希望在func处下个断点,然后打印global变量后无需人工干预继续执行下面的语句:
[root@localhost gdb]# gdb ./test -q
Reading symbols from /root/gdb/test...done.
(gdb) b func
Breakpoint 1 at 0x400521: file test.c, line 8.
(gdb) commands 1
Type commands for breakpoint(s) 1, one per line.
End with a line saying just "end".
>p global
>cont
>end
(gdb) r
Starting program: /root/gdb/./test
Breakpoint 1, func () at test.c:8
8 printf("Process in [%s]\n", __FUNCTION__);
$1 = 1
Process in [func]
[Inferior 1 (process 6831) exited normally]
(gdb)
为断点写一个commands命令序列即可,其中commands序列用断点编号来索引。
浙江温州皮鞋湿,下雨进水不会胖。