systemtap引用自定义头文件的手艺

自从写第一个guru模式的stap脚本,我就再也不写内核模块了。stap简直太方便了,竟然可以将C语言当脚本语言来玩。

随着我写的stap脚本越来越复杂,我需要使用一些半吊子设计模式来让代码更好看些,比如我不会再将所有东西写在一个stap脚本里,我会将一些公共的结构体定义,宏定义写在一个头文件里:

// common.h
#define VAR	100

然后在我的stap脚本里引用它们就是了:

#!/usr/local/bin/stap -g
// aa.stp
#!/usr/local/bin/stap -g

%{
    
    
#include "common.h"
%}

function func(who:long)
%{
    
    

	STAP_PRINTF("%d   %lu\n", VAR, STAP_ARG_who);
%}

probe begin
{
    
    
	func($1);
	exit();
}

然而不行。会报错:

[root@localhost test]# ./aa.stp 1
/tmp/stapxVC8YT/stap_c5a511d01be2d078445a0de2c20c8a7f_1308_src.c:29:20: 致命错误:common.h:没有那个文件或目录
 #include "common.h"
                    ^
编译中断。
make[1]: *** [/tmp/stapxVC8YT/stap_c5a511d01be2d078445a0de2c20c8a7f_1308_src.o] 错误 1
make: *** [_module_/tmp/stapxVC8YT] 错误 2
WARNING: kbuild exited with status: 2
Pass 4: compilation failed.  [man error::pass4]

明明common.h就在当前目录,却没有找到。遇到这种问题,应该如何解决呢?

其实,了解stap原理的都会明白怎么回事,只需要用-vvv导出详细日志,看一下make参数即可:

[root@localhost test]# ./aa.stp 1 -vvv >./log 2>&1
[root@localhost test]# grep -rn 'make -C' ./log
521:Running env -uARCH -uKBUILD_EXTMOD -uCROSS_COMPILE -uKBUILD_IMAGE -uKCONFIG_CONFIG -uINSTALL_PATH -uLD_LIBRARY_PATH PATH=/usr/bin:/bin:/root/.cargo/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin:/bin:/sbin make -C /lib/modules/3.10.0-327.x86_64/build M=/tmp/stapmjgFbY modules CONFIG_DEBUG_INFO= CONFIG_STACK_VALIDATION= CONFIG_MODVERSIONS= ARCH=x86_64 V=1 -j2

你看,你看,include目录根本没有包含当前目录嘛。

如果想包含当前目录,需要在Makefile中增加以下选项:

EXTRA_CFLAGS += -I$(pwd)

然而如何去做到呢?

嗯,修改stap源码是最直接的,直接找到生成Makefile的逻辑的地方,照猫画虎将以上字符串padding到Makefile末尾即可。在完成手艺之后,我特意尝试了这种重新编译的方法,确实简单。

grep EXTRA_CFLAGS找到buildrun.cxx文件的compile_pass函数,增加:

扫描二维码关注公众号,回复: 12142195 查看本文章
string module_cflags = "EXTRA_CFLAGS";
o << module_cflags << " :=" << endl;
// 以下为新增行
o << module_cflags << "EXTRA_CFLAGS += -I$(pwd)" << endl;

然后重新编译。

但这不是手艺人的做法!手艺人需要不修改代码,不重新编译完成此事。

咬尾蛇玩多了,思路就是这么简单, 让stap自己tap自己呗!

使用stap将被运行的stap的Makefile命令生成函数hook住,然后在命令生成之前往Makefile中padding上面的字符串。

为了不依赖源代码找出需要hook的函数,只能靠猜测:

stap -L 'process("/usr/local/bin/stap").function("*make*")'

以上命令会导出一个列表,依赖作者函数命名的清晰程度,我一个一个连猜带试折腾了一个小时,终于确定了下面的函数:

process("/usr/local/bin/stap").function("make_any_make_cmd@/usr/test/systemtap/buildrun.cxx:96") $s:struct systemtap_session& $dir:string const& $target:string const& $newpath:string $make_cmd:class vector<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::allocator<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > >

我一开始用的是strings /usr/local/bin/stap|grep make,而不是用的stap -L。

找到了目标函数,我通过printf还知道其dir参数就是Makefile的目录,接下来让我们开始吧:

#!/usr/local/bin/stap -g

function padding(dir:string)
%{
    
    
	char mf[64];
	// 增加一个新的环境变量,以引用任意地方的头文件
	char *ext = "EXTRA_CFLAGS += -I$(STAP_INCLUDE)\n";
	struct file *fp;
	mm_segment_t old_fs;
	loff_t pos;

	snprintf(mf, 64, "%s/Makefile", (char *)STAP_ARG_dir);
	// 在内核空间将EXTRA_CFLAGS追加到Makefile的最后
	fp = filp_open(mf, O_RDWR | O_APPEND, 0644);
	old_fs = get_fs();
	set_fs(KERNEL_DS);
	pos = fp->f_pos;
	vfs_write(fp, ext, strlen(ext), &pos);
	fp->f_pos = pos;
	set_fs(old_fs);
	filp_close(fp, NULL);
%}

probe process("/usr/local/bin/stap").function("make_any_make_cmd")
{
    
    
	// 不懂C++,我是通过printf("%s\n", $dir$);找到_M_local_buf成员变量的。
	padding(user_string($dir->_M_local_buf));
}

来来来,看效果:

[root@localhost test]# export STAP_INCLUDE=`pwd`
[root@localhost test]# echo $STAP_INCLUDE
/usr/test
[root@localhost test]# ./selftap -c  '/usr/local/bin/stap -g ./aa.stp 3'
100   3

事情就是这样。

值得一提的是,只要你的aa.stp脚本不发生变化,且其运行参数不发生变化,当你后续再执行aa.stp的时候,就可以直接执行了,无需再selftap:

[root@localhost test]# ./aa.stp 3
100   3

然而一旦脚本本身或者参数发生了变化,就必须再重新selftap一次:

[root@localhost test]# ./aa.stp 3
100   3
[root@localhost test]# ./aa.stp 5
/tmp/stapLTrZIV/stap_4e39e7642313b2bf2a6d9120e03487ab_1308_src.c:29:20: 致命错误:common.h:没有那个文件或目录
 #include "common.h"
                    ^
编译中断。
make[1]: *** [/tmp/stapLTrZIV/stap_4e39e7642313b2bf2a6d9120e03487ab_1308_src.o] 错误 1
make: *** [_module_/tmp/stapLTrZIV] 错误 2
WARNING: kbuild exited with status: 2
Pass 4: compilation failed.  [man error::pass4]
[root@localhost test]# ./selftap -c  '/usr/local/bin/stap -g ./aa.stp 5'
100   5
[root@localhost test]# ./aa.stp 5
100   5

这是stap的cache机制在起作用,上一次操作的一切都cache在~/.systemtap/cache目录下。

其实,这篇文章展示的手艺旨在表达一种感情,只要理解了一个软件的运行原理,你可以肆意折腾它,在不改变源码的情况下改变其行为。

理解了stap其实是通过编译成内核模块完成工作的大前提之后,就可以hook住其生成编译命令的过程,在Makefile里任意存在命令,达到一些特殊的目的。


浙江温州皮鞋湿,下雨进水不会胖。

猜你喜欢

转载自blog.csdn.net/dog250/article/details/108230157