三大调试工具gdb,*trace,systemTap使用指南

2 篇文章 1 订阅

        总体而言三者各有千秋,gdb可动态调试,ftrace主打系统函数追踪,而systemtap功能很强大,个人感觉基本覆盖了ftrace,且能动态捕获系统函数或应用函数的执行时间,项目工作中很实用。

1.gdb

gdb太普遍了,讲的人很多,直接给前人的链接了:

GDB调试之基本指令介绍 | 田宇的个人博客

https://blog.csdn.net/weixin_37921201/article/details/120117096

https://zhuanlan.zhihu.com/p/100403438
GDB条件断点(condition命令)详解

GDB学习_东边的西瓜皮的博客-CSDN博客

这里只补充下启动gdb的几种方式,本次以用户程序qemu为例说明:

//直接加程序名调试
gdb qemu-system-x86_64

//带参数调试(qemu-system-ppc64后面的都是qemu自己的参数)
gdb --args /home/jcf/qemu-6.2.0/build/qemu-system-ppc64 -m 1G -smp 1 -boot cd -hda /home/jcf/pseries.img

//图形化界面调试
gdb --tui --args /home/jcf/qemu-6.2.0/build/qemu-system-ppc64 -m 1G -smp 1 -boot cd -hda /home/jcf/pseries.img

//程序已启动,附加上去调试(pid需要用 ps -ef 提前获取)
gdb --tui attach $pid
gdb --tui attach --pid=xxx
gdb --tui /home/jcf/qemu-6.2.0/build/qemu-system-ppc64 $pid

2.ftrace,strace,ptrace……

一、总体介绍

*trace系列基本用于跟踪,细分的话有跟踪系统调用的strace(简单易用),跟踪系统函数执行的ftrace,跟踪进程执行的ptrace等,本文目前暂介绍实际工程项目中用到的ftrace。

ftrace官方文档在kernel/Documentation/trace/ftrace.txt文件中。

首先要确定内核打开了ftrace的编译选项: CONFIG_FTRACE (一般默认打开)

使用ftrace接口之前,先检查debugfs系统是否挂载(一般默认打开)

# mount | grep debugfs

如果系统没有自动挂载debugfs文件系统,则要先手动挂载:

# mount -t debugfs nodev /sys/kernel/debug

ftracer的目录为/sys/kernel/debug/tracing,下面介绍这个目录下的常用文件:

tracing_on,启用/禁用向追踪缓冲区写入功能。1为启用,0为禁用。

available_tracers,当前内核中可用的插件追踪器。

current_tracer,指定当前要使用的插件追踪器。

tracing_cpumask,以十六进制的位掩码指定要作为追踪对象的处理器,例如,指定0xb时仅在处理器0、1、3上进行追踪。

set_ftrace_pid,指定作为追踪对象的进程的PID号。

trace,以文本格式输出内核中追踪缓冲区的内容。

trace_pipe,与trace相同,但是运行时像管道一样,可以在每次事件发生时读出追踪信息,但是读出的内容不能再次读出。

buffer_size_kb,以KB为单位指定各个CPU追踪缓冲区的大小。系统追踪缓冲区的总大小就是这个值乘以CPU的数量。设置buffer_size_kb时,必须设置current_tracer为nop追踪器。

set_ftrace_filter,指定要追踪的函数名称,函数名称仅可以包含一个通配符。

set_ftrace_notrace,指定不要追踪的函数名称。

ftrace可以使用的插件追踪器主要有:

function,函数调用追踪器,可以看出哪个函数何时调用。

function_graph,函数调用图表追踪器,可以看出哪个函数被哪个函数调用,何时返回。

mmiotrace,MMIO( Memory MappedI/O)追踪器,用于Nouveau驱动程序等逆向工程。

blk,block I/O追踪器。

wakeup,进程调度延迟追踪器。

wakeup_rt,与wakeup相同,但以实时进程为对象。

irqsoff,当中断被禁止时,系统无法响应外部事件,造成系统响应延迟,irqsoff跟踪并记录内核中哪些函数禁止了中断,对于其中禁止中断时间最长的,irqsoff将在log文件的第一行标示出来,从而可以迅速定位造成系统响应延迟的原因。

