整体的学习思维导图如下,后续持续更新完善
3.10.0-957.5.1.el7.x86_64
centos下安装如下rpm包, 安装前uname -r
核对自己的内核版本,下载自己对应的内核版kernel包进行安装即可
rpm -ivh kernel-debuginfo-common-x86_64-3.10.0-123.el7.x86_64.rpm
rpm -ivh kernel-debuginfo-3.10.0-123.el7.x86_64.rpm
rpm -ivh kernel-debug-devel-3.10.0-123.el7.x86_64.rpm
安装systemtap
yum install systemtap-devel-2.4-14.el7.x86_64
yum install systemtap-client-2.4-14.el7.x86_64
安装成功后测试如下:
[root@node1 kernel_compile]# stap -L 'kernel.statement("sys_open")'
kernel.statement("SyS_open@fs/open.c:1063") $filename:long int $flags:long int $mode:long int
systemtap 是一个非常强大的性能诊断以及内核调试工具,相比于传统的内核调试方法(开debug级别,printk,加打印。。):修改代码,编译模块,安装模块,运行模块 的调试过程,systemtap提供友好的内核调试方法,极大得节省了调试损耗的精力。
官网地址:systemtap
主要分为5步,从开始到结果最终是将具有探针行为的Stap脚本转换为内核模块并加载
可以使用命令stap -v xxx.stp
将解析以及加载过程打印出来
[root@node1 ~]# stap -v -g vfs.stp
Pass 1: parsed user script and 116 library script(s) using 230588virt/40808res/3156shr/38524data kb, in 190usr/10sys/195real ms.
Pass 2: analyzed script: 43 probe(s), 28 function(s), 1 embed(s), 0 global(s) using 709984virt/91896res/8712shr/81664data kb, in 880usr/190sys/1072real ms.
Pass 3: using cached /root/.systemtap/cache/b0/stap_b066af4ce5ad5d99a19d4ad9ec41fc8e_23076.c
Pass 4: using cached /root/.systemtap/cache/b0/stap_b066af4ce5ad5d99a19d4ad9ec41fc8e_23076.ko
Pass 5: starting run
stap -L 'kernel.statement("sys_read")'
查看sys_read系统调用的函数位置cat test.stp
sys_read
系统调用的进程名称和进程号#!/usr/bin/stap
probe begin { printf("begin to probe");
}probe kernel.function("vfs_read") { printf("%s %d
", execname(),pid());
}probe end { printf("end to probe");
}
执行方式 stap test.stp
或者./test.stp
脚本方式执行更加规范,且更容易编写,所以这里建议使用脚本方式进行执行
kernel.function(pattern) | 在内核函数的入口处放置探测点,可以获取参数$parm |
---|---|
kernel.function(pattern).return | 在内核函数返回时的出口处放置探测点,可以获取返回时的参数$parm |
kernel.function(pattern).call | 内核函数的调用入口处放置探测点,获取对应函数信息 |
kernel.fuction(pattern).inline | 获取符合条件的内联函数 |
kernel.function(pattern).exported | 只选择导出的函数 |
module(moduname).fuction(pattern) | 在模块modulename中调用的函数入口处放置探测点 |
module(moduname).fuction(pattern).return | 在模块module中调用的函数返回时放置探测点 |
module(moduname).fuction(pattern).call | 在模块modulename中调用的函数入口处放置探测点 |
module(moduname).fuction(pattern).inline | 在模块modulename中调用的内联函数处放置探测点 |
kernel.statement(pattern) | 在内核中的某个地址处增加探针(函数、文件行号) |
kernel.statement(pattern).absolute | 在内核中的某个地址处增加探针(函数、文件行号),精确匹配地址 |
module(modulename).statement(pattern) | 在内核模块中的某个地址处增加探针(函数、文件行号) |
函数 | 说明 |
---|---|
execname() | 获取当前进程名称 |
pid() | 当前进程的ID |
tid() | 当前线程ID |
cpu() | 当前cpu号 |
gettimeofday_s() | 获取当前系统时间,秒 |
gettimeofday_usec() | 获取当前系统时间,微秒 |
ppfunc() | 获取当前probe的函数名称,可以知道当前probe位于哪个函数 |
print_backtrace() | 打印内核函数调用栈 |
print_ubacktrace() | 打印用户态函数调用栈 |
探针名称 | 探针含义 |
---|---|
begin | 脚本开始时触发 |
end | 脚本结束时触发 |
kernel.function(“sys_read”) | 调用sys_read时触发 |
kernel.function(“sys_read”).call | 同上 |
kernel.function(“sys_read”).return | sys_read执行完,返回时触发 |
kernel.syscall.* | 调用任何系统调用时触发 |
kernel.function("*@kernel/fork.c:934") | 执行到fork.c的934行时触发 |
module(“ext3”).function(“ext3_file_write”) | 调用ext3模块中的ext3_file_write时触发 |
timer.jiffies(1000) | 每隔1000个内核jiffy时触发一次 |
timer.ms(200).randomize(50) | 每隔200毫秒触发一次,带有线性分布的随机附加时间(-50到+50) |
变量格式 | 使用 |
---|---|
$varname | 引用变量varname |
$var->field | 引用结构的成员变量 |
$var[N] | 引用数组的成员变量 |
&$var | 变量的地址 |
@var(“varname”) | 引用变量varname |
@var(“var@src/file.c”) | 引用src中file.c编译时的全局变量 |
@var(“var@src/file.c”)->field | src/file.c 中全局结构的成员变量 |
@var(“var@src/file.c”)[N] | src/file.c中全局数据变量 |
&@var(“var@src/file.c”) | 引用变量的地址 |
$var$ | 将变量转为字符串类型 |
$$vars | 包含函数所有参数,局部变量,需以字符串类型输出 |
$$locals | 包含函数所有局部变量,需以字符串类型输出 |
$$params | 包含所有函数参数的变量,需以字符串类型输出 |
stap -L 'kernel.function("vfs_statfs")'
kernel.function("vfs_statfs@fs/statfs.c:68") $path:struct path* $buf:struct kstatfs* $error:int
打印出函数的所处文件位置及行号,同时-L
参数支持打印函数的参数及类型
stap -l 'kernel.function("vfs_statfs")'
-l
参数打印的信息就稍微简略一点
kernel.function("vfs_statfs@fs/statfs.c:68")
stap -L 'kernel.statement("*@fs/statfs.c")'
查看statfs.c 文件中能够添加探针的位置
kernel.statement("SYSC_fstatfs64@fs/statfs.c:204") $fd:unsigned int $sz:size_t $buf:struct statfs64* $st:struct kstatfs
kernel.statement("SYSC_fstatfs@fs/statfs.c:195") $fd:unsigned int $buf:struct statfs* $st:struct kstatfs
kernel.statement("SYSC_statfs64@fs/statfs.c:183") $pathname:char const* $sz:size_t $buf:struct statfs64* $st:struct kstatfs
kernel.statement("SYSC_statfs@fs/statfs.c:174") $pathname:char const* $buf:struct statfs* $st:struct kstatfs
kernel.statement("SYSC_ustat@fs/statfs.c:230") $dev:unsigned int $ubuf:struct ustat* $tmp:struct ustat $sbuf:struct kstatfs
kernel.statement("SyS_fstatfs64@fs/statfs.c:204") $fd:long int $sz:long int $buf:long int $ret:long int
kernel.statement("SyS_fstatfs@fs/statfs.c:195") $fd:long int $buf:long int $ret:long int
kernel.statement("SyS_statfs64@fs/statfs.c:183") $pathname:long int $sz:long int $buf:long int $ret:long int
kernel.statement("SyS_statfs@fs/statfs.c:174") $pathname:long int $buf:long int $ret:long int
kernel.statement("SyS_ustat@fs/statfs.c:230") $dev:long int $ubuf:long int $ret:long int
kernel.statement("calculate_f_flags@fs/statfs.c:45")
kernel.statement("do_statfs64@fs/statfs.c:150") $st:struct kstatfs* $p:struct statfs64* $buf:struct statfs64
kernel.statement("do_statfs_native@fs/statfs.c:108") $st:struct kstatfs* $p:struct statfs* $buf:struct statfs
kernel.statement("fd_statfs@fs/statfs.c:97") $fd:int $st:struct kstatfs*
kernel.statement("flags_by_mnt@fs/statfs.c:12") $mnt_flags:int
kernel.statement("flags_by_sb@fs/statfs.c:33") $s_flags:int
kernel.statement("statfs_by_dentry@fs/statfs.c:51") $dentry:struct dentry* $buf:struct kstatfs*
kernel.statement("user_statfs@fs/statfs.c:79") $pathname:char const* $st:struct kstatfs* $path:struct path
kernel.statement("vfs_statfs@fs/statfs.c:68") $path:struct path* $buf:struct kstatfs* $error:int
kernel.statement("vfs_ustat@fs/statfs.c:218") $dev:dev_t $sbuf:struct kstatfs*
vfs_statfs函数如下:
int vfs_statfs(struct path *path, struct kstatfs *buf)
{ int error;error = statfs_by_dentry(path->dentry, buf);if (!error)buf->f_flags = calculate_f_flags(path->mnt);return error;
}
如下脚本test.stp
:
#!/usr/bin/stapprobe begin { printf("begin to probe");
}probe kernel.function("vfs_statfs") { //在调用vfs_statfs处添加探针printf("%s %d
", execname(),pid());//打印调用vfs_statfs函数的进程名称以及进程号printf("path : %s buf : %s
",$path->mnt->mnt_root->d_iname$,$buf$); //打印path参数的结构体成员,以及整个buf结构体probe end { printf("end to probe");
}
运行stap test.stp
输出如下:
[root@node1 ~]# stap test.stp
begin to probesafe_timer 145286
df 2041244
path : "/" buf : { .f_type=393, .f_bsize=140207161574688, .f_blocks=94629230035116, .f_bfree=29879, .f_bavail=18446620715516395336, .f_files=18446744071970029248, .f_ffree=4294967295, .f_fsid={ ...}, .f_namelen=4294967295, .f_frsize=94629230035016, .f_flags=1574682515518227985, .f_spare=[...]}
safe_timer 145286
vfs_statfs函数如下:
int vfs_statfs(struct path *path, struct kstatfs *buf)
{ int error;error = statfs_by_dentry(path->dentry, buf);if (!error)buf->f_flags = calculate_f_flags(path->mnt);return error;
}
我想要查看error
在函数statfs_by_dentry
执行完成之后的结果,那么就在下一行处添加探针
查看如下脚本test.stp
:
#!/usr/bin/stapprobe begin { printf("begin to probe");
}probe kernel.statement("vfs_statfs@fs/statfs.c:73") { //探测statfs.c中的第73行printf("%s %d
", execname(),pid());printf("error number is %d
",$error);
}probe end { printf("end to probe");
}
这里需要注意脚本中在statfs.c的第73行增加探测点,必须填写行号正确,如果73行处没有接下来想要探测的error
变量,执行报错
执行stap test.stp
输出如下
begin to probe
safe_timer 145286
error number is 0
...
我们想要将上一个打印的变量的值从0 更改为其他的数值
查看如下脚本test.stp
#!/usr/bin/stapprobe begin { printf("begin to probe");
}probe kernel.statement("vfs_statfs@fs/statfs.c:73") { printf("%s %d
", execname(),pid());printf("error number before modify is %d
",$error);$error=$1; //传入的第一个参数printf("error number after modify is %d
",$error);
}probe end { printf("end to probe");
}
执行stap -g test.stp 2
将2传入,但是运行的时候需要增加-g
参数
输出如下:
begin to probe
df 3173946
error number before modify is 0
error number after modify is 2
...
还是举例我们的vfs_statfs
,这个函数主要是在ls,df,stat…类似获取文件或者文件夹属性时由系统调用SyS_statfs
调用的
函数实现如下
int vfs_statfs(struct path *path, struct kstatfs *buf)
{ int error;error = statfs_by_dentry(path->dentry, buf);if (!error)buf->f_flags = calculate_f_flags(path->mnt);return error;
}
这里我们想要查看一下函数返回时error变量的值,查看如下test1.stp
#!/usr/bin/stapprobe begin { printf("begin to probe
");
}probe kernel.function("vfs_statfs").return { //在调用vfs_statfs处添加探针printf("%s %d
", execname(),pid());//打印调用vfs_statfs函数的进程名称以及进程号printf("error's return value is %d
", @entry($error));//打印局部变量需使用@entry($varname)
}probe end { printf("end to probe");
}
输出如下:
begin to probe
safe_timer 752879
error's return value is 0
safe_timer 752879
error's return value is 0
我们想要打印某一个系统调用的调用栈,可以执行如下脚本
#!/usr/bin/stapprobe kernel.function("vfs_statfs") { printf("%s %d
", execname(),pid());printf("----------------------------------
");print_backtrace();//打印vfs_statfs在内核态的调用栈printf("----------------------------------
");
}
如果过程中发现调用栈打印不全,则尝试如下两种办法解决
--all-modules
,类似如:stap --all-modules test.stp
,探测所有的系统模块stap
版本,像我的环境版本为version 2.4/0.158, rpm 2.4-14.el7
导致调用栈没有任何打印;需要升级stap的库才行,执行yum install systemtap -y
即可,升级之后我的版本version 4.0/0.176, rpm 4.0-10.el7_7
,这个时候成功打印内核调用栈输出如下:
safe_timer 752879
----------------------------------0xffffffff98677430 : vfs_statfs+0x0/0xc0 [kernel]0xffffffff98677725 : user_statfs+0x55/0xa0 [kernel]0xffffffff98677797 : SYSC_statfs+0x27/0x60 [kernel]0xffffffff9867799e : SyS_statfs+0xe/0x10 [kernel]0xffffffff98b5fddb : system_call_fastpath+0x22/0x27 [kernel]0x7fcb8bcfe787
----------------------------------
如果想要通过print_ubacktrace()
函数来打印用户态的系统调用,则需要安装glibc的符号调试包glibc-debuginfo
从而能够对libc
库进行调试
如下脚本test.stp
,想要获取系统调用了vfs_statfs
函数的次数
#!/usr/bin/stap
global count = 0;//全局变量
function getcount:long(task:long) //C函数计算次数
%{ int count = (int) STAP_ARG_task;count ++;STAP_RETURN(count);
%}probe begin { printf("begin to probe
");
}probe kernel.function("vfs_statfs") { printf("%s %d
", execname(),pid());printf("----------------------------------
");print_ubacktrace();printf("----------------------------------
");count = getcount(count);printf("c code caculate the count is %d
", count);
}probe end { printf("end to probe
");
}
最终输出如下,可以看到count一直在增加:
[root@node1 ~]# stap -g test.stp
WARNING: Missing unwind data for a module, rerun with 'stap -d /usr/lib64/libc-2.17.so'
begin to probe
safe_timer 752879
----------------------------------0x7fcb8bcfe787 [/usr/lib64/libc-2.17.so+0xef787/0x3c8000]
----------------------------------
c code caculate the count is 1
safe_timer 752879
----------------------------------0x7fcb8bcfe787 [/usr/lib64/libc-2.17.so+0xef787/0x3c8000]
----------------------------------
c code caculate the count is 2
systemd-journal 564
----------------------------------0x7f6176e2f7b7 [/usr/lib64/libc-2.17.so+0xef7b7/0x3c8000]
----------------------------------
c code caculate the count is 3
safe_timer 752879
以上出现的warning是因为系统并未安装glibc的调试库导致的
关于Stap嵌入C代码需要注意以下几点
getcount
函数使用的是STAP_ARG_task
来获取传入的count参数STAP_RETURN
,一般字符串则使用snprintf、strncat等方式把字符串复制到STAP_RETVALUE
里面我们想要知道当前函数被哪个进程调用,且在该函数处执行了多长时间,可以使用如下脚本进程探测
trace_sys_read.stp
#!/usr/bin/stapprobe begin { printf("begin to probe");
}probe kernel.function("sys_read").call { printf("%s -> %s
",thread_indent(4),ppfunc());
}
probe kernel.function("sys_read").return { printf("%s <- %s
",thread_indent(-4),ppfunc());
}
其中thread_indent()
函数为/usr/share/systemtap/tapset/indent.stp
中实现的一个stap脚本,该函数的功能是增加函数执行时间(微妙),进程名称(pid)打印出来,传入的参数是打印空格的个数
输出如下:
0 msgr-worker-1(2445831): -> SyS_read
31 ps(2445722): <- SyS_read
0 ps(2445722): -> SyS_read
1 ps(2445722): <- SyS_read
0 sed(2445856): -> SyS_read
2 sed(2445856): <- SyS_read
24 msgr-worker-1(2445831): <- SyS_read
发现Sys_read
系统调用被某个进程调用时的开始到返回的执行时间,这个信息对内核代码的流程分析非常有利
除了跟踪具体的某个函数被哪个进程调用之外我们还能够跟踪一个进程所调用过的函数
如下脚本,我们追踪sshd
进程调用的系统调用
#!/usr/bin/stap
probe begin { printf("begin to probe
");
}probe syscall.* //探测所有的系统调用
{ procname = execname();if (procname =~ "sshd.*"){ //使用stp脚本中的通配符匹配所有的sshd服务的子进程printf("%s[%d]: %s -> %s
", procname,pid(),name,ppfunc()); //name为sshd内部函数,ppfunc为该函数调用的系统调用}
}
输出如下,非常直观
sshd[2087388]: write -> SyS_write
sshd[2087388]: clock_gettime -> SyS_clock_gettime
sshd[2087388]: select -> SyS_select
sshd[2087388]: rt_sigprocmask -> SyS_rt_sigprocmask
sshd[2087388]: rt_sigprocmask -> SyS_rt_sigprocmask
sshd[2087388]: clock_gettime -> SyS_clock_gettime
sshd[2087388]: write -> SyS_write
sshd[2087388]: clock_gettime -> SyS_clock_gettime
sshd[2087388]: select -> SyS_select
我们来看一个有意思且非常便捷准确的阅码方式,如下代码为内核处理文件属性的逻辑
分支相对来说较多,我们想要知道当前系统针对该函数的处理过程,走到哪个分支,则执行如下探测脚本
#!/usr/bin/stapprobe begin
{ printf("begin to probe
");
}probe kernel.statement("do_statfs_native@fs/statfs.c:*")
{ printf("%s
",pp());
}
输出结果如下:
[root@node1 stap]# stap trace_code.stp
begin to probe
kernel.statement("do_statfs_native@fs/statfs.c:109")
kernel.statement("do_statfs_native@fs/statfs.c:113")
kernel.statement("do_statfs_native@fs/statfs.c:146")
kernel.statement("do_statfs_native@fs/statfs.c:148")
执行过程的具体行号已经打印出来了,此时对照代码即可知道内核处理该函数时如何进行分支处理
我们想要查看一个源码文件中函数的执行流程时怎么查看呢?因为上文已经描述了如何跟踪特定进程的执行过程,对代码稍作修改如下
#!/usr/bin/stapprobe begin
{ printf("begin to probe
");
}probe module("ceph").function("*@mds_client.c").call{ //监控mds_client.c文件中所有的函数,调用时打印printf("%s -> %s
", thread_indent(4), ppfunc());
}probe module("ceph").function("*@mds_client.c").return{ //监控mds_client.c文件中所有的函数,返回时打印printf("%s <- %s
", thread_indent(-4), ppfunc());
}
输出如下:
[root@node3 stap]# stap trace_mdsclient.stp
begin to probe0 kworker/3:2(582080): -> delayed_work 6 kworker/3:2(582080): -> __ceph_lookup_mds_session 8 kworker/3:2(582080): -> get_session 10 kworker/3:2(582080): <- get_session 12 kworker/3:2(582080): <- __ceph_lookup_mds_session 14 kworker/3:2(582080): -> con_get 16 kworker/3:2(582080): -> get_session 16 kworker/3:2(582080): <- get_session 18 kworker/3:2(582080): <- con_get
如果我们想要监控指定的进程在指定的内核文件中的执行过程,可以使用如下代码进行监控
#!/usr/bin/stapprobe begin
{ printf("begin to probe
");
}probe module("ceph").function("*@mds_client.c").call{ if(target() == pid()) { //使用target过滤我们输入的进程ipprintf("%s -> %s
", thread_indent(4), ppfunc());}
}probe module("ceph").function("*@mds_client.c").return{ if(target() == pid()) { printf("%s <- %s
", thread_indent(-4), ppfunc());}
}
执行如下stap -x pid trace_mdsclient.stp
即可对指定的进程idpid
的过滤,输出其在mds_client.c代码中的执行流程
如我想要调试ceph模块,基本的脚本编写语法我们已经在前文脚本语法中提到过,类似如下module("ceph").function("")
,整体的调试方式和我们前面描述的内核调试方式类似
需要注意调试模块之前需要将模块拷贝到目录/usr/lib/modules/`uname -r`/extra/ 之下才能够正常调试
如何检测能够正常调试一个自己的模块呢,使用如下命令
这里使用ceph模块中的ceph_statfs来做测试
stap -l 'module("ceph").function("ceph_statfs")'
,显示如下输出即可
module("ceph").function("ceph_statfs@fs/ceph/super.c:55")
kill -l
相关的信号想要抓取系统中哪个进程发送的kill信号
global target
global signal
probe nd_syscall.kill
{ target[tid()] = uint_arg(1);signal[tid()] = uint_arg(2);
}probe nd_syscall.kill.return
{ if (target[tid()] != 0) { printf("%-6d %-12s %-5d %-6d %6d
", pid(), execname(),signal[tid()], target[tid()], int_arg(1));delete target[tid()];delete signal[tid()];}
}
stap test.stp
会抓取发送kill信号的进程
草色新雨中, 松声晚窗里。之前我们学习 Power Query 都是用鼠标就完成了很多复杂的操作。虽然 PowerQuery 已经将大部分常用功能内置成到功能区。基本能完成我们大部分的报表自动化功能。但是总有些复杂的或者个性化的问题是开发团队没有预先想到的,这时我们就需要学习 M 语言。一、M 语言在哪里?M语言的函数公式有三个地...
前言从2020年3月份开始,计划写一系列文档--《小白从零开始学编程》,记录自己从0开始学习的一些东西。第一个系列:python,计划从安装、环境搭建、基本语法、到利用Django和Flask两个当前最热的web框架完成一个小的项目第二个系列:可能会选择Go语言,也可能会选择Vue.js。具体情况待定,拭目以待吧。。。基本概念表达式表...
1.1函数1.1.1什么是函数函数就是程序实现模块化的基本单元,一般实现某一功能的集合。函数名:就相当于是程序代码集合的名称参数:就是函数运算时需要参与运算的值被称作为参数函数体:程序的某个功能,进行一系列的逻辑运算return 返回值:函数的返回值能表示函数的运行结果或运行状态。1.1.2函数的作用函数是组织好的,可重复使用的,用来...
原标题:基于Python建立深度神经网络!你学会了嘛?图1 神经网络构造的例子(符号说明:上标[l]表示与第l层;上标(i)表示第i个例子;下标i表示矢量第i项)单层神经网络图2 单层神经网络示例神经元模型是先计算一个线性函数(z=Wx+b),接着再计算一个激活函数。一般来说,神经元模型的输出值是a=g(Wx+b),其中g是激活函数(...
在学习MySQL的时候你会发现,它有非常多的函数,在学习的时候没有侧重。小编刚开始学习的时候也会有这个感觉。不过,经过一段时间的学习之后,小编发现尽管函数有很多,但是常用的却只有那几个。今天小编就把常用的函数汇总一下,为大家能够能好的学习MySQL中的函数。MySQL常使用的函数大概有四类。时间函数、数学函数、字符函数、控制函数。让我...
#include
awk格式化使用printf函数,类似于C语言中的printf函数 比如 awk '{printf "%s ", $1}' test1 上面的方式是awk每次处理一行,然后进行替换的,如果我们想要传入多个参数,此时就需要多个格式化...
【目的】 定义一个结构体类,其中的成员变量数组长度不定,根据实例化的对象指定长度,所以想到用指针实现 【现状】 指针可以指向任意长度数组,但结构体类只分配指针本身4字节长度,所以无法扩展 1 /** 2 *****************************************************...
1.有一个四位正整数,组成这个四位数的四个数字各不相同,如果把它们的首尾互换,第二位与第三位互换,组成一个新的四位数。原四位数为新四位数的4倍,请找出一个这样的四位数。 #include
if条件语句 非零即真 0即假 if(表达式){ //成立之后要处理的事情 } 以atm小程序为例 //判断用户选择的操作 if (operation == 1){ //输入密码 printf("输入密码 "); } if (operation...