从 极客时间 《容器高手实战课》 《趣谈linux操作系统》学习来的知识。
可以看到,作为云计算的从业者,无论是openstack,还是kubernetes,掌握linux内核的源码是必要的技能,还好现在认识到这个还不算晚。
tracepoint
如果内核在某个内核符号上加上了固定的tracepoint,说明,这个函数是在某个执行流程中的关键函数。
kprobe
如果某个内核函数已经有了固定的hook点函数,则用entry替换,不是int3。
#define BRAKPOINT_INSTRUCTION 0xcc
// 指令替换函数,将内核符号的地址替换为0xcc指令,该指令会触发硬中断
void arch_arm_kprobe(struct kprobe *p) {
text_poke(p->addr, (unsigned char []){BREAKPOINT_INSTRUCTION},1);
}
// 中断处理函数是do_int3
idtentry int3 do_int3
struct idt_data early_idts[] = {
....
SYSG(X86_TRAP_BP, int3)
}
kprobe_int3_handler 做前置处理
int kprobe_int3_handler(struct pt_regs *regs)
{
kprobe_opcode_t *addr;
addr = (kprobe_opcode_t *)(regs->ip - sizeof(kprobe_opcode_t));
// 根据符号地址找到kprobe的结构,全局变量kprobe_table
p = get_kprobe(addr);
}
最后调用do_trap(X86_TRAP_BP, SIGTRAP, "int3", regs, error_code, NULL)
注册kprobe接口register_kprobe
struct kprobe {
struct list_head list;
kprobe_opcode_t *addr; // 可以填写地址;
const char *symbol_name; // 可以使用内核符号,最终符号也是要转换为地址;
}
ftrace 与tracefs
DECLARE_TRACE
__DEFINE_TRACE -> __DO_TRACE
DEFINE_EVENT include\trace\event\net.h中有很多。
DECLARE_EVENT_CLASS(net_dev_template) include\trace\perf.h
这两个宏用来定义trace函数(tracepoint_probe_register)。
阐述trace的原理,以及tracefs,比起其他工具的优缺点;
可以查看函数调用栈,函数调用延时,不能查看返回值;
通过tracefs向trace模块下发命令,结果采样输出到ring_buff,用户从tracefs获取结果。
func_stack_trace 看完整调用栈;
function_graph trracer 可以用来查看内核函数和它的子函数调用关系以及调用时间。
fs/tracefs/inode.c中注册了tracefs的文件系统
static struct file_system_type trace_fs_type = {
.owner = THIS_MODULE,
.name = "tracefs",
.mount = trace_mount,
.kill_sb = kill_litter_super,
};
kernel\trace\*.c 是trace实现的主要逻辑。
mount -t tracefs
cat available_tracers 查看可用的trace类型
echo do_mount > set_ftrace_filter 设置filter,减少输出,利于分析。
echo 1 > options/func_stack_trace 打开调用栈
# echo kfree_skb > set_graph_function
#设置kfree_skb()
echo nop > current_tracer
#暂时把current_tracer设置为nop, 这样可以清空trace
# echo function_graph > current_tracer
### 把current_tracer设置为function_graph
inux 内核在编译的时候,缺省会使用三个 gcc 的参数"-pg -mfentry -mrecord-mcount"。
其中,"-pg -mfentry"这两个参数的作用是,给编译出来的每个函数开头都插入一条指令"callq "。
在内核符号前面放上一条 callq <__fentry__>
而"-mrecord-mcount"参数在最后的内核二进制文件 vmlinux 中附加了一个 mcount_loc 的段,这个段里记录了所有"callq "指令的地址。这样我们很容易就能找到每个函数的这个入口点。
内核初始化过程中,会重新申请ftrace_pages,并在一开是就将fentry替换为nop指令,相当于预留了5个字节,当需要使用的时候,可以在放入callq指令。
__ftrace_ops_list_func
ftrace_ops_list
function_trace_call
trace_function 放入ring_buff
使用ftrace最终路由查询时间
for pid in `cat calico_pid`; do echo $pid >> set_ftrace_pid; done
echo 'netlink_dump' > set_graph_function
echo function_graph > current_tracer
echo 2 > max_graph_depth
cat trace_pipe
perf 与火焰图
要阐述清楚,perf的工作方式是什么。
perf list可以看到所有的tracepoint点,这是内置的固定的trace点。
perf依赖于event的数据采集,是内核代码中的一个工具。
perf_sw_event(PERF_COUNT_SW_PAGE_FAULTS,1,regs,address)增加采样值。
在kernel\events\core.c中定义了系统调用的入口
SYSCALL_DEFINE5(perf_event_open,
struct perf_event_attr __user *, attr_uptr,
pid_t, pid, int, cpu, int, group_fd, unsigned long, flags)
编译静态链接的perf工具
在内核源码toos/perf/
修改Makefile.perf,增加LDFLAGS=-static
执行make,即可生成静态链接的perf工具,拷贝到相同内核版本的机器上,就可以直接运行。
perf record -a -g -- sleep 60
perf script > out.perf
git clone --depth 1 https://github.com/brendangregg/FlameGraph.git
FlameGraph/stackcollapse-perf.pl out.perf > out.folded
FlameGraph/flamegraph.pl out.folded > out.sv
ebpf
先编译成bpfbytecodes,在用jit编译成宿主机本地指令。
samples/bpf下有大量的示例程序。
编译命令
clang -O2 -target bpf -c src/bpf_program.c -Ikernel-src/tools/testing/selftests/bpf -Ikernel-src/tools/lib/bpf -o src/bpf_program.o
do_load_bpf_file 负责解析内核.o文件,分析各个section,
static int do_load_bpf_file(const char *path, )
{
fd = open(path, O_RDONLY, 0);
elf = elf_begin();
for (i = 1; i < ehdr.e_shnum; i++)
{
license
version
maps
}
load_elf_maps_section();
for (i = 1; i < ehdr.e_shnum; i++) {
kprobe
tracepoint
socket
cgroup
sk_msg
load_and_attach();
sys_bpf(cmd, attr, size) //调用bpf的系统调用
}
}
___bpf_prog_run 执行具体的epbf字节码
三种分类
BPF Program Types 程序类型细分,tracepoint,kprobe,xdp,socket,cgroup...
BPF Maps 内核产生的数据
BPF Helpers 提供的辅助通用函数
enum bpf_cmd;
enum bpf_map_type;
enum bpf_prog_type;
enum bpf_attach_type;
kernel/bpf/syscall.c 有定义bpf的系统调用处理
SYSCALL_DEFINE3(bpf, int, cmd...)
ebpf前端工具
bcc工具集,bpftool,ebpf-exporter
opensnoop-bpfcc 事件
softirqd-bpfcc 直方图
很奇葩,python代码中,用字符串将C代码包起来,利用python的libbcc库将c代码编译成字节码(bpf_module_create_c_from_string),libbcc中集成了llvm/clang工具集,但是依赖头文件,相当于还是得有环境才可以用。
root@iZt4n98ewjqvn9dxd7lg5gZ:~/linux-4.19# ls /usr/sbin/*-bpfcc
/usr/sbin/argdist-bpfcc /usr/sbin/deadlock_detector-bpfcc /usr/sbin/killsnoop-bpfcc /usr/sbin/pythoncalls-bpfcc /usr/sbin/syscount-bpfcc
/usr/sbin/bashreadline-bpfcc /usr/sbin/deadlock_detector.c-bpfcc /usr/sbin/llcstat-bpfcc /usr/sbin/pythonflow-bpfcc /usr/sbin/tcpaccept-bpfcc
/usr/sbin/biolatency-bpfcc /usr/sbin/execsnoop-bpfcc /usr/sbin/mdflush-bpfcc /usr/sbin/pythongc-bpfcc /usr/sbin/tcpconnect-bpfcc
/usr/sbin/biosnoop-bpfcc /usr/sbin/ext4dist-bpfcc /usr/sbin/memleak-bpfcc /usr/sbin/pythonstat-bpfcc /usr/sbin/tcpconnlat-bpfcc
/usr/sbin/biotop-bpfcc /usr/sbin/ext4slower-bpfcc /usr/sbin/mountsnoop-bpfcc /usr/sbin/reset-trace-bpfcc /usr/sbin/tcplife-bpfcc
/usr/sbin/bitesize-bpfcc /usr/sbin/filelife-bpfcc /usr/sbin/mysqld_qslower-bpfcc /usr/sbin/rubycalls-bpfcc /usr/sbin/tcpretrans-bpfcc
/usr/sbin/bpflist-bpfcc /usr/sbin/fileslower-bpfcc /usr/sbin/nfsdist-bpfcc /usr/sbin/rubyflow-bpfcc /usr/sbin/tcptop-bpfcc
/usr/sbin/btrfsdist-bpfcc /usr/sbin/filetop-bpfcc /usr/sbin/nfsslower-bpfcc /usr/sbin/rubygc-bpfcc /usr/sbin/tcptracer-bpfcc
/usr/sbin/btrfsslower-bpfcc /usr/sbin/funccount-bpfcc /usr/sbin/nodegc-bpfcc /usr/sbin/rubyobjnew-bpfcc /usr/sbin/tplist-bpfcc
/usr/sbin/cachestat-bpfcc /usr/sbin/funclatency-bpfcc /usr/sbin/nodestat-bpfcc /usr/sbin/rubystat-bpfcc /usr/sbin/trace-bpfcc
/usr/sbin/cachetop-bpfcc /usr/sbin/funcslower-bpfcc /usr/sbin/offcputime-bpfcc /usr/sbin/runqlat-bpfcc /usr/sbin/ttysnoop-bpfcc
/usr/sbin/capable-bpfcc /usr/sbin/gethostlatency-bpfcc /usr/sbin/offwaketime-bpfcc /usr/sbin/runqlen-bpfcc /usr/sbin/vfscount-bpfcc
/usr/sbin/cobjnew-bpfcc /usr/sbin/hardirqs-bpfcc /usr/sbin/oomkill-bpfcc /usr/sbin/slabratetop-bpfcc /usr/sbin/vfsstat-bpfcc
/usr/sbin/cpudist-bpfcc /usr/sbin/javacalls-bpfcc /usr/sbin/opensnoop-bpfcc /usr/sbin/softirqs-bpfcc /usr/sbin/wakeuptime-bpfcc
/usr/sbin/cpuunclaimed-bpfcc /usr/sbin/javaflow-bpfcc /usr/sbin/phpcalls-bpfcc /usr/sbin/solisten-bpfcc /usr/sbin/xfsdist-bpfcc
/usr/sbin/dbslower-bpfcc /usr/sbin/javagc-bpfcc /usr/sbin/phpflow-bpfcc /usr/sbin/sslsniff-bpfcc /usr/sbin/xfsslower-bpfcc
/usr/sbin/dbstat-bpfcc /usr/sbin/javaobjnew-bpfcc /usr/sbin/phpstat-bpfcc /usr/sbin/stackcount-bpfcc /usr/sbin/zfsdist-bpfcc
/usr/sbin/dcsnoop-bpfcc /usr/sbin/javastat-bpfcc /usr/sbin/pidpersec-bpfcc /usr/sbin/statsnoop-bpfcc /usr/sbin/zfsslower-bpfcc
/usr/sbin/dcstat-bpfcc /usr/sbin/javathreads-bpfcc /usr/sbin/profile-bpfcc /usr/sbin/syncsnoop-bpfcc
示例
./functioncount '__netlink_dump_start' -I 10
./functionlatency '__netlink_dump_start' -I 10
./funcslower mutex_lock -m 2 -T
systemtap
红帽出品,文档详细。
安装
kernel-devel , kernel-debuginfo, kernel-debuginfo-common-arch
官网提示,需要debuginfo的rpm包(需要开启debug的repo),而不是debug包,where is the difference?
systemtap 和 systemtap runtime
yum install -y kernel-devel-$(uname -r) \
kernel-debuginfo-$(uname -r) \
kernel-debuginfo-common-$(uname -m)-$(uname -r)
交叉编译
stap -r kernel_version script -m module_name -p4
-r 内核代码 kernel_version(uname -r)
script 脚本路径
-m 生成的模块名称
-p 执行到第4步
在目标机器上执行staprun module_name.ko
example:
stap -v -r $(uname -r) -e 'probe vfs.read {printf("read aaa"); exit()}' -m luglread -p4
内置函数
pp(), execname(), pid(),cpu(), thread_indent(优化输出结果,非常有用),probefunc() ...
使用示例
sstap -v -e 'probe kernel.function("net_rx_action").call {print_backtrace();printf("%s : %d--------------\n", execname(), pid())}s'
stap -L 'kernel.function("net_rx_action")'
stap -v -e 'probe kernel.function("sys_open").call {
printf("filename: %p(%s), flags: %d, mode: %x\n",
pointer_arg(1), kernel_string(pointer_arg(1)), int_arg(2), int_arg(3));
}'
stap -v -e 'probe syscall.open {printf("%s(%d) open (%s)\n", execname(), pid() ,argstr)}'
stap -v -e 'probe kernel.function("*@net/socket.c").call {printf("%s -> %s\n", thread_indent(1),ppfunc())} probe kernel.function("*@net/socket.c").return {printf("%s <- %s\n", thread_indent(-1), ppfunc())}'
stap -v -e 'probe syscall.*.call {printf("%s -> %s\n", thread_indent(1),ppfunc())} probe syscall.*.return {printf("%s <- %s\n", thread_indent(-1), ppfunc())}'
sysrq
echo h > /proc/sysrq-trigger 显示当前可用的trigger信息,不同系统显示不通
sysrq: SysRq : HELP : loglevel(0-9) reboot(b) crash(c) terminate-all-tasks(e) memory-full-oom-kill(f) kill-all-tasks(i) thaw-filesystems(j) sak(k) show-backtrace-all-active-cpus(l) show-memory-usage(m) nice-all-RT-tasks(n) poweroff(o) show-registers(p) show-all-timers(q) unraw(r) sync(s) show-task-states(t) unmount(u) show-blocked-tasks(w) dump-ftrace-buffer(z)
各种型号电脑触发sysrq的方式也不同。
static struct sysrq_key_op *sysrq_key_table[36] = {
&sysrq_loglevel_op, /* 0 */
&sysrq_loglevel_op, /* 1 */
&sysrq_loglevel_op, /* 2 */
&sysrq_loglevel_op, /* 3 */
&sysrq_loglevel_op, /* 4 */
&sysrq_loglevel_op, /* 5 */
&sysrq_loglevel_op, /* 6 */
&sysrq_loglevel_op, /* 7 */
&sysrq_loglevel_op, /* 8 */
&sysrq_loglevel_op, /* 9 */
/*
* a: Don't use for system provided sysrqs, it is handled specially on
* sparc and will never arrive.
*/
NULL, /* a */
&sysrq_reboot_op, /* b */
&sysrq_crash_op, /* c */
&sysrq_showlocks_op, /* d */
&sysrq_term_op, /* e */
&sysrq_moom_op, /* f */
/* g: May be registered for the kernel debugger */
NULL, /* g */
NULL, /* h - reserved for help */
&sysrq_kill_op, /* i */
#ifdef CONFIG_BLOCK
&sysrq_thaw_op, /* j */
#else
NULL, /* j */
#endif
&sysrq_SAK_op, /* k */
#ifdef CONFIG_SMP
&sysrq_showallcpus_op, /* l */
#else
NULL, /* l */
#endif
&sysrq_showmem_op, /* m */
&sysrq_unrt_op, /* n */
/* o: This will often be registered as 'Off' at init time */
NULL, /* o */
&sysrq_showregs_op, /* p */
&sysrq_show_timers_op, /* q */
&sysrq_unraw_op, /* r */
&sysrq_sync_op, /* s */
&sysrq_showstate_op, /* t */
&sysrq_mountro_op, /* u */
/* v: May be registered for frame buffer console restore */
NULL, /* v */
&sysrq_showstate_blocked_op, /* w */
/* x: May be registered on mips for TLB dump */
/* x: May be registered on ppc/powerpc for xmon */
/* x: May be registered on sparc64 for global PMU dump */
/* y: May be registered on sparc64 for global register dump */
NULL, /* x */
NULL, /* y */
&sysrq_ftrace_dump_op, /* z */
};