preemptoff,追踪并记录禁止内核抢占的函数,并清晰显示出禁止内核抢占时间最长的函数。

preemptirqsoff,追踪并记录禁止内核抢占和中断时间最长的函数。

sched_switch,进行上下文切换的追踪,可以得知从哪个进程切换到了哪个进程。

nop,不执行任何操作。不使用插件追踪器时指定。

二、使用案例步骤

cd /sys/kernel/debug/tracing

echo 0 > tracing_on  //关闭trace

echo function_graph > current_tracer //设置当前的跟踪器是function_graph(调用关系)

echo 5 > max_graph_depth //设置调用关系深度为5层

echo function > current_tracer //设置当前的跟踪器是function(列表)

echo funcgraph-proc > trace_options

echo funcgraph-abstime > trace_options  //在输出结果里增加每个函数的时间戳

ps -aux | grep xxx //查看要追踪的进程pid号

echo xxxpid> set_ftrace_pid //把你要跟踪的进程PID配置进ftrace

echo your_func > set_ftrace_filter //把你要跟踪的函数配置进ftrace

grep -i your_func available_filter_functions //可以查看上个步骤的函数有没有设置好

echo 0 > trace //把之前跟踪buffer里的数据清空

echo 1 > tracing_on //开始trace

启动你要跟踪的程序执行

echo 0 > tracing_on //关闭trace

cat trace > your_trace_result //输出得到的跟踪信息,可以将信息重定向到文件

三、参考

1. ftrace用法 - 走看看

2. https://www.csdn.net/tags/MtTaEgzsMTA4MzEzLWJsb2cO0O0O.html

3.Systemtap

一、总体介绍

使用systemtap需要安装kernel-devel和待测软件如qemu-devel

二、环境测试

stap -e 'probe begin { printf("Hello World!\n") exit() }'

hw.tap脚本测试环境,其中begin为开始执行tap脚本打印的函数,end为结束(如手动ctrl+D)时打印的函数;

[root@test111 stapscripts]# cat hw.stap

#! /bin/stap

probe begin {

    print("===Hello World===\n")

}

probe end {

    print("===GunLe===\n")

}

[root@test111 stapscripts]#

测试结果:

三、使用案例

  1. 查找qemu中函数定义:

stap -L 'process("/usr/libexec/qemu-kvm").function("qemu_vfree")'

 查找kernel函数定义

stap -l 'kernel.function("sys_recv")'

  1. 内核是怎么收包

[root@D01-303-D2-8 /home/jianchunfu]$ cat  netif_receive_skb.stap

probe kernel.function("netif_receive_skb").call

{

    printf("--------------------------------------------------------\n");

    print_backtrace();

    printf("--------------------------------------------------------\n");

}

[root@D01-303-D2-8 /home/jianchunfu]$

  1. 输出调用堆栈(谁调用了xxx,上层函数跟踪)

用户态探测点堆栈:print_ubacktrace()、sprint_ubacktrace()

内核态探测点堆栈:print_backtrace()、sprint_backtrace()

不带s和带s的区别是前者直接输出,后者是返回堆栈字符串。

[root@D01-303-D2-8 /home/jianchunfu]$ cat qemu-stack.stap

#! /bin/stap

probe process("/usr/libexec/qemu-kvm").function("qemu_vfree")

{

    printf("--------------------------------------------------------\n");

    print_ubacktrace();

    printf("--------------------------------------------------------\n");

}

[root@D01-303-D2-8 /home/jianchunfu]$

[root@D01-303-D2-8 /home/jianchunfu]$ cat qemu-cpr.stap

#! /bin/stap

probe process("/usr/libexec/qemu-kvm").function("qmp_cpr_save")

{

    printf("--------------------------------------------------------\n");

    print_ubacktrace();

    printf("--------------------------------------------------------\n");

}

[root@D01-303-D2-8 /home/jianchunfu]$

  1. 跟踪进程执行流程

thread_indent(n): 补充空格

