内核调试 - 乱七八糟

从 极客时间 《容器高手实战课》 《趣谈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 */
};

你可能感兴趣的:(内核调试 - 乱七八糟)