ppfunc(): 当前探测点所在的函数

在call探测点调用thread_indent(4)补充4个空格,在return探测点调用thread_indent(-4)回退4个空格,效果如下

[root@D01-303-D2-8 /home/jianchunfu]$ cat qemu-trace.tap

#! /bin/stap

probe process("/usr/libexec/qemu-kvm").function("qemu_memalign").call

{

    printf("%s -> %s\n", thread_indent(4), ppfunc());

}

probe process("/usr/libexec/qemu-kvm").function("qemu_memalign").return

{

    printf("%s <- %s\n", thread_indent(-4), ppfunc());

}

[root@D01-303-D2-8 /home/jianchunfu]$

[root@D01-303-D2-8 /home/jianchunfu]$ cat trace.tap

probe process("/usr/libexec/qemu-kvm").function("*cpr*").call

{

        printf("%s -> %s\n", thread_indent(4), ppfunc());

}

probe process("/usr/libexec/qemu-kvm").function("*cpr*").return

{

        printf("%s <- %s\n", thread_indent(-4), ppfunc());

}

[root@D01-303-D2-8 /home/jianchunfu]$

Windows1:

 

 Windows2:

  1. 打印函数耗时

global times

probe  process("/usr/libexec/qemu-kvm").function("hmp_cpr_save").return,

       process("/usr/libexec/qemu-kvm").function("hmp_cpr_exec").return,

       process("/usr/libexec/qemu-kvm").function("hmp_cpr_load").return

{

        #耗时及耗时信息放在times 数组之中

        times[pid(), ppfunc()] += gettimeofday_us() - @entry(gettimeofday_us())

}

probe timer.s(10) { #每隔10秒打印一次

    println("========%s", execname())

    foreach([pid, pp] in times - limit 10) {

       printf("pid:%5d %50s %10ld(us)\n", pid, pp, times[pid, pp])

    }

    delete times

}

Windows1:

 Windows2:

对于进程较多脚本输出刷屏情况,可以 -x PID指定只追踪某一进程,也可以 -o file将输出重定向到某一文件,具体help指令可参见后续官方参考。

四、参考:

0.SystemTap

1. SystemTap使用指南_HanBlogs的博客-CSDN博客_systemtap tapset

2. 关于 Rocksdb 性能分析 需要知道的一些“小技巧“ -- perf_context的“内功” ,systemtap、perf、 ftrace的颜值_z_stand的博客-CSDN博客

3. SystemTap使用技巧【二】-CSDN博客

  • 0
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
使用调试工具 GDB(GNU调试器)可以帮助我们定位和解决程序中的错误。以下是使用 GDB 的一般步骤: 1. 在编译程序时,确保启用了调试信息。可以通过在编译命令中添加 `-g` 标志来实现,例如:`gcc -g main.c -o program`。 2. 在终端中运行 GDB:`gdb program`。其中,`program` 是你要调试的可执行文件。 3. 在 GDB 提示符下,输入 `run` 命令来运行程序。 4. 如果程序在运行过程中产生了错误,GDB 将会停止执行并显示错误信息。你可以使用 `backtrace` 命令查看函数调用栈,并使用 `print` 命令查看变量的值。 5. 使用 GDB 提供的命令进行调试。常用的命令包括: - `break`:设置断点,使程序在指定位置停止执行。 - `step`:逐行单步执行代码。 - `next`:逐行执行代码,但是将函数作为一个单独的步骤执行。 - `continue`:继续执行程序直到下一个断点或程序结束。 - `watch`:设置监视点,当变量的值发生变化时停止程序执行。 6. 当程序执行到错误位置时,可以使用 `print` 命令查看变量的值,或者使用 `backtrace` 命令查看函数调用栈,以帮助定位问题。 7. 如果需要进一步调试,可以使用其他 GDB 命令来检查内存、寄存器状态等。 8. 当调试完成后,可以使用 `quit` 命令退出 GDB。 这只是 GDB 的基本用法,涉及到更高级的调试技巧和使用场景时,还可以深入学习 GDB 的更多功能和命令。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值