Linux Kernel GDB tracepoint module

(KGTP)




Update in 2014-07-22



目录

关于本文 5

什么是KGTP 6

快速配置和启动KGTP 7

需要帮助或者汇报问题 8

GDB调试普通程序和KGTP的区别表 9

如何使用GDB控制KGTP跟踪和调试Linux内核 11

在普通模式直接访问当前值 11

Linux内核的内存 12

trace状态变量 13

GDB tracepoint 14

设置tracepint 15

这个函数确实存在但是设置tracepoint到上面会失败如何处理? 16

如何设置条件tracepoint 17

如何处理错误 "Unsupported operator (null) (52) in expression." 18

actions [num] 19

collect expr1, expr2, ... 20

teval expr1, expr2, ... 21

while-stepping n 22

启动和停止 tracepoint 23

Enable disable tracepoint 24

tfind选择trace帧缓存里面的条目 25

如何处理错误 "No such file or directory." 或者 "没有那个文件或目录." 26

保存trace帧信息到一个文件中 27

显示和存储tracepoint 28

删除tracepoint 29

tracepoint从内核中某点取得寄存器信息 30

tracepoint从内核中某点取得变量的值 32

显示当前这一条trace缓存里存储的所有信息 33

取得 tracepoint 的状态 34

设置trace缓存为循环缓存 35

GDB断开的时候不要停止tracepoint 36

kprobes-optimizationtracepoint的执行速度 37

如何使用trace状态变量 38

普通trace状态变量 39

Per_cpu trace状态变量 41

如何定义 42

本地CPU变量 42

CPU id变量 42

例子1 43

例子2 44

特殊trace状态变量 $current_task$current_task_pid$current_thread_info$cpu_id$dump_stack$printk_level$printk_format$printk_tmp$clock$hardirq_count$softirq_count $irq_count 45

特殊trace状态变量 $self_trace 47

$kret trace函数的结尾 48

$ignore_error $last_errno 忽略tstart的错误 49

使用 $cooked_clock $cooked_rdtsc 取得不包含KGTP运行时间的时间信息 50

使用 $xtime_sec $xtime_nsec 取得 timespec 51

如何 backtrace (stack dump) 52

通过$bt收集栈并用GDB命令backtrace进行分析 53

$_ret来取得当前函数的调用函数的栈 56

$dump_stack 输出栈分析到printk 58

如何让tracepoint直接输出信息 59

切换collect为直接输出数据 59

如何用watch tracepoint控制硬件断点记录内存访问 61

watch tracepointtrace状态变量 62

静态watch tracepoint 65

动态watch tracepoint 66

使用while-steppingLinux内核做单步 67

如何使用 while-stepping 67

while-steppingtraceframe 68

如何显示被优化掉的变量值 70

升级你的GCC 70

通过分析汇编代码取得访问被优化掉变量的方法 71

如何取得函数指针指向的函数 74

如果函数指针没有被优化掉 74

如果函数指针被优化掉了 75

/sys/kernel/debug/gtpframe和离线调试 76

如何使用 /sys/kernel/debug/gtpframe_pipe 78

GDB读帧信息 78

cat读帧信息 79

getframe读帧信息 80

使用 $pipe_trace 81

和用户程序一起使用KGTP 82

GDB为访问用户程序而连接KGTP 82

直接读用户程序的内存 83

Trace用户程序 84

tracepoint收集系统调用的从内核到用户层的的栈信息(可用来做backtrace) 86

如何使用 add-ons/hotcode.py 89

如何增加用C写的插件 90

API 91

例子 93

如何使用 94

如何使用性能计数器 95

定义一个perf event trace状态变量 96

定义一个per_cpu perf event trace状态变量 97

perf event的类型和配置 98

$p_pe_en打开和关闭一个CPU上所有的perf event 100

用来帮助设置和取得perf event trace状态变量的GDB脚本 101

附录A 使用KGTP前的准备工作 104

Linux内核 104

如果你的系统内核是自己编译的 104

如果是Android内核 105

如果你的系统内核是发行版自带的 106

Ubuntu 106

安装Linux内核调试镜像的标准方法 106

安装Linux内核调试镜像的第二方法 106

安装内核头文件包 107

安装内核源码 107

新方法 107

老方法 107

Fedora 108

安装Linux内核调试镜像 108

安装Linux内核开发包 108

其他系统 109

确定Linux内核调试镜像是正确的 110

当前Linux内核调试镜像在哪 111

使用/proc/kallsyms 112

使用linux_banner 113

处理Linux内核调试镜像地址信息和Linux内核执行时不同的问题 114

取得KGTP 115

通过http下载KGTP 115

通过git下载KGTP 116

镜像 117

配置KGTP 118

编译KGTP 119

普通编译 119

编译错误处理 120

用一些特殊选项编译KGTP 121

安装和卸载 KGTP 122

DKMS一起使用KGTP 123

使用KGTP Linux内核patch 124

安装可以和KGTP一起使用的GDB 125

附录B 如何让GDB连接KGTP 126

普通Linux 126

安装KGTP模块 126

处理找不到"/sys/kernel/debug/gtp"的问题 127

GDB连接到KGTP 128

装载Linux内核调试镜像到GDB 128

GDB在本地主机上 129

如果GDB在远程主机上 130

Android 131

安装KGTP模块 132

处理找不到"/sys/kernel/debug/gtp"的问题 133

GDB连接KGTP 134

附录C 增加模块的符号信息到GDB 135

如何使用getmod 136

如何使用getmod.py 137



关于本文

http://teawater.github.io/kgtp/kgtpcn.html HTML格式的本文最后版本。

https://raw.github.com/teawater/kgtp/master/kgtpcn.pdf PDF格式的本文最后版本。

https://raw.github.com/teawater/kgtp/release/kgtpcn.pdf PDF格式的本文最后发布版本。

什么是KGTP

KGTP是一个能在产品系统上实时分析Linux内核和应用程序(包括Android)问题的全面动态跟踪器。

使用KGTP 不需要 在Linux内核上打PATCH或者重新编译,只要编译KGTP模块并insmod就可以。

其让Linux内核提供一个远程GDB调试接口,于是在本地或者远程的主机上的GDB可以在不需要停止内核的情况下用GDB tracepoint和其他一些功能 调试跟踪 Linux内核和应用程序。

即使板子上没有GDB而且其没有可用的远程接口,KGTP也可以用离线调试的功能调试内核(见/sys/kernel/debug/gtpframe 和 离线调试)。



http://www.youtube.com/watch?v=7nfGAbNsEZY 或者 http://www.tudou.com/programs/view/fPu_koiKo38/ 是介绍KGTP英文视频。

http://www.infoq.com/cn/presentations/gdb-sharp-knife-kgtp-linux-kernel 是介绍KGTP中文视频。



KGTP支持 X86-32 X86-64 MIPS ARM

KGTP支持大部分版本的Linux内核 (2.6.18 最新版)



请到 UPDATE 去看KGTP的更新信息。



快速配置和启动KGTP

#kgtp.py将在本机上自动配置和启动KGTPGDB
#第一次使用这个脚本需要等一段时间因为有一些包需要下载。
wget https://raw.githubusercontent.com/teawater/kgtp/master/kgtp.py
sudo python kgtp.py
#访问内核的内存。
(gdb) p jiffies_64
$2 = 5081634360
#在函数vfs_read上设置trace点并收集这个函数的backtrace信息。
(gdb) trace vfs_read
Tracepoint 1 at 0xffffffff811b8c70: file fs/read_write.c, line 382.
(gdb) actions
Enter actions for tracepoint 1, one per line.
End with a line saying just "end".
>collect $bt
>end
(gdb) tstart
(gdb) tstop
(gdb) tfind
Found trace frame 0, tracepoint 1
#0 vfs_read (file=file@entry=0xffff88022017b000,
buf=buf@entry=0x7fff0fdd80f0 <Address 0x7fff0fdd80f0 out of bounds>,
count=count@entry=16, pos=pos@entry=0xffff8800626aff50) at fs/read_write.c:382
382 {
(gdb) bt
#0 vfs_read (file=file@entry=0xffff88022017b000,
buf=buf@entry=0x7fff0fdd80f0 <Address 0x7fff0fdd80f0 out of bounds>,
count=count@entry=16, pos=pos@entry=0xffff8800626aff50) at fs/read_write.c:382
#1 0xffffffff811b9819 in SYSC_read (count=16,
buf=0x7fff0fdd80f0 <Address 0x7fff0fdd80f0 out of bounds>, fd=<optimized out>)
at fs/read_write.c:506



如果要在远程主机或者和Android一起使用KGTP,请阅读附录A 使用KGTP前的准备工作附录B 如何让GDB连接KGTP

需要帮助或者汇报问题

请把问题发到https://github.com/teawater/kgtp/issues

或者写信到mailto:teawater@gmail.com?Subject=汇报一个KGTP的问题

或者汇报到QQ317654748

KGTP小组将尽全力帮助你。



请到 https://code.google.com/p/kgtp/issues/list 访问旧问题列表。

GDB调试普通程序和KGTP的区别表

这个表是给在使用过GDB调试程序的人准备的,他可以帮助你理解和记住KGTP的功能。

功能

GDB调试普通程序

GDB控制KGTP调试Linux内核

准备工作

系统里安装了GDB
程序用 "-g"选项编译。

快速配置和启动KGTP

Attach

使用命令"gdb -p pid"或者GDB命令"attach pid"可以attach系统中的某个程序.

Breakpoints

GDB命令"b place_will_stop",让程序在执行这个命令后执行,则程序将停止在设置这个断点的地方。

KGTP不支持断点但是支持tracepointTracepoints可以被看作一种特殊的断点。其可以设置在Linux kernel中的一些地方然后定义一些命令到它的action中。当tracepoint开始的时候,他们将会在内核执行到这些地方的时候执行这些命令。当tracepoint停止的时候,你可以像断点停止程序后你做的那样用GDB命令分析tracepoint得到的数据。 区别是断点会停止程序但是KGTP中的tracepoint不会。 请到 GDB tracepoint 看如何使用它。

Memory

GDB停止程序后(也许不需要),它可以用GDB命令"print"或者"x"等应用程序的内存。

你可以在tracepoint中设置特殊的action收集内存到trace frame中,在tracepoint停止后取得他们的值。collect expr1, expr2, ... tfind选择trace帧缓存里面的条目

或者你可以在内核或者应用程序执行的时候直接读他们的内存。在普通模式直接访问当前值

Step continue

GDB可以用命令"continue"继续程序的执行,用CTRL-C停止其。

KGTP不会停止Linux内核,但是tracepoint可以开始和停止。启动和停止 tracepoint

或者用 while-stepping tracepoint记录一定次数的single-stepping然后让KGTP切换到回放模式。这样其就支持执行和方向执行命令了。 使用while-steppingLinux内核做单步

Backtrace

GDB可以用命令"backtrace"打印全部调用栈。

KGTP也可以。如何 backtrace (stack dump)

Watchpoint

GDB可以用watchpoint让程序在某些内存访问发生的时候停止。

KGTP可以用watch tracepoint记录内存访问。 如何用watch tracepoint控制硬件断点记录内存访问

调用函数

GDB可以用命令"call function(xx,xx)"调用程序中的函数。

KGTP可以用插件调用内核中的函数。如何增加用C写的插件







如何使用GDB控制KGTP跟踪和调试Linux内核

在普通模式直接访问当前值

GDB连到KGTP上以后,如果没有用GDB命令"tfind"选择一条trace帧缓存里面的条目,GDB就处于 普通模式。于是你可以直接访问内存(Linux内核或者用户程序)的值和trace状态变量的值。

如果你选择了一个trace帧条目,可以用GDB命令"tfind -1"返回到普通模式。请到在普通模式直接访问当前值取得GDB命令"tfind"的更多信息。



Linux内核的内存

例如你可以用下面的命令访问"jiffies_64"

(gdb) p jiffies_64



或者你可以用下面的命令访问"static LIST_HEAD(modules)"的第一条记录:

(gdb) p *((struct module *)((char *)modules->next - ((size_t) &(((struct module *)0)->list))))

或者你可以访问"DEFINE_PER_CPU(struct device *, mce_device);"CPU0的数据:

p *(struct device *)(__per_cpu_offset[0]+(uint64_t)(&mce_device))



如果想在用一个GDB命令显示多个变量,请使用下面的例子:

(gdb) printf "%4d %4d %4d %4d %4d %4d %18d %lu\n", this_rq->cpu, this_rq->nr_running, this_rq->nr_uninterruptible, nr_active, calc_load_tasks->counter, this_rq->calc_load_active, delta, this_rq->calc_load_update
2 1 0 0 0 0 673538312 717077240



trace状态变量

你可以使用和访问内存一样的命令访问TSV。 请到 如何使用trace状态变量 取得更多TSV的信息。



GDB tracepoint

tracepoint就是GDB定义一些地址和一些动作。在tracepoint启动之后,当Linux内核执行到那些地址的时候,KGTP将执行这些动作(它们中的有些会收集数据并存入tracepoint帧缓冲)并把它们发给调试目标(KGTP)。而后,Linux内核将继续执行。

KGTP提供了一些接口可以让GDB或者其他程序取出tracepoint帧缓冲的数据做分析。

关于这些接口,文档前面已经介绍了"/sys/kernel/debug/gtp",将在后面介绍"/sys/kernel/debug/gtpframe" "/sys/kernel/debug/gtpframe_pipe"



GDB tracepoint文档在 http://sourceware.org/gdb/current/onlinedocs/gdb/Tracepoints.html



设置tracepint

trace命令非常类似break命令,它的参数可以是文件行,函数名或者一个地址。trace将定义一个或者多个地址定义一个tracepointKGTP将在这个点做一些动作。

这是一些使用trace命令的例子:

(gdb) trace foo.c:121 // 一个文件和行号

(gdb) trace +2 // 2行以后

(gdb) trace my_function // 函数的第一行

(gdb) trace *my_function // 函数的第一个地址

(gdb) trace *0x2117c4 // 一个地址



这个函数确实存在但是设置tracepoint到上面会失败如何处理?

GCC为了提高程序执行效率会inline一些static函数。因为目标文件没有inline函数的符号,所以你不能设置tracepoint在函数名上。

你可以用"trace 文件:行号"在其上设置断点。



如何设置条件tracepoint

http://sourceware.org/gdb/current/onlinedocs/gdb/Tracepoint-Conditions.html

breakpoint一样,我们可以设置tracepoint的触发条件。而且因为条件检查是在KGTP执行的,所以速度比breakpoint的条件检查快很多。

例如:

(gdb) trace handle_irq if (irq == 47)

tracepoint 1的动作将只在irq47的时候才被执行。



你还可以用GDB命令"condition"设置tracepoint的触发条件。GDB命令"condition N COND"将设置tracepoint N只有条件COND为真的时候执行。

例如:

(gdb) trace handle_irq
(gdb) condition 1 (irq == 47)

GDB命令"info tracepoint"将显示tracepointID



$bpnum的值是最后一个GDB tracepointID,所以你可以不取得tracepointID就用condition来设置最后设置的tracepoint的条件,例如:

(gdb) trace handle_irq
(gdb) condition $bpnum (irq == 47)

如何处理错误 "Unsupported operator (null) (52) in expression."

如果你使用关于字符串的条件tracepoint,你在调用"tstart"的时候可能得到这个出错信息。

你可以转化charint来处理这个问题,例如:

(gdb) p/x 'A'
$4 = 0x41
(gdb) condition 1 (buf[0] == 0x41)

actions [num]

这个命令将设置一组actiontracepoint num触发的时候执行。如果没有设置num则将设置action到最近创建的tracepoint(因此你可以定义一个tracepoint然后直接输入actions而不需要参数)。然后就要在后面输入action,最后以end为结束。到目前为止,支持的actioncollecttevalwhile-stepping



collect expr1, expr2, ...

tracepoint触发的时候,收集表达式的值。这个命令可接受用逗号分割的一组列表,这些列表除了可以是全局,局部或者本地变量,还可以是下面的这些参数:

$regs 收集全部寄存器。
$args 收集函数参数。
$locals 收集全部局部变量。

注意 collect 一个指针(collect ptr)将只能collect这个指针的地址. 在指针前面增加一个 * 将会让action collect指针指向的数据(collect *ptr)



teval expr1, expr2, ...

tracepoint触发的时候,执行指定的表达式。这个命令可接受用逗号分割的一组列表。表达式的结果将被删除,所以最主要的作用是把值设置到trace状态变量中 (see 普通trace状态变量),而不用像collect一样把这些值存到trace帧中。



while-stepping n

请到 使用while-steppingLinux内核做单步 去看如何使用它。



启动和停止 tracepoint

tracepoint只有在用下面的GDB命令启动后才可以执行action

(gdb) tstart



它可以用下面的命令停止:

(gdb) tstop



Enable disable tracepoint

breakpoint一样,tracepoint可以使用GDB命令 "enable" "disable"。但是请 注意 它们只在tracepoint停止的时候有效。



tfind选择trace帧缓存里面的条目

tracepoint停止的时候,GDB命令"tfind"可以用来选择trace帧缓存里面的条目。

GDB"tfind"模式的时候,其只能显示用collection命令收集的的存在于这个条目中的数据。所以如果打印没有collect的数据例如函数的参数的时候,GDB将输出一些错误信息。这不是bug,不用担心。

如果想选择下一个条目,可以再次使用命令"tfind"。还可以用"tfind 条目ID"去选择某个条目。

要回到普通模式(在普通模式直接访问当前值),请使用GDB命令"tfind -1"。 请到 http://sourceware.org/gdb/current/onlinedocs/gdb/tfind.html 取得它的详细信息。



如何处理错误 "No such file or directory." 或者 "没有那个文件或目录."

GDB不能找到Linux内核源码的时候,其就会显示这个错误信息。 例如:

(gdb) tfind
Found trace frame 0, tracepoint 1
#0 vfs_read (file=0xffff8801b6c3a500, buf=0x3f588b8 <Address 0x3f588b8 out of bounds>, count=8192,
pos=0xffff8801eee49f48) at /build/buildd/linux-3.2.0/fs/read_write.c:365
365 /build/buildd/linux-3.2.0/fs/read_write.c: 没有那个文件或目录.

你可以用GDB命令 "set substitute-path" 处理它。前面这个例子Linux内核源码在"/build/buildd/test/linux-3.2.0/"但是vmlinuxGDB"/build/buildd/linux-3.2.0/"找内核远啊,你可以处理他们:

(gdb) set substitute-path /build/buildd/linux-3.2.0/ /build/buildd/test/linux-3.2.0/
(gdb) tfind
Found trace frame 1, tracepoint 1
#0 vfs_read (file=0xffff8801c36e6400, buf=0x7fff51a8f110 <Address 0x7fff51a8f110 out of bounds>, count=16,
pos=0xffff8801761dff48) at /build/buildd/linux-3.2.0/fs/read_write.c:365
365 {

GDB还提供其他的命令处理源码问题,请到http://sourceware.org/gdb/current/onlinedocs/gdb/Source-Path.html取得他们的介绍。



保存trace帧信息到一个文件中

/sys/kernel/debug/gtpframe是一个当KGTP停止时的tfind格式(GDB可以读取它)的接口。

注意 有些"cp"不能很好的处理这个问题,可以用"cat /sys/kernel/debug/gtpframe > ./gtpframe"拷贝它。

你可以在需要的时候打开文件gtpframe:

(gdb) target tfile ./gtpframe
Tracepoint 1 at 0xffffffff8114f3dc: file /home/teawater/kernel/linux-2.6/fs/readdir.c, line 24.
Created tracepoint 1 for target's tracepoint 1 at 0xffffffff8114f3c0.
(gdb) tfind
Found trace frame 0, tracepoint 1
#0 vfs_readdir (file=0xffff880036e8f300, filler=0xffffffff8114f240 <filldir>, buf=0xffff880001e5bf38)
at /home/teawater/kernel/linux-2.6/fs/readdir.c:24
24 {



显示和存储tracepoint

你可以用GDB命令"info tracepoints"显示所有的tracepoint

你可以用GDB命令"save tracepoints filename"保存所有的设置tracepoint的命令到文件filename里。于是你可以在之后用GDB命令"source filename"设置重新这些tracepoint



删除tracepoint

GDB命令"delete id"将删除tracepoint id。如果"delete"没有参数,则删除所有tracepoint



tracepoint从内核中某点取得寄存器信息

下面是记录内核调用函数"vfs_readdir"时的寄存器信息的例子:

(gdb) target remote /sys/kernel/debug/gtp
(gdb) trace vfs_readdir
Tracepoint 1 at 0xc01a1ac0: file
/home/teawater/kernel/linux-2.6/fs/readdir.c, line 23.
(gdb) actions
Enter actions for tracepoint 1, one per line.
End with a line saying just "end".
>collect $reg
>end
(gdb) tstart
(gdb) shell ls
(gdb) tstop
(gdb) tfind
Found trace frame 0, tracepoint 1
#0 0xc01a1ac1 in vfs_readdir (file=0xc5528d00, filler=0xc01a1900 <filldir64>,
buf=0xc0d09f90) at /home/teawater/kernel/linux-2.6/fs/readdir.c:23
23 /home/teawater/kernel/linux-2.6/fs/readdir.c: No such file or directory.
in /home/teawater/kernel/linux-2.6/fs/readdir.c
(gdb) info reg
eax 0xc5528d00 -984445696
ecx 0xc0d09f90 -1060069488
edx 0xc01a1900 -1072031488
ebx 0xfffffff7 -9
esp 0xc0d09f8c 0xc0d09f8c
ebp 0x0 0x0
esi 0x8061480 134616192
edi 0xc5528d00 -984445696
eip 0xc01a1ac1 0xc01a1ac1 <vfs_readdir+1>
eflags 0x286 [ PF SF IF ]
cs 0x60 96
ss 0x8061480 134616192
ds 0x7b 123
es 0x7b 123
fs 0x0 0
gs 0x0 0
(gdb) tfind
Found trace frame 1, tracepoint 1
0xc01a1ac1 23 in /home/teawater/kernel/linux-2.6/fs/readdir.c
(gdb) info reg
eax 0xc5528d00 -984445696
ecx 0xc0d09f90 -1060069488
edx 0xc01a1900 -1072031488
ebx 0xfffffff7 -9
esp 0xc0d09f8c 0xc0d09f8c
ebp 0x0 0x0
esi 0x8061480 134616192
edi 0xc5528d00 -984445696
eip 0xc01a1ac1 0xc01a1ac1 <vfs_readdir+1>
eflags 0x286 [ PF SF IF ]
cs 0x60 96
ss 0x8061480 134616192
ds 0x7b 123
es 0x7b 123
fs 0x0 0
gs 0x0 0

tracepoint从内核中某点取得变量的值

下面是记录内核调用函数"vfs_readdir""jiffies_64"的值的例子:

(gdb) target remote /sys/kernel/debug/gtp
(gdb) trace vfs_readdir
Tracepoint 1 at 0xc01ed740: file /home/teawater/kernel/linux-2.6/fs/readdir.c, line 24.
(gdb) actions
Enter actions for tracepoint 1, one per line.
End with a line saying just "end".
>collect jiffies_64
>collect file->f_path.dentry->d_iname
>end
(gdb) tstart
(gdb) shell ls
arch drivers include kernel mm Module.symvers security System.map virt
block firmware init lib modules.builtin net sound t vmlinux
crypto fs ipc Makefile modules.order scripts source usr vmlinux.o
(gdb) tstop
(gdb) tfind
Found trace frame 0, tracepoint 1
#0 0xc01ed741 in vfs_readdir (file=0xf4063000, filler=0xc01ed580 <filldir64>, buf=0xd6dfdf90)
at /home/teawater/kernel/linux-2.6/fs/readdir.c:24
24 {
(gdb) p jiffies_64
$1 = 4297248706
(gdb) p file->f_path.dentry->d_iname
$1 = "b26", '\000' <repeats 28 times>

显示当前这一条trace缓存里存储的所有信息

在用"tfind"选择好一个条目后,你可以用"tdump"

(gdb) tdump
Data collected at tracepoint 1, trace frame 0:
$cr = void
file->f_path.dentry->d_iname = "gtp\000.google.chrome.g05ZYO\000\235\337\000\000\000\000\200\067k\364\200\067", <incomplete sequence \364>
jiffies_64 = 4319751455

取得 tracepoint 的状态

请用GDB命令"tstatus"



设置trace缓存为循环缓存

http://sourceware.org/gdb/current/onlinedocs/gdb/Starting-and-Stopping-Trace-Experiments.html

帧缓存默认情况下不是循环缓存。当缓存满了的时候,tracepoint将停止。

下面的命令将设置trace缓存为循环缓存,当缓存满了的时候,其将自动删除最早的数据并继续trace

(gdb) set circular-trace-buffer on



GDB断开的时候不要停止tracepoint

http://sourceware.org/gdb/current/onlinedocs/gdb/Starting-and-Stopping-Trace-Experiments.html

默认情况下,当GDB断开KGTP的时候将自动停止tracepoint并删除trace帧。

下面的命令将打开KGTP disconnect-trace。在设置之后,当GDB断开KGTP的时候,KGTP将不停止tracepointGDB重新连到KGTP的时候,其可以继续控制KGTP

(gdb) set disconnected-tracing on



kprobes-optimizationtracepoint的执行速度

因为tracepoint是和Linux内核一起执行,所以它的速度将影响到系统执行的速度。

KGTP tracepoint是基于Linux内核kprobe。因为普通kprobe是基于断点指令,所以它的速度不是很快。



但是如果你的archX86_64 或者 X86_32 而且内核配置没有打开"Preemptible Kernel" (PREEMPT)kprobe的速度将被kprobes-optimization (CONFIG_OPTPROBES)提高很多。

可以用下面的命令来确认:

sysctl -A | grep kprobe
debug.kprobes-optimization = 1

这个的意思就是你的系统支持kprobes-optimization

注意 一些KGTP的功能会导致tracepoint只能使用普通kprobe即使系统支持kprobes-optimization。文档将在介绍这些功能的时候增加提醒,如果你很介意tracepoint的速度就请避免使用这些功能。



如何使用trace状态变量

http://sourceware.org/gdb/current/onlinedocs/gdb/Trace-State-Variables.html

trace状态变量简称TSV

TSV可以在tracepoint actioncondition中被访问,并且可以直接被GDB命令访问。

注意 GDB 7.2.1和更晚的版本直接访问trace状态变量,比他们老的GDB只能通过命令"info tvariables"取得trace状态变量的值。



普通trace状态变量

定义trace状态变量$c.

(gdb) tvariable $c



trace状态变量 $c 被创建并初始化0。 下面的action将使用$c记录内核里发生了多少次IRQ

(gdb) target remote /sys/kernel/debug/gtp
(gdb) trace handle_irq
(gdb) actions
Enter actions for tracepoint 3, one per line.
End with a line saying just "end".
>collect $c #Save current value of $c to the trace frame buffer.
>teval $c=$c+1 #Increase the $c.
>end

你还可以将某个变量的值传到状态变量里,但是别忘记转化这个值为"uint64_t"

>teval $c=(uint64_t)a



你可以取得$c的值当trace在运行或者停止的时候。

(gdb) tstart
(gdb) info tvariables
$c 0 31554
(gdb) p $c
$5 = 33652
(gdb) tstop
(gdb) p $c
$9 = 105559



当使用tfind的时候,你可以分析trace frame buffer。如果trace状态变量被收集了,你可以把它取出来。

(gdb) tstop
(gdb) tfind
(gdb) info tvariables
$c 0 0
(gdb) p $c
$6 = 0
(gdb) tfind 100
(gdb) p $c
$7 = 100

需要的时候,访问trace状态变量的tracepoint action将自动加锁,所以其可以很好的处理trace状态变量的竞态条件问题。

下面这个例子即使在一个多CPU的环境也可以正常使用。

>teval $c=$c+1

Per_cpu trace状态变量

Per_cpu trace状态变量是一种特殊的trace状态变量。 当一个tracepoint action访问到其的时候,其将自动访问这个CPUPer_cpu trace状态变量。

它有两个优点:

1. 访问Per_cpu trace状态变量的tracepoint actions不存在竞态条件问题,所以其不需要对trace状态变量加锁。所以其在多核的机器上速度更快。

2. 写针对记录某个CPUtracepoint actions比普通trace状态变量更容易。



如何定义

Per_cpu trace状态变量有两种类型:

本地CPU变量
"per_cpu_"+string

或者

"p_"+string

例如:

(gdb) tvariable $p_count

tracepoint action中访问这个trace状态变量的时候,其将返回这个变量在这个action运行的CPU上的值。

CPU id变量
"per_cpu_"+string+CPU_id

或者

"p_"+string+CPU_id

例如:

(gdb) tvariable $p_count0
(gdb) tvariable $p_count1
(gdb) tvariable $p_count2
(gdb) tvariable $p_count3

tracepoint action或者GDB命令行中访问这个变量的时候,其将返回这个变量在CPU CPI_id 上的值。

下面这个例子可以自动这个这台主机上的每个CPU定义CPU id变量。(请 注意 用这些命令之前需要让GDB连上KGTP)

(gdb) set $tmp=0
(gdb) while $tmp<$cpu_number
>eval "tvariable $p_count%d",$tmp
>set $tmp=$tmp+1
>end

例子1

这个例子定义了一个记录每个CPU调用多少次vfs_readtracepoint

tvariable $p_count
set $tmp=0
while $tmp<$cpu_number
eval "tvariable $p_count%d",$tmp
set $tmp=$tmp+1
end
trace vfs_read
actions
teval $p_count=$p_count+1
end

于是你可以在"tstart"后显示每个CPU调用了多少次vfs_read

(gdb) p $p_count0
$3 = 44802
(gdb) p $p_count1
$4 = 55272
(gdb) p $p_count2
$5 = 102085
(gdb) p $p_count3

例子2

这个例子记录了每个CPU上关闭IRQ时间最长的函数的stack dump

set pagination off

tvariable $bt=1024
tvariable $p_count
tvariable $p_cc
set $tmp=0
while $tmp<$cpu_number
eval "tvariable $p_cc%d",$tmp
set $tmp=$tmp+1
end

tvariable $ignore_error=1

trace arch_local_irq_disable
actions
teval $p_count=$clock
end
trace arch_local_irq_enable if ($p_count && $p_cc < $clock - $p_count)
actions
teval $p_cc = $clock - $p_count
collect $bt
collect $p_cc
teval $p_count=0
end

enable
set pagination on

特殊trace状态变量 $current_task$current_task_pid$current_thread_info$cpu_id$dump_stack$printk_level$printk_format$printk_tmp$clock$hardirq_count$softirq_count $irq_count

KGTP特殊trace状态变量$current_task$current_thread_info$cpu_id $clock可以很容易的访问各种特殊的值,当你用GDB连到KGTP后就可以访问到他们。 你可以在tracepoint条件和actions里使用他们。

tracepoint条件和actions里访问$current_task可以取得get_current()的返回值。

tracepoint条件和actions里访问$current_task_pid可以取得get_current()->pid的值。

tracepoint条件和actions里访问$current_thread_info可以取得current_thread_info()的返回值。

tracepoint条件和actions里访问$cpu_id可以取得smp_processor_id()的返回值。

tracepoint条件和actions里访问$clock可以取得local_clock()的返回值,也就是取得纳秒为单位的时间戳。

$rdtsc只在体系结构是X86或者X86_64的时候访问的到,任何时候访问它可以取得用指令RDTSC取得的TSC的值。

tracepoint条件和actions里访问$hardirq_count可以取得hardirq_count()的返回值。

tracepoint条件和actions里访问$softirq_count可以取得softirq_count()的返回值。

tracepoint条件和actions里访问$irq_count可以取得irq_count()的返回值。



KGTP还有一些特殊trace状态变量$dump_stack$printk_level$printk_format $printk_tmp。他们可以用来直接显示值,请看如何让tracepoint直接输出信息



下面是一个用$c记录进程16663调用多少次vfs_read并收集thread_info结构的例子:

(gdb) target remote /sys/kernel/debug/gtp
(gdb) trace vfs_read if (((struct task_struct *)$current_task)->pid == 16663)
(gdb) tvariable $c
(gdb) actions
Enter actions for tracepoint 4, one per line.
End with a line saying just "end".
>teval $c=$c+1
>collect (*(struct thread_info *)$current_thread_info)
>end
(gdb) tstart
(gdb) info tvariables
Name Initial Current
$c 0 184
$current_task 0 <unknown>
$current_thread_info 0 <unknown>
$cpu_id 0 <unknown>
(gdb) tstop
(gdb) tfind
(gdb) p *(struct thread_info *)$current_thread_info
$10 = {task = 0xf0ac6580, exec_domain = 0xc07b1400, flags = 0, status = 0, cpu = 1, preempt_count = 2, addr_limit = {
seg = 4294967295}, restart_block = {fn = 0xc0159fb0 <do_no_restart_syscall>, {{arg0 = 138300720, arg1 = 11,
arg2 = 1, arg3 = 78}, futex = {uaddr = 0x83e4d30, val = 11, flags = 1, bitset = 78, time = 977063750,
uaddr2 = 0x0}, nanosleep = {index = 138300720, rmtp = 0xb, expires = 335007449089}, poll = {
ufds = 0x83e4d30, nfds = 11, has_timeout = 1, tv_sec = 78, tv_nsec = 977063750}}},
sysenter_return = 0xb77ce424, previous_esp = 0, supervisor_stack = 0xef340044 "", uaccess_err = 0}

这是一个记录每个CPU调用了多少次sys_read()的例子。

(gdb) tvariable $c0
(gdb) tvariable $c1
(gdb) trace sys_read
(gdb) condition $bpnum ($cpu_id == 0)
(gdb) actions
>teval $c0=$c0+1
>end
(gdb) trace sys_read
(gdb) condition $bpnum ($cpu_id == 1)
(gdb) actions
>teval $c1=$c1+1
>end
(gdb) info tvariables
Name Initial Current
$current_task 0 <unknown>
$cpu_id 0 <unknown>
$c0 0 3255
$c1 0 1904

sys_read() CPU0上被执行了3255次,CPU1上执行了1904次。请 注意 这个例子只是为了显示如何使用$cpu_id,实际上用per_cpu trace状态变量写更好。



特殊trace状态变量 $self_trace

$self_trace和前面介绍的特殊trace状态变量不同,它是用来控制tracepoint的行为的。

默认情况下,tracepoint被触发后,如果current_taskKGTP自己的进程(GDBnetcatgetframe或者其他访问KGTP接口的进程)的时候,其将不执行任何actions

如果你想让tracepoint actions和任何task的时候都执行,请包含一个包含一个访问到$self_trace的命令到actions中,也就是说增加下面的命令到actions中:

>teval $self_trace=0



$kret trace函数的结尾

有时,因为内核是用优化编译的,所以在函数结尾设置tracepoint有时很困难。这时你可以用$kret帮助你。

$kret是一个类似$self_trace的特殊trace状态变量。当你在tracepoint action里设置它的值的时候,这个tracepoint将用kretprobe而不是kprobe注册。于是其就可以trace一个函数的结尾。

注意 这个tracepoint 必须用 "function_name" 的格式设置在函数的第一个地址上



下面的部分是一个例子:

#"*(function_name)" format can make certain that GDB send the first address of function to KGTP.
(gdb) trace *vfs_read
(gdb) actions
>teval $kret=0
#Following part you can set commands that you want.



$ignore_error $last_errno 忽略tstart的错误

KGTPtstart取得错误,这个命令将失败。

但有时我们需要忽略这个错误信息并让KGTP工作。例如:如果你在inline函数spin_lock设置tracepoint,这个tracepoint将被设置到很多地址上,有一些地址不能设置kprobe,于是它就会让tstart出错。这时你就可以用"$ignore_error"忽略这些错误。

最后一个错误信息将存在"$last_errno"中。



(gdb) tvariable $ignore_error=1

这个命令将打开忽略。



(gdb) tvariable $ignore_error=0

这个命令将关闭忽略。



使用 $cooked_clock $cooked_rdtsc 取得不包含KGTP运行时间的时间信息

访问这两个trace状态变量可以取得不包含KGTP运行时间的时间信息,于是我们可以取得一段代码更真实的执行时间即使这个tracepointaction比较复杂。



使用 $xtime_sec $xtime_nsec 取得 timespec

访问trace状态变量将返回用getnstimeofday取得的timespec时间信息。

$xtime_sec 将返回timespec秒的部分。

$xtime_nsec 将返回timespec纳秒的部分。



如何 backtrace (stack dump)

每次你的程序做一个函数调用的时候, 这次调用的信息就会生成。这些信息包括调用函数的地址,调用参数,局部变量的值。这些信息被存储在我们称为栈帧的地方,栈帧是从调用栈中分配而来。



通过$bt收集栈并用GDB命令backtrace进行分析

因为这个方法更快(只在trace时候收集栈帧信息)而且可以分析出大部分的调用栈中的信息(前面介绍的栈信息都可以分析出来),所以时间你使用这个方法做栈分析。

首先我们需要在tracepoint action中增加命令收集栈。

GDB收集栈的通常命令是: 在x86_32, 下面的命令将收集512字节的栈内容。

>collect *(unsigned char *)$esp@512

x86_64, 下面的命令将收集512字节的栈内容。

>collect *(unsigned char *)$rsp@512

MIPS或者ARM, 下面的命令将收集512字节的栈内容。

>collect *(unsigned char *)$sp@512

这些命令很难记,而且不同的体系结构需要不同的命令。

KGTP有一个特殊trace状态变量$bt。如果tracepoint action访问到它,KGTP将自动收集$bt长度(默认值是512)的栈。下面这个action将收集512字节的栈内存:

>collect $bt

如果你想改变$bt的值,你可以在"tstart"使用下面这个GDB命令:

(gdb) tvariable $bt=1024



下面的部分是一个收集栈并用GDB进行分析的例子:

(gdb) target remote /sys/kernel/debug/gtp
(gdb) trace vfs_readdir
Tracepoint 1 at 0xffffffff8118c300: file /home/teawater/kernel2/linux/fs/readdir.c, line 24.
(gdb) actions
Enter actions for tracepoint 1, one per line.
End with a line saying just "end".
>collect $bt
>end
(gdb) tstart
(gdb) shell ls
1 crypto fs include kernel mm Module.symvers security System.map vmlinux
arch drivers hotcode.html init lib modules.builtin net sound usr vmlinux.o
block firmware hotcode.html~ ipc Makefile modules.order scripts source virt
(gdb) tstop
(gdb) tfind
Found trace frame 0, tracepoint 1
#0 vfs_readdir (file=0xffff8800c5556d00, filler=0xffffffff8118c4b0 <filldir>, buf=0xffff880108709f40)
at /home/teawater/kernel2/linux/fs/readdir.c:24
24 {
(gdb) bt
#0 vfs_readdir (file=0xffff8800c5556d00, filler=0xffffffff8118c4b0 <filldir>, buf=0xffff880108709f40)
at /home/teawater/kernel2/linux/fs/readdir.c:24
#1 0xffffffff8118c689 in sys_getdents (fd=<optimized out>, dirent=0x1398c58, count=32768) at /home/teawater/kernel2/linux/fs/readdir.c:214
#2 <signal handler called>
#3 0x00007f00253848a5 in ?? ()
#4 0x00003efd32cddfc9 in ?? ()
#5 0x00002c15b7d04101 in ?? ()
#6 0x000019c0c5704bf1 in ?? ()
#7 0x0000000900000000 in ?? ()
#8 0x000009988cc8d269 in ?? ()
#9 0x000009988cc9b8d1 in ?? ()
#10 0x0000000000000000 in ?? ()
(gdb) up
#1 0xffffffff8118c689 in sys_getdents (fd=<optimized out>, dirent=0x1398c58, count=32768) at /home/teawater/kernel2/linux/fs/readdir.c:214
214 error = vfs_readdir(file, filldir, &buf);
(gdb) p buf
$1 = {current_dir = 0x1398c58, previous = 0x0, count = 32768, error = 0}
(gdb) p error
$3 = -9
(gdb) frame 0
#0 vfs_readdir (file=0xffff8800c5556d00, filler=0xffffffff8118c4b0 <filldir>, buf=0xffff880108709f40)
at /home/teawater/kernel2/linux/fs/readdir.c:24
24 {

从这个例子,我们可以看到一些分析调用栈的GDB命令:

你还可以看到当你用updown或者frame来选择调用栈帧的时候,你可以输出不同帧的参数和局部变量。

要取得更多关于如何使用GDB分析调用栈的信息,请到http://sourceware.org/gdb/current/onlinedocs/gdb/Stack.html



$_ret来取得当前函数的调用函数的栈

如果你只想取得当前函数的调用函数的栈,可以用$_ret

注意 使用$_rettracepoint不能设置在函数的第一个地址上。

例如:

(gdb) list vfs_read
360 }
361
362 EXPORT_SYMBOL(do_sync_read);
363
364 ssize_t vfs_read(struct file *file, char __user *buf, size_t count, loff_t *pos)
365 {
366 ssize_t ret;
367
368 if (!(file->f_mode & FMODE_READ))
369 return -EBADF;
(gdb) trace 368
Tracepoint 2 at 0xffffffff8117a244: file /home/teawater/kernel2/linux/fs/read_write.c, line 368.
(gdb) actions
Enter actions for tracepoint 2, one per line.
End with a line saying just "end".
>collect $_ret
>end
(gdb) tstart
(gdb) tstop
(gdb) tfind
Found trace frame 0, tracepoint 2
#0 vfs_read (file=0xffff880141c46000, buf=0x359bda0 <Address 0x359bda0 out of bounds>, count=8192, pos=0xffff88012fa49f48)
at /home/teawater/kernel2/linux/fs/read_write.c:368
368 if (!(file->f_mode & FMODE_READ))
(gdb) bt
#0 vfs_read (file=0xffff880141c46000, buf=0x359bda0 <Address 0x359bda0 out of bounds>, count=8192, pos=0xffff88012fa49f48)
at /home/teawater/kernel2/linux/fs/read_write.c:368
#1 0xffffffff8117a3ea in sys_read (fd=<optimized out>, buf=<unavailable>, count=<unavailable>)
at /home/teawater/kernel2/linux/fs/read_write.c:469
Backtrace stopped: not enough registers or memory available to unwind further
(gdb) up
#1 0xffffffff8117a3ea in sys_read (fd=<optimized out>, buf=<unavailable>, count=<unavailable>)
at /home/teawater/kernel2/linux/fs/read_write.c:469
469 ret = vfs_read(file, buf, count, &pos);
(gdb) p ret
$2 = -9

我们可以看到调用vfs_read的函数是sys_read,函数sys_read的局部变量ret的值是-9



$dump_stack 输出栈分析到printk

因为这个方法需要在trace的时候分析栈并调用printk,所以它比较慢,不安全,不清晰也不能访问调用栈中的很多内容,所以我建议你使用上一部分介绍的方法

KGTP有一个特殊的trace状态变量$dump_stack,收集这个变量可以令GDB调用栈分析并用printk输出。 下面是一个让内核输出vfs_readdir栈分析的例子:

target remote /sys/kernel/debug/gtp
trace vfs_readdir
commands
collect $dump_stack
end

于是你的内核就会printk这样的信息:

[22779.208064] gtp 1:Pid: 441, comm: python Not tainted 2.6.39-rc3+ #46
[22779.208068] Call Trace:
[22779.208072] [<fe653cca>] gtp_get_var+0x4a/0xa0 [gtp]
[22779.208076] [<fe653d79>] gtp_collect_var+0x59/0xa0 [gtp]
[22779.208080] [<fe655974>] gtp_action_x+0x1bb4/0x1dc0 [gtp]
[22779.208084] [<c05b6408>] ? _raw_spin_unlock+0x18/0x40
[22779.208088] [<c023f152>] ? __find_get_block_slow+0xd2/0x160
[22779.208091] [<c01a8c56>] ? delayacct_end+0x96/0xb0
[22779.208100] [<c023f404>] ? __find_get_block+0x84/0x1d0
[22779.208103] [<c05b6408>] ? _raw_spin_unlock+0x18/0x40
[22779.208106] [<c02e0838>] ? find_revoke_record+0xa8/0xc0
[22779.208109] [<c02e0c45>] ? jbd2_journal_cancel_revoke+0xd5/0xe0
[22779.208112] [<c02db51f>] ? __jbd2_journal_temp_unlink_buffer+0x2f/0x110
[22779.208115] [<fe655c4c>] gtp_kp_pre_handler+0xcc/0x1c0 [gtp]
[22779.208118] [<c05b8a88>] kprobe_exceptions_notify+0x3d8/0x440
[22779.208121] [<c05b7d54>] ? hw_breakpoint_exceptions_notify+0x14/0x180
[22779.208124] [<c05b95eb>] ? sub_preempt_count+0x7b/0xb0
[22779.208126] [<c0227ac5>] ? vfs_readdir+0x15/0xb0
[22779.208128] [<c0227ac4>] ? vfs_readdir+0x14/0xb0
[22779.208131] [<c05b9743>] notifier_call_chain+0x43/0x60
[22779.208134] [<c05b9798>] __atomic_notifier_call_chain+0x38/0x50
[22779.208137] [<c05b97cf>] atomic_notifier_call_chain+0x1f/0x30
[22779.208140] [<c05b980d>] notify_die+0x2d/0x30
[22779.208142] [<c05b71c5>] do_int3+0x35/0xa0

如何让tracepoint直接输出信息

在前面的章节,你可以看到如果想取得Linux内核的信息,你需要用tracepoint "collect" action来保存信息到tracepoint帧中并用GDB tfind命来来分析这些数据帧。

但是有时我们希望直接取得这些数据,所以KGTP提供了一种直接取得这些数据的方法。

切换collect为直接输出数据

KGTP有特殊trace状态变量$printk_level$printk_format $printk_tmp支持这个功能。

$printk_level,如果这个值是8(这是默认值),"collect" action将是普通行为也就是保存数据到tracepoint帧中。

如果值是0-7"collect" 将以这个数字为printk级别输出信息,这些级别是:

0 KERN_EMERG system is unusable
1 KERN_ALERT action must be taken immediately
2 KERN_CRIT critical conditions
3 KERN_ERR error conditions
4 KERN_WARNING warning conditions
5 KERN_NOTICE normal but significant condition
6 KERN_INFO informational
7 KERN_DEBUG debug-level messages

$printk_formatcollect printk将按照这里设置的格式进行输出。 这些格式是:

0 这是默认值。
如果collect的长度是1248则其将输出一个无符号十进制数。
如果不是,则其将输出十六进制字串。
1 输出值是有符号十进制数。
2 输出值是无符号十进制数。
3 输出值是无符号十六进制数。
4 输出值是字符串。
5 输出值是十六进制字串。

如果要输出一个全局变量,需要将其先设置到$printk_tmp中。



下面是一个显示调用vfs_readdir时的计数,pidjiffies_64和文件名的例子:

(gdb) target remote /sys/kernel/debug/gtp
(gdb) tvariable $c
(gdb) trace vfs_readdir
(gdb) actions
>teval $printk_level=0
>collect $c=$c+1
>collect ((struct task_struct *)$current_task)->pid
>collect $printk_tmp=jiffies_64
>teval $printk_format=4
>collect file->f_path.dentry->d_iname
>end

于是内核将printk这些信息:

gtp 1:$c=$c+1=41
gtp 1:((struct task_struct *)$current_task)->pid=12085
gtp 1:$printk_tmp=jiffies_64=4322021438
gtp 1:file->f_path.dentry->d_iname=b26
gtp 1:$c=$c+1=42
gtp 1:((struct task_struct *)$current_task)->pid=12085
gtp 1:$printk_tmp=jiffies_64=4322021438
gtp 1:file->f_path.dentry->d_iname=b26

"gtp 1" 的意思是数据是tracepoint 1输出的。



如何用watch tracepoint控制硬件断点记录内存访问

Watch tracepoint 可以通过设置一些特殊的trace状态变量设置硬件断点来记录内存访问。

注意 watch tracepoint现在只有X86X86_64支持。而且因为Linux 2.6.26和更老版本有一些IPI的问题,只有Linux 2.6.27和更新版本上可以正常使用动态watch tracepoint



watch tracepointtrace状态变量

名称

普通tracepoint

普通tracepoint

静态static tracepoint

静态static tracepoint

动态static tracepoint

动态static tracepoint

$watch_static

不支持

不支持

如果"teval $watch_static=1"则这个tracepoint是静态watch tracepoint

不支持

如果"teval $watch_static=0"则这个tracepoint是动态watch tracepoint

不支持

$watch_set_id

当这个tracepoint要设置一个动态watch tracepoint的时候,设置动态watch tracepointID$watch_set_id来标明你要设置哪个动态watch tracepoint

不支持

不支持

不支持

不支持

不支持

$watch_set_addr

当这个tracepoint要设置一个动态watch tracepoint的时候,设置动态watch tracepoint的地址到$watch_set_addr来标明你要设置哪个动态watch tracepoint

不支持

不支持

不支持

不支持

不支持

$watch_type

当这个tracepoint要设置一个动态watch tracepoint的时候,设置watch类型到$watch_type
0
是执行。 1是写。 2是读或者写。

取得这个tracepoint设置到$watch_type里的值。

设置watch tracepoint的类型。

取得这个watch tracepoint的类型。

设置watch tracepoint的默认类型。

取得这个watch tracepoint在实际执行中的类型。

$watch_size

当这个tracepoint要设置一个动态watch tracepoint的时候,设置watch长度到$watch_size
长度是1, 2, 4, 8

取得这个tracepoint设置到$watch_size里的值。

设置watch tracepoint的长度。

取得这个watch tracepoint的长度。

设置watch tracepoint的默认长度。

取得这个watch tracepoint在实际执行中的长度。

$watch_start

设置地址到动态watch tracepoint($watch_set_addr或者$watch_set_id设置)中并让其开始工作。

取得这次开始的返回值。
(其可能会失败因为X86只有4个硬件断点)
取得0则成功,小于0则是错误ID

不支持

不支持

不支持

不支持

$watch_stop

设置地址到$watch_stop将让一个watch这个地址的动态watch tracepoint停止。

取得这次停止的返回值。

不支持

不支持

不支持

不支持

$watch_trace_num

不支持

不支持

不支持

不支持

不支持

设置这个动态watch tracepointtracepoint的号码。

$watch_trace_addr

不支持

不支持

不支持

不支持

不支持

设置这个动态watch tracepointtracepoint的地址。

$watch_addr

不支持

不支持

不支持

这个watch tracepoint监视的地址。

不支持

这个watch tracepoint监视的地址。

$watch_val

不支持

不支持

不支持

这个watch tracepoint监视的内存的当前值。

不支持

这个watch tracepoint监视的内存的当前值。

$watch_prev_val

不支持

不支持

不支持

这个watch tracepoint监视的内存的修改前值。

不支持

这个watch tracepoint监视的内存的修改前值。

$watch_count

不支持

不支持

不支持

不支持

不支持

这个watch tracepoint会话的一个特殊计数ID





静态watch tracepoint

当你要监视全局变量或者可以取得地址的变量的值的时候,你可以使用静态watch tracepoint。下面是一个监视jiffies_64写的例子:

#静态watch tracepointtracepoint的地址中取得要监视的地址
trace *&jiffies_64
actions
#Set this watch tracepoint to static
teval $watch_static=1
#Watch memory write
teval $watch_type=1
teval $watch_size=8
collect $watch_val
collect $watch_prev_val
collect $bt
end

动态watch tracepoint

当你要监视局部变量或者只能在函数中取得地址的变量的值的时候,你可以使用动态watch tracepoint。下面是一个监视函数function get_empty_filpf->f_posf->f_op写的例子:

trace *1
commands
teval $watch_static=0
teval $watch_type=1
teval $watch_size=8
collect $bt
collect $watch_addr
collect $watch_val
collect $watch_prev_val
end

定义了一个动态watch tracepoint。地址"1"并不是其要监视的地址。其将帮助tracepoint来找到这个动态watch tracepoint



list get_empty_filp
trace 133
commands
teval $watch_set_addr=1
teval $watch_size=4
teval $watch_start=&(f->f_pos)
teval $watch_size=8
teval $watch_start=&(f->f_op)
end

在函数get_empty_filp中定义一个普通tracepoint,其将开始监视f->f_posf->f_op



trace file_sb_list_del
commands
teval $watch_stop=&(file->f_pos)
teval $watch_stop=&(file->f_op)
end

在函数file_sb_list_del中定义一个普通tracepoint,其将停止监视file->f_posfile->f_op



使用while-steppingLinux内核做单步

请 注意 while-stepping现在只有X86X86_64支持。

介绍使用while-stepping的视频 http://www.codepark.us/a/12

如何使用 while-stepping

while-stepping 是一种可以包含actions的特殊tracepoint action

当一个actions中包含了“while-stepping n”tracepoint执行的时候,其将做n次单步并执行while-steppingactions。例如:

trace vfs_read
#因为单步会影响系统速度,所以最好用passcount或者condition限制tracepoint的执行次数。
passcount 1
actions
collect $bt
collect $step_count
#2000次单步。
while-stepping 2000
#下面这部分是"while-stepping 2000"actions
#因为单步可能会执行到其他函数,所以最好不要访问局部变量。
collect $bt
collect $step_count
end
end

注意 tracepoint在执行单步的时候会关闭当前CPU的中断。 在actions中访问 $step_count 将得到从1开始的这步的计数。



while-steppingtraceframe

不同step的数据将会被记录到不同的traceframe中,你可以用tfind (tfind选择trace帧缓存里面的条目) 选择他们。

或者你可以将KGTP切换到回放模式,这样GDB可以用执行和反向执行命令选择一个while-stepping tracepointtraceframe。例如:



tfind选择一个while-steppingtraceframe

(gdb) tfind
Found trace frame 0, tracepoint 1
#0 vfs_read (file=0xffff8801f7bd4c00, buf=0x7fff74e4edb0 <Address 0x7fff74e4edb0 out of bounds>, count=16,
pos=0xffff8801f4b45f48) at /build/buildd/linux-3.2.0/fs/read_write.c:365
365 {



下面的命令将切换KGTP到回放模式。

(gdb) monitor replay
(gdb) tfind -1
No longer looking at any trace frame
#0 vfs_read (file=0xffff8801f7bd4c00, buf=0x7fff74e4edb0 <Address 0x7fff74e4edb0 out of bounds>, count=16,
pos=0xffff8801f4b45f48) at /build/buildd/linux-3.2.0/fs/read_write.c:365
365 {



于是可以使用执行命令。

(gdb) n
368 if (!(file->f_mode & FMODE_READ))
(gdb) p file->f_mode
$5 = 3



设置断点 (只在回放模式下有效,不会影响到Linux内核执行)

(gdb) b 375
Breakpoint 2 at 0xffffffff81179b75: file /build/buildd/linux-3.2.0/fs/read_write.c, line 375.
(gdb) c
Continuing.

Breakpoint 2, vfs_read (file=0xffff8801f7bd4c00, buf=0x7fff74e4edb0 <Address 0x7fff74e4edb0 out of bounds>, count=16,
pos=0xffff8801f4b45f48) at /build/buildd/linux-3.2.0/fs/read_write.c:375
375 ret = rw_verify_area(READ, file, pos, count);
(gdb) s
rw_verify_area (read_write=0, file=0xffff8801f7bd4c00, ppos=0xffff8801f4b45f48, count=16)
at /build/buildd/linux-3.2.0/fs/read_write.c:300
300 inode = file->f_path.dentry->d_inode;

使用反向执行命令。

(gdb) rs

Breakpoint 2, vfs_read (file=0xffff8801f7bd4c00, buf=0x7fff74e4edb0 <Address 0x7fff74e4edb0 out of bounds>, count=16,
pos=0xffff8801f4b45f48) at /build/buildd/linux-3.2.0/fs/read_write.c:375
375 ret = rw_verify_area(READ, file, pos, count);
(gdb) rn
372 if (unlikely(!access_ok(VERIFY_WRITE, buf, count)))

GDB命令tstarttstoptfind或者quit可以自动关闭回放模式。



如何显示被优化掉的变量值

有时GDB会这样输出信息:

inode has been optimized out of existence.
res has been optimized out of existence.

这是因为inoderes的值被优化掉了。内核用-O2编译的所以你有时会碰到这个问题。

有两个方法处理这个问题:

升级你的GCC

VTA branch http://gcc.gnu.org/wiki/Var_Tracking_Assignments 已经整合进GCC 4.5,其可以帮助生成之前被标记为"optimized out"的值的调试信息。



通过分析汇编代码取得访问被优化掉变量的方法

即使升级了GCC,你可能还会遇到问题。主要原因是数据在寄存器中但是GCC没有把信息放到调试信息中。所以GDB只能显示这个变量被又优化掉了。

但你可以通过分析汇编代码取得这个变量在哪并在tracepoint actions中访问其。

下面是一个在函数get_empty_filp中寻找变量"f"并在tracepoint actions中使用其的例子:

我们想collect变量f的值,但是其已经被优化掉了。

(gdb) list get_empty_filp
...
...
...
137 INIT_LIST_HEAD(&f->f_u.fu_list);
138 atomic_long_set(&f->f_count, 1);
139 rwlock_init(&f->f_owner.lock);
140 spin_lock_init(&f->f_lock);
141 eventpoll_init_file(f);
(gdb)
142 /* f->f_version: 0 */
143 return f;
(gdb) trace 143
Tracepoint 1 at 0xffffffff8119b30e: file fs/file_table.c, line 143.
(gdb) actions
Enter actions for tracepoint 1, one per line.
End with a line saying just "end".
>collect f
`f' is optimized away and cannot be collected.

现在用"disassemble /m"命令取得和"f"有关的汇编代码和源码并分析他们。

(gdb) disassemble /m get_empty_filp
...
...
...
125 f = kmem_cache_zalloc(filp_cachep, GFP_KERNEL);
126 if (unlikely(!f))
0xffffffff8119b28c <+92>: test %rax,%rax
0xffffffff8119b292 <+98>: je 0xffffffff8119b362 <get_empty_filp+306>

127 return ERR_PTR(-ENOMEM);
0xffffffff8119b362 <+306>: mov $0xfffffffffffffff4,%rax
0xffffffff8119b369 <+313>: jmp 0xffffffff8119b311 <get_empty_filp+225>

因为"+98""+132"的代码因为属于inline函数所以没有在这里显示,但是你可以用"disassemble get_empty_filp"取得他们。

0xffffffff8119b287 <+87>: callq 0xffffffff81181cb0 <kmem_cache_alloc>
0xffffffff8119b28c <+92>: test %rax,%rax
0xffffffff8119b28f <+95>: mov %rax,%rbx
0xffffffff8119b292 <+98>: je 0xffffffff8119b362 <get_empty_filp+306>
0xffffffff8119b298 <+104>: mov 0xb4d406(%rip),%edx # 0xffffffff81ce86a4 <percpu_counter_batch>
0xffffffff8119b29e <+110>: mov $0x1,%esi
0xffffffff8119b2a3 <+115>: mov $0xffffffff81c05340,%rdi
---Type <return> to continue, or q <return> to quit---
0xffffffff8119b2aa <+122>: callq 0xffffffff8130dd20 <__percpu_counter_add>

根据汇编代码你可以看到kmem_cache_alloc的返回值在$rax中,其的值被设置到了$rbx中。

看起来$rbx"f"的值,让我们看其他的汇编代码。



128
129 percpu_counter_inc(&nr_files);
130 f->f_cred = get_cred(cred);
0xffffffff8119b2b4 <+132>: mov %r12,0x70(%rbx)

设置一个值到f的元素中。汇编代码是设置$r12的值到以$rbx为基础地址的内存中。其让$rbx看起来是"f"



131 error = security_file_alloc(f);
0xffffffff8119b2b8 <+136>: mov %rbx,%rdi
0xffffffff8119b2bb <+139>: callq 0xffffffff8128ee30 <security_file_alloc>

132 if (unlikely(error)) {
0xffffffff8119b2c0 <+144>: test %eax,%eax
0xffffffff8119b2c2 <+146>: jne 0xffffffff8119b36b <get_empty_filp+315>
---Type <return> to continue, or q <return> to quit---

133 file_free(f);
134 return ERR_PTR(error);
0xffffffff8119b393 <+355>: movslq -0x14(%rbp),%rax
0xffffffff8119b397 <+359>: jmpq 0xffffffff8119b311 <get_empty_filp+225>

135 }
136
137 INIT_LIST_HEAD(&f->f_u.fu_list);
138 atomic_long_set(&f->f_count, 1);
139 rwlock_init(&f->f_owner.lock);
0xffffffff8119b2e4 <+180>: movl $0x100000,0x50(%rbx)

140 spin_lock_init(&f->f_lock);
0xffffffff8119b2c8 <+152>: xor %eax,%eax
0xffffffff8119b2d1 <+161>: mov %ax,0x30(%rbx)

141 eventpoll_init_file(f);
142 /* f->f_version: 0 */
143 return f;
0xffffffff8119b30e <+222>: mov %rbx,%rax

在检查了其他汇编代码后,你可以确定$rbx就是"f"



于是你可以在tracepoint actions中通过访问$rbx而访问"f",例如:

(gdb) trace 143
Tracepoint 1 at 0xffffffff8119b30e: file fs/file_table.c, line 143.
(gdb) actions
Enter actions for tracepoint 1, one per line.
End with a line saying just "end".
#collect f
>collect $rbx
#collect *f
>collect *((struct file *)$rbx)
#collect f->f_op
>collect ((struct file *)$rbx)->f_op
>end

如何取得函数指针指向的函数

如果函数指针没有被优化掉

你可以直接collect这个指针,例如:

377 count = ret;
378 if (file->f_op->read)
379 ret = file->f_op->read(file, buf, count, pos);
(gdb)
(gdb) trace 379
Tracepoint 1 at 0xffffffff81173ba5: file /home/teawater/kernel/linux/fs/read_write.c, line 379.
(gdb) actions
Enter actions for tracepoint 1, one per line.
End with a line saying just "end".
>collect file->f_op->read
>end
(gdb) tstart
(gdb) tstop
(gdb) tfind
(gdb) p file->f_op->read
$5 = (ssize_t (*)(struct file *, char *, size_t, loff_t *)) 0xffffffff81173190 <do_sync_read>
#于是就知道file->f_op->read指向do_sync_read

如果函数指针被优化掉了

可以用tracepoint step处理这个问题,例如:

#找到调用指针的指令
(gdb) disassemble /rm vfs_read
379 ret = file->f_op->read(file, buf, count, pos);
0xffffffff81173ba5 <+181>: 48 89 da mov %rbx,%rdx
0xffffffff81173ba8 <+184>: 4c 89 e9 mov %r13,%rcx
0xffffffff81173bab <+187>: 4c 89 e6 mov %r12,%rsi
0xffffffff81173bae <+190>: 4c 89 f7 mov %r14,%rdi
0xffffffff81173bb1 <+193>: ff d0 callq *%rax
0xffffffff81173bb3 <+195>: 48 89 c3 mov %rax,%rbx
(gdb) trace *0xffffffff81173bb1
Tracepoint 1 at 0xffffffff81173bb1: file /home/teawater/kernel/linux/fs/read_write.c, line 379.
(gdb) actions
Enter actions for tracepoint 1, one per line.
End with a line saying just "end".
>while-stepping 1
>collect $reg
>end
>end
(gdb) tstart
(gdb) tstop
(gdb) tfind
#0 tty_read (file=0xffff88006ca74900, buf=0xb6b7dc <Address 0xb6b7dc out of bounds>, count=8176,
ppos=0xffff88006e197f48) at /home/teawater/kernel/linux/drivers/tty/tty_io.c:960
960 {
#于是就知道file->f_op->read指向tty_read

注意 while-stepping 将让tracepoint不能使用kprobes-optimization



/sys/kernel/debug/gtpframe和离线调试

/sys/kernel/debug/gtpframe是一个当KGTP停止时的tfind格式(GDB可以读取它)的接口。



在运行GDB的主机上:

改变 "target remote XXXX" 为:

(gdb) target remote | perl ./getgtprsp.pl



之后像平时一样设置tracepoint

(gdb) trace vfs_readdir
Tracepoint 1 at 0xffffffff8114f3c0: file /home/teawater/kernel/linux-2.6/fs/readdir.c, line 24.
(gdb) actions
Enter actions for tracepoint 1, one per line.
End with a line saying just "end".
#If your GDB support tracepoint "printf" (see "Howto use tracepoint printf"), use it to show the value directly is better.
>collect $reg
>end
(gdb) tstart
(gdb) stop
(gdb) quit

于是你可以在当前目录找到文件gtpstartgtpstop,把他们拷贝到你想调试的主机上。



在被调试主机上,先拷贝KGTP目录中的程序"putgtprsp""gtp.ko"到这台机器上。insmod gtp.ko之后:

启动tracepoint

./putgtprsp ./gtpstart

停止tracepoint

./putgtprsp ./gtpstop



可以按照如何让tracepoint直接输出信息直接在板子上显示信息。



如果要保存trace帧之后再分析,你可以拷贝文件"/sys/kernel/debug/gtpframe"到有GDB的主机上。

注意 有些"cp"不能很好的处理这个问题,可以用"cat /sys/kernel/debug/gtpframe > ./gtpframe"拷贝它。

在运行GDB的主机上:

(gdb) target tfile ./gtpframe
Tracepoint 1 at 0xffffffff8114f3dc: file /home/teawater/kernel/linux-2.6/fs/readdir.c, line 24.
Created tracepoint 1 for target's tracepoint 1 at 0xffffffff8114f3c0.
(gdb) tfind
Found trace frame 0, tracepoint 1
#0 vfs_readdir (file=0xffff880036e8f300, filler=0xffffffff8114f240 <filldir>, buf=0xffff880001e5bf38)
at /home/teawater/kernel/linux-2.6/fs/readdir.c:24
24 {

注意 如果你想在使用离线调试后从远程主机上的GDB连接KGTP,你需要在调用"nc"之前"rmmod gtp""insmod gtp.ko"



如何使用 /sys/kernel/debug/gtpframe_pipe

这个接口提供和"gtpframe"同样的数据,但是可以在KGTP tracepoint运行的时候也可以使用。在数据读出之后,其将自动从trace帧里删除类似ftrace "trace_pipe"

GDB读帧信息

#连接到接口上
(gdb) target tfile /sys/kernel/debug/gtpframe_pipe
#取得一个trace帧条目
(gdb) tfind 0
Found trace frame 0, tracepoint 1
#取得下一个
(gdb) tfind
Target failed to find requested trace frame.
(gdb) tfind 0
Found trace frame 0, tracepoint 1

这个方法和python一起分析内核比较好,add-ons/hotcode.py就是这样的例子。



cat读帧信息

sudo cat /sys/kernel/debug/gtpframe_pipe > g

于是所有帧信息都被存入了文件"g"



getframe读帧信息

KGTP包含一个"getframe"可以用来帮助取得trace帧。

下面这里是它的帮助:

getframe -h
Get the trace frame of KGTP and save them in current
directory with tfile format.
Usage: ./getframe [option]

-g n Set the minimum free size limit to n G.
When free size of current disk is smaller than n G,
./getframe will exit (-q) or wait some seconds (-w).
The default value of it is 2 G.

-q Quit when current disk is smaller than
minimum free size limit (-g).

-w n Wait n seconds when current disk is smaller
than minimum free size limit (-g).

-e n Set the entry number of each tfile to n.
The default value of it is 1000.

-h Display this information.

使用 $pipe_trace

为了锁安全,KGTP默认将自动忽略读/sys/kernel/debug/gtpframe_pipe的任务。

如果你真希望trace这个任务而且确定这是安全的,你可以使用"tstart"之前使用下面的命令:

(gdb) tvariable $pipe_trace=1

于是KGTP将不再忽略读/sys/kernel/debug/gtpframe_pipe的任务。



和用户程序一起使用KGTP

KGTP可以在不停止用户程序的情况下,访问内存和trace这个应用层程序。

GDB为访问用户程序而连接KGTP

1) 在 不装载 任何应用程序的情况下打开GDB

2) 如果用户程序在本机运行,则使用GDB命令 "target extended-remote /sys/kernel/debug/gtp" 连接KGTP。 如果用户程序运行在远程主机上,则使用类似 如果GDB在远程主机上 的方法但是需要将 "target remote" 替换为 "target extended-remote"

3) GDB命令 "file" 装载用户程序 (其必须在编译时候增加GCC参数"-g"保证其有调试信息)

4) GDB命令 "attach pid" attachtaskpid



因此让GDB为访问用户程序而连接KGTP的步骤将是:

sudo gdb-release
(gdb) target extended-remote /sys/kernel/debug/gtp
Remote debugging using /sys/kernel/debug/gtp
0x00000000 in ?? ()
(gdb) file a.out
A program is being debugged already.
Are you sure you want to change the file? (y or n) y
Reading symbols from /home/teawater/kernel/kgtp/a.out...done.
(gdb) attach 15412
A program is being debugged already. Kill it? (y or n) y
Attaching to program: /home/teawater/kernel/kgtp/a.out, Remote target
# 一些版本的GDB可能会出现internal-error,请回答 "n" 来忽略他们。

直接读用户程序的内存

GDB attach用户程序成功后,你可以用GDB命令"p""x"访问到这个task的内存。你可以用GDB命令"help p""help x"取得这两个命令的帮助。例如:

(gdb) p c
$19 = 4460
(gdb) p &c
$21 = (int *) 0x601048 <c>
(gdb) x 0x601048
0x601048 <c>: 0x00001181

Trace用户程序

KGTPLinux内核功能 uprobes trace用户程序,只有Linux内核3.9或者更新版本支持这个功能。

大部分Linux发行版所使用的内核(3.9或者更新版本)的编译选项都打开了 uprobes

如果是自己编译的内核:

Kernel hacking --->
[*] Tracers --->
[*] Enable uprobes-based dynamic events

如果当前Linux内核的 uprobes 是打开的,可以在attach上用户程序后根据 GDB tracepoint 设置tracepoint。例如:

(gdb) trace 14
Tracepoint 1 at 0x400662: file /home/teawater/kernel/kgtp-misc/test.c, line 14.
(gdb) actions
Enter actions for tracepoint 1, one per line.
End with a line saying just "end".
>collect $bt
>collect c
>end
(gdb) tstart
(gdb) tstatus
Trace is running on the target.
Collected 5 trace frames.
Trace buffer has 20824428 bytes of 20828160 bytes free (0% full).
Trace will stop if GDB disconnects.
Not looking at any trace frame.
(gdb) tstop
(gdb) tfind
Found trace frame 0, tracepoint 1
#0 main (argc=1, argv=0x7fff5e878368, envp=0x7fff5e878378) at /home/teawater/kernel/kgtp-misc/test.c:14
14 c += 1;
(gdb) bt
#0 main (argc=1, argv=0x7fff5e878368, envp=0x7fff5e878378) at /home/teawater/kernel/kgtp-misc/test.c:14
(gdb) p c
$7 = 36

注意即使你只attach了一个task,用户层tracepoint会在这个用户程序的所有task上上触发。(我认为这是 uprobes 的一个很有趣的特色,所以我没在KGTP tracepoint中对其进行限制。)



你可以在tracepoint conditions中增加对$current_task_pid的检查来让tracepoint只在某task上被触发。下面的例子就是一个设置只在task 985上触发tracepoint上的例子:

(gdb) trace 14
Tracepoint 1 at 0x400662: file /home/teawater/kernel/kgtp-misc/test.c, line 14.
(gdb) condition $bpnum ($current_task_pid == 985)



同时你还可以在tracepoint actions中增加"collect $current_task_pid"来确定哪个task触发了这个tracepoint。例如:

(gdb) trace 14
Tracepoint 2 at 0x400662: file /home/teawater/kernel/kgtp-misc/test.c, line 14.
(gdb) actions
Enter actions for tracepoint 2, one per line.
End with a line saying just "end".
>collect $current_task_pid
>collect c
>end
(gdb) tstart
(gdb) tstatus
Trace is running on the target.
Collected 6 trace frames.
Trace buffer has 20827776 bytes of 20828160 bytes free (0% full).
Trace will stop if GDB disconnects.
Not looking at any trace frame.
(gdb) tstop
(gdb) tfind
Found trace frame 0, tracepoint 2
#0 main (argc=<unavailable>, argv=<unavailable>, envp=<unavailable>) at /home/teawater/kernel/kgtp-misc/test.c:14
14 c += 1;
(gdb) p $current_task_pid
$2 = 9983
(gdb) tfind
Found trace frame 1, tracepoint 2
14 c += 1;
(gdb) p $current_task_pid
$3 = 9982
(gdb)

tracepoint收集系统调用的从内核到用户层的的栈信息(可用来做backtrace)

$current 是一个特殊trace状态变量。当一个tracepointaction 访问其的时候,tracepoint将收集当前task的寄存器和内存值而不是内核中的值。

一般来说,tracepoint通过 task_pt_regs 取得寄存器的值。于是在tracepoint actionscollect $current 将让tracepoint访问当前task。例如:

(gdb) actions
Enter actions for tracepoint 1, one per line.
End with a line saying just "end".
>collect $current
>collect $bt
>end

此外,针对一些参数中包含指向当前TASK寄存器指针的特殊函数(例如:X86do_IRQ函数)tracepoint需要从函数的参数中取得寄存器信息。则设置指针到 $current 将让tracepoint得到其。例如:

(gdb) actions
Enter actions for tracepoint 1, one per line.
End with a line saying just "end".
>teval $current=(uint64_t)regs
>collect $bt
>end

$current_task_user 是一个特殊trace状态变量。当current task user模式的时候,其的值为真。

用这两个trace状态变量,就可以用KGTP收集用户程序的栈信息(可用来做backtrace)

下面这个例子显示如何从用户层到Linux内核层做backtrace(stack dump)

#连接KGTP(和上一节介绍的方法相同)
(gdb) target extended-remote /sys/kernel/debug/gtp
#设置一个收集进程18776的用户栈的tracepoint
(gdb) trace vfs_read
Tracepoint 1 at 0xffffffff8117a3d0: file /home/teawater/kernel/linux/fs/read_write.c, line 365.
(gdb) condition 1 ($current_task_user && $current_task_pid == 18776)
(gdb) actions
Enter actions for tracepoint 1, one per line.
End with a line saying just "end".
>collect $current
>collect $bt
>end
#Setup a tracepoint that collect kernel space stack of task 18776.
#设置一个收集进程18776的内核栈的tracepoint
(gdb) trace vfs_read
Note: breakpoint 1 also set at pc 0xffffffff8117a3d0.
Tracepoint 2 at 0xffffffff8117a3d0: file /home/teawater/kernel/linux/fs/read_write.c, line 365.
(gdb) condition 2 ($current_task_user && $current_task_pid == 18776)
(gdb) actions
Enter actions for tracepoint 2, one per line.
End with a line saying just "end".
>collect $bt
>end
(gdb) tstart
(gdb) tstop
#下面这部分和上一节相同,增加一个新的inferior用来分析应用程序的信息。
(gdb) add-inferior
Added inferior 2
(gdb) inferior 2
[Switching to inferior 2 [<null>] (<noexec>)]
(gdb) file gdb
Reading symbols from /usr/local/bin/gdb...done.
(gdb) attach 18776
#tracepoint 1 收集了用户层的栈信息。
(gdb) tfind
Found trace frame 0, tracepoint 1
#0 0x00007f77331d7d0f in __read_nocancel () from /lib/x86_64-linux-gnu/libpthread.so.0
#这是程序18776用户层的backtrace
(gdb) bt
#0 0x00007f77331d7d0f in __read_nocancel () from /lib/x86_64-linux-gnu/libpthread.so.0
#1 0x000000000078e145 in rl_callback_read_char () at ../../src/readline/callback.c:201
#2 0x000000000069de79 in rl_callback_read_char_wrapper (client_data=<optimized out>) at ../../src/gdb/event-top.c:169
#3 0x000000000069ccf8 in process_event () at ../../src/gdb/event-loop.c:401
#4 process_event () at ../../src/gdb/event-loop.c:351
#5 0x000000000069d448 in gdb_do_one_event () at ../../src/gdb/event-loop.c:465
#6 0x000000000069d5d5 in start_event_loop () at ../../src/gdb/event-loop.c:490
#7 0x0000000000697083 in captured_command_loop (data=<optimized out>) at ../../src/gdb/main.c:226
#8 0x0000000000695d8b in catch_errors (func=0x697070 <captured_command_loop>, func_args=0x0, errstring=0x14df99e "",
mask=6) at ../../src/gdb/exceptions.c:546
#9 0x00000000006979e6 in captured_main (data=<optimized out>) at ../../src/gdb/main.c:1001
#10 0x0000000000695d8b in catch_errors (func=0x697360 <captured_main>,
func@entry=<error reading variable: PC not available>, func_args=0x7fff08afd5b0,
func_args@entry=<error reading variable: PC not available>, errstring=<unavailable>,
errstring@entry=<error reading variable: PC not available>, mask=<unavailable>,
mask@entry=<error reading variable: PC not available>) at ../../src/gdb/exceptions.c:546
#11 <unavailable> in ?? ()
Backtrace stopped: not enough registers or memory available to unwind further
#tracepoint 2收集了内核空间的栈,所以要切换回inferior 1装载内核调试信息。
(gdb) tfind
Found trace frame 1, tracepoint 2
#0 0xffffffff8117a3d0 in ?? ()
(gdb) inferior 1
[Switching to inferior 1 [Remote target] (/home/teawater/kernel/b/vmlinux)]
[Switching to thread 1 (Remote target)]
#0 vfs_read (file=0xffff88021a559500, buf=0x7fff08afd31f <Address 0x7fff08afd31f out of bounds>, count=1,
pos=0xffff8800c47e1f48) at /home/teawater/kernel/linux/fs/read_write.c:365
365 {
#这是内核栈的backtrace
(gdb) bt
#0 vfs_read (file=0xffff88021a559500, buf=0x7fff08afd31f <Address 0x7fff08afd31f out of bounds>, count=1,
pos=0xffff8800c47e1f48) at /home/teawater/kernel/linux/fs/read_write.c:365
#1 0xffffffff8117a59a in sys_read (fd=<optimized out>, buf=0x7fff08afd31f <Address 0x7fff08afd31f out of bounds>,
count=1) at /home/teawater/kernel/linux/fs/read_write.c:469
#2 <signal handler called>
#3 0x00007f77331d7d10 in ?? ()
#4 0x0000000000000000 in ?? ()

如何使用 add-ons/hotcode.py

这个脚本可以通过记录并分析中断处理时候的取得的PC值从而得到Linux kernel或者用户层程序的热点代码。

请到 http://code.google.com/p/kgtp/wiki/hotcode 去看如何使用它。



如何增加用C写的插件

KGTP支持用C写的插件,插件将被编译成LKM



API

#include "gtp.h"

这是插件需要的包含API的头文件。



extern int gtp_plugin_mod_register(struct module *mod);
extern int gtp_plugin_mod_unregister(struct module *mod);

这两个函数注册和注销插件模块。这样KGTP就可以在访问插件模块资源的时候增加其的引用计数了。



extern struct gtp_var *gtp_plugin_var_add(char *name, int64_t val,
struct gtp_var_hooks *hooks);

这个函数会增加特殊trace状态变量到KGTP



struct gtp_var_hooks {
int (*gdb_set_val)(struct gtp_trace_s *unused, struct gtp_var *var,
int64_t val);
int (*gdb_get_val)(struct gtp_trace_s *unused, struct gtp_var *var,
int64_t *val);
int (*agent_set_val)(struct gtp_trace_s *gts, struct gtp_var *var,
int64_t val);
int (*agent_get_val)(struct gtp_trace_s *gts, struct gtp_var *var,
int64_t *val);
};



extern int gtp_plugin_var_del(struct gtp_var *var);

rmmod插件模块的时候,用这个函数删除gtp_plugin_var_add增加的TSV



例子

KGTP目录里的plugin_example.cKGTP plugin的例子,可以用"make P=1"直接编译其。其将增加四个TSVKGTP中。





如何使用

注意 KGTP支持加入多个插件。



如何使用性能计数器

性能计数器是大部分现代CPU都有的特殊硬件寄存器。这些寄存器对一些硬件事件进行计数:例如指令执行数量,cachemisses数量,分支预测失败数,而且这些计数不会让应用程序或者内核变慢。其还可以设置到达一定的值的时候发生中断,这些就可以用来分析在某CPU上执行程序的性能。

Linux性能计数器子系统perf event可以用来取得性能计数器的值。你可以用KGTP perf event trace状态变量访问这些值。

请读内核目录里的tools/perf/design.txt文件取得perf event的更多信息。



定义一个perf event trace状态变量

访问一个性能计数器需要定义下面的trace状态变量:

"pe_cpu_"+tv_name 定义性能计数器的CPU ID
"pe_type_"+tv_name 定义性能计数器的类型。
"pe_config_"+tv_name 定义性能计数器的配置。
"pe_en_"+tv_name 定义性能计数器的启动开关。
默认情况下性能计数器是关闭的。
"pe_val_"+tv_name 访问这个变量能取得性能计数器的值。

定义一个per_cpu perf event trace状态变量

定义一个per_cpu perf event trace状态变量和Per_cpu trace状态变量一样。

"p_pe_"+perf_event type+string+CPU_id

注意 如果定义一个per_cpu perf event trace状态变量,就不需要在定义cpu id("pe_cpu")因为KGTP已经取得了CPUID



perf event的类型和配置

类型可以是:

0 PERF_TYPE_HARDWARE
1 PERF_TYPE_SOFTWARE
2 PERF_TYPE_TRACEPOINT
3 PERF_TYPE_HW_CACHE
4 PERF_TYPE_RAW
5 PERF_TYPE_BREAKPOINT

如果类型是0(PERF_TYPE_HARDWARE),配置可以是:

0 PERF_COUNT_HW_CPU_CYCLES
1 PERF_COUNT_HW_INSTRUCTIONS
2 PERF_COUNT_HW_CACHE_REFERENCES
3 PERF_COUNT_HW_CACHE_MISSES
4 PERF_COUNT_HW_BRANCH_INSTRUCTIONS
5 PERF_COUNT_HW_BRANCH_MISSES
6 PERF_COUNT_HW_BUS_CYCLES
7 PERF_COUNT_HW_STALLED_CYCLES_FRONTEND
8 PERF_COUNT_HW_STALLED_CYCLES_BACKEND



如果类型是3(PERF_TYPE_HW_CACHE),配置要分为3部分: 第一部分是cache id,其在设置进配置的时候需要 << 0

0 PERF_COUNT_HW_CACHE_L1D
1 PERF_COUNT_HW_CACHE_L1I
2 PERF_COUNT_HW_CACHE_LL
3 PERF_COUNT_HW_CACHE_DTLB
4 PERF_COUNT_HW_CACHE_ITLB
5 PERF_COUNT_HW_CACHE_BPU

第二部分是cache op id,其在设置进配置的时候需要 << 8

0 PERF_COUNT_HW_CACHE_OP_READ
1 PERF_COUNT_HW_CACHE_OP_WRITE
2 PERF_COUNT_HW_CACHE_OP_PREFETCH



第三部分是cache op result id,其在设置进配置的时候需要 << 16

0 PERF_COUNT_HW_CACHE_RESULT_ACCESS
1 PERF_COUNT_HW_CACHE_RESULT_MISS



如果你想取得PERF_COUNT_HW_CACHE_L1I(1), PERF_COUNT_HW_CACHE_OP_WRITE(1) and PERF_COUNT_HW_CACHE_RESULT_MISS(1)你需要使用:



(gdb) tvariable $pe_config_cache=1 | (1 << 8) | (1 << 16)

内核目录中的tools/perf/design.txt是关于perf event的类型和配置。



$p_pe_en打开和关闭一个CPU上所有的perf event

我认为取得一段代码的性能计数器信息比较好的办法是在函数开头打开计数器在函数结束的时候关闭计数器。你可以用"pe_en"设置他们,但是如果你有多个perf event trace状态变量的时候,这样会让tracepoint action很大。$p_pe_en就是处理这种问题的。 你可以打开所有perf event trace状态变量在当前CPU上用下面的action

>teval $p_pe_en=1



设置$p_pe_en0来关闭他们。

>teval $p_pe_en=0



用来帮助设置和取得perf event trace状态变量的GDB脚本

下面这个GDB脚本定义了2个命令dpespe来帮助定义和显示perf event trace状态变量。

你可以把他们存在~/.gdbinit或者你自己的tracepoint脚本中。于是你就可以在GDB中直接使用这2个命令。

define dpe
if ($argc < 2)
printf "Usage: dpe pe_type pe_config [enable]\n"
end
if ($argc >= 2)
eval "tvariable $p_pe_val_%d%d_c",$arg0, $arg1
eval "tvariable $p_pe_en_%d%d_c",$arg0, $arg1
set $tmp=0
while $tmp<$cpu_number
eval "tvariable $p_pe_type_%d%d_c%d=%d",$arg0, $arg1, $tmp, $arg0
eval "tvariable $p_pe_config_%d%d_c%d=%d",$arg0, $arg1, $tmp, $arg1
eval "tvariable $p_pe_val_%d%d_c%d=0",$arg0, $arg1, $tmp
if ($argc >= 3)
eval "tvariable $p_pe_en_%d%d_c%d=%d",$arg0, $arg1, $tmp, $arg2
end
set $tmp=$tmp+1
end
end
end

document dpe
Usage: dpe pe_type pe_config [enable]
end

define spe
if ($argc != 2 && $argc != 3)
printf "Usage: spe pe_type pe_config [cpu_id]\n"
end
if ($argc == 2)
set $tmp=0
while $tmp<$cpu_number
eval "printf \"$p_pe_val_%%d%%d_c%%d=%%ld\\n\",$arg0, $arg1, $tmp, $p_pe_val_%d%d_c%d", $arg0, $arg1, $tmp
set $tmp=$tmp+1
end
end
if ($argc == 3)
eval "printf \"$p_pe_val_%%d%%d_c%%d=%%ld\\n\",$arg0, $arg1, $tmp, $p_pe_val_%d%d_c%d", $arg0, $arg1, $arg2
end
end

document spe
Usage: spe pe_type pe_config [cpu_id]
end

下面是一个取得函数tcp_v4_rcv性能计数器的例子:

#连接KGTP
(gdb) target remote /sys/kernel/debug/gtp
#定义3perf event trace状态变量PERF_COUNT_HW_CPU_CYCLESPERF_COUNT_HW_CACHE_MISSESPERF_COUNT_HW_BRANCH_MISSES
(gdb) dpe 0 0
(gdb) dpe 0 3
(gdb) dpe 0 5
#在函数开头打开这个CPU的性能寄存器
(gdb) trace tcp_v4_rcv
(gdb) action
>teval $p_pe_en=1
>end
#$kret 让我们可以处理到函数tcp_v4_rcv的结尾:
(gdb) trace *(tcp_v4_rcv)
(gdb) action
>teval $kret=0
#关闭这个CPU上的所有性能计数器
>teval $p_pe_en=0
#访问这些perf event trace状态变量将取得他们的值
>collect $p_pe_val_00_0
>collect $p_pe_val_03_0
>collect $p_pe_val_05_0
#设置这些perf event trace状态变量为0
>teval $p_pe_val_00_0=0
>teval $p_pe_val_03_0=0
>teval $p_pe_val_05_0=0
>end
tstart
#等一会让每个CPU收一些TCP
(gdb) tstop
(gdb) tfind
(gdb) spe 0 0 $cpu_id
$p_pe_val_00_2=12676
(gdb) spe 0 3 $cpu_id
$p_pe_val_03_2=7
(gdb) spe 0 5 $cpu_id
$p_pe_val_05_2=97

附录A 使用KGTP前的准备工作

Linux内核

如果你的系统内核是自己编译的

要使用KGTP,你需要打开下面这些内核选项:

General setup --->
[*] Kprobes

[*] Enable loadable module support --->

Kernel hacking --->
[*] Debug Filesystem
[*] Compile the kernel with debug info

如果你改了Linux内核config的任何项目,请重新编译你的内核。



如果是Android内核

默认的Android Linux内核config应该不支持KGTP。要使用KGTP,你需要打开下面这些内核选项:

[*] Enable loadable module support --->
General setup --->
[*] Prompt for development and/or incomplete code/drivers
[*] Kprobes
Kernel hacking --->
[*] Debug Filesystem
[*] Compile the kernel with debug info

如果你改了Linux内核config的任何项目,请重新编译你的内核。



如果你的系统内核是发行版自带的

你需要安装一些Linux内核软件包。

Ubuntu

安装Linux内核调试镜像的标准方法

1) 增加调试源到Ubuntu源列表。

echo "deb http://ddebs.ubuntu.com $(lsb_release -cs) main restricted universe multiverse" | \
sudo tee -a /etc/apt/sources.list.d/ddebs.list
echo "deb http://ddebs.ubuntu.com $(lsb_release -cs)-updates main restricted universe multiverse
deb http://ddebs.ubuntu.com $(lsb_release -cs)-security main restricted universe multiverse
deb http://ddebs.ubuntu.com $(lsb_release -cs)-proposed main restricted universe multiverse" | \
sudo tee -a /etc/apt/sources.list.d/ddebs.list
sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 428D7C01
运行:
sudo apt-get update



2) 安装Linux内核调试镜像

sudo apt-get install linux-image-$(uname -r)-dbgsym

于是你可以在"/usr/lib/debug/boot/vmlinux-$(uname -r)"找到内核调试镜像。

请 注意 当内核更新的时候这一步 安装Linux内核调试镜像 需要再做一次。

安装Linux内核调试镜像的第二方法

如果用标准方法出现问题,请用下面这些命令安装Linux内核调试镜像。

wget http://ddebs.ubuntu.com/pool/main/l/linux/linux-image-$(uname -r)-dbgsym_$(dpkg -s linux-image-$(uname -r) | grep ^Version: | sed 's/Version: //')_$(uname -i | sed 's/x86_64/amd64/').ddeb
sudo dpkg -i linux-image-$(uname -r)-dbgsym_$(dpkg -s linux-image-$(uname -r) | grep ^Version: | sed 's/Version: //')_$(uname -i | sed 's/x86_64/amd64/').ddeb

注意 当内核更新的时候这个方法需要重新做一次。



安装内核头文件包
sudo apt-get install linux-headers-generic
安装内核源码
新方法

安装需要的软件包:

sudo apt-get install dpkg-dev



取得源码:

apt-get source linux-image-$(uname -r)

则在当前目录找到内核源码目录。

移动这个目录到"/build/buildd/"中。

老方法
sudo apt-get install linux-source
sudo mkdir -p /build/buildd/
sudo tar vxjf /usr/src/linux-source-$(uname -r | sed 's/-.*//').tar.bz2 -C /build/buildd/
sudo rm -rf /build/buildd/linux-$(uname -r | sed 's/-.*//')
sudo mv /build/buildd/linux-source-$(uname -r | sed 's/-.*//') /build/buildd/linux-$(uname -r | sed 's/-.*//')

注意 当内核更新的时候这一步 安装内核源码 需要再做一次。



Fedora

安装Linux内核调试镜像

使用下面的命令:

sudo debuginfo-install kernel



或者:

sudo yum --enablerepo=fedora-debuginfo install kernel-debuginfo

于是你可以在"/usr/lib/debug/lib/modules/$(uname -r)/vmlinux"找到内核调试镜像。

安装Linux内核开发包
sudo yum install kernel-devel-$(uname -r)

请 注意 在升级过内核包之后,你可能需要重新调用这个命令。



其他系统

需要安装Linux内核调试镜像和Linux内核源码包。



确定Linux内核调试镜像是正确的

因为GDBLinux内核调试镜像里取得地址信息和调试信息,所以使用正确的Linux内核调试镜像是非常重要的。所以在使用KGTP前,请先做检查。

2个方法进行检查,我建议2个方法都做一次来确保Linux内核调试镜像是正确的。



注意 如果你确定使用了正确的Linux内核调试镜像但是不能通过这个两个方法。请看 处理Linux内核调试镜像地址信息和Linux内核执行时不同的问题



当前Linux内核调试镜像在哪

UBUNTU中,你可以在"/usr/lib/debug/boot/vmlinux-$(uname -r)"找到它。

Fedora中,你可以在"/usr/lib/debug/lib/modules/$(uname -r)/vmlinux"找到它。

如果你自己编译的内核,内核编译目录中的文件“vmlinux”是调试镜像。



使用/proc/kallsyms

在运行着要trace的内核的系统上,用下面的命令取得sys_readsys_write的地址:

sudo cat /proc/kallsyms | grep sys_read
ffffffff8117a520 T sys_read
sudo cat /proc/kallsyms | grep sys_write
ffffffff8117a5b0 T sys_write

于是我们就可以得到sys_read的地址是0xffffffff8117a520sys_write的地址是0xffffffff8117a5b0



之后我们用GDBLinux内核调试镜像中取得sys_readsys_write的地址:

gdb ./vmlinux
(gdb) p sys_read
$1 = {long int (unsigned int, char *, size_t)} 0xffffffff8117a520 <sys_read>
(gdb) p sys_write
$2 = {long int (unsigned int, const char *, size_t)} 0xffffffff8117a5b0 <sys_write>

sys_readsys_write的地址一样,所以Linux内核调试镜像是正确的。



使用linux_banner

sudo gdb ./vmlinux
(gdb) p linux_banner
$1 = "Linux version 3.4.0-rc4+ (teawater@teawater-Precision-M4600) (gcc version 4.6.3 (GCC) ) #3 SMP Tue Apr 24 13:29:05 CST 2012\n"

linux_bannerLinux内核调试镜像里的内核信息。



之后,根据 GDB连接到KGTP 里的方法连接到KGTP上并再次打印linux_banner

(gdb) target remote /sys/kernel/debug/gtp
Remote debugging using /sys/kernel/debug/gtp
0x0000000000000000 in irq_stack_union ()
(gdb) p linux_banner
$2 = "Linux version 3.4.0-rc4+ (teawater@teawater-Precision-M4600) (gcc version 4.6.3 (GCC) ) #3 SMP Tue Apr 24 13:29:05 CST 2012\n"

这个linux_bannerKGTP正在trace的内核的内核信息,如果相同,则Linux内核调试镜像是正确的。



处理Linux内核调试镜像地址信息和Linux内核执行时不同的问题

X86_32上,用 确定Linux内核调试镜像是正确的 介绍的方法发现Linux内核调试镜像地址信息和Linux内核执行时不同,而且确定使用的Linux内核调试镜像是正确的。

这个问题是因为:

Processor type and features --->
(0x1000000) Physical address where the kernel is loaded
(0x100000) Alignment value to which kernel should be aligned

这个两个参数的值不同。请 注意 "Physical address where the kernel is loaded" 有时不会在配置的时候显示,你可以通过搜索 "PHYSICAL_START" 取得它的值。



你可以通过修改 "Alignment value to which kernel should be aligned" 的值和 "Physical address where the kernel is loaded" 来处理这个问题。

这个问题不影响X86_64



取得KGTP

通过http下载KGTP

请到 https://github.com/teawater/kgtp/archive/master.zip 取得KGTP的最新版本。

请到 https://github.com/teawater/kgtp/archive/release.zip 取得KGTP最新发布版本。



通过git下载KGTP

下面的命令将让你取得KGTP的最新版本:

git clone https://github.com/teawater/kgtp.git



下面的命令将让你取得KGTP最后的发布版本:

git clone https://github.com/teawater/kgtp.git -b release

镜像

https://code.csdn.net/teawater/kgtp

https://www.gitshell.com/teawater/kgtp/

https://git.oschina.net/teawater/kgtp

配置KGTP

下面这部分是在KGTP Makefile里的配置。用这个配置,KGTP将自动和当前系统的内核一起编译。

KERNELDIR := /lib/modules/`uname -r`/build
CROSS_COMPILE :=

KERELDIR 设置了你要一起编译的内核,默认情况下,KGTP会和当前的内核一起编译。

注意 这个目录应该是内核编译目录或者linux-headers目录,而不是内核源码目录。内核编译目录只有在编译成功后才能使用。

CROSS_COMPILE 设置编译KGTP的编译器前缀名。留空则使用默认编译器。

ARCH 是体系结构。



或者你可以通过修改KGTP目录里的Makefile选择你要和哪个内核一起编译以及你用什么编译器编译KGTP

例如:

KERNELDIR := /home/teawater/kernel/bamd64
CROSS_COMPILE :=x86_64-glibc_std-
ARCH := x86_64

KERNELDIR 设置为 /home/teawater/kernel/bamd64Compiler 设置为 x86_64-glibc_std-gcc



编译KGTP

普通编译

cd kgtp/
make





编译错误处理

cd kgtp/
make kgtp.ko



sudo yum install glibc-static

cd kgtp/
make NO_WARNING=1

或者:

cd kgtp/
export NO_WARNING=1
make

用一些特殊选项编译KGTP

大部分时候,KGTP可以自动选择正确的参数和和各种版本的Linux内核一起编译。

但是如果你想配置一些特殊选项,可以按照下面的介绍来做:



用这个选项,KGTP将不自动选择任何编译选项。

make AUTO=0

用这个选项,KGTP将使用简单frame替代KGTP ring buffer

简单frame不支持gtpframe_pipe,它现在只用来调试KGTP

make AUTO=0 FRAME_SIMPLE=1



用这个选项,$clock将返回rdtsc的值而不是local_clock

make AUTO=0 CLOCK_CYCLE=1



用这个选项,KGTP可以用procfs替代debugfs

make AUTO=0 USE_PROC=1



这些选线可以一起使用,例如:

make AUTO=0 FRAME_SIMPLE=1 CLOCK_CYCLE=1



安装和卸载 KGTP

因为KGTP可以直接在编译目录里insmod,所以不编译后不安装也可以直接使用(见 如何让GDB连接KGTP)。但是如果需要也可以将其安装到系统中。 安装:

cd kgtp/
sudo make install



卸载:

cd kgtp/
sudo make uninstall



DKMS一起使用KGTP

如果你需要的话,你还可以让DKMS来使用KGTP

下面的命令将拷贝KGTP的文件到DKMS需要的目录中。

cd kgtp/
sudo make dkms

于是你可以用DKMS命令控制KGTP。请到 http://linux.dell.com/dkms/manpage.html 去看如何使用DKMS



使用KGTP Linux内核patch

大多数时候,你不需要KGTP patch,因为KGTP以一个LKM的形式编译安装更为方便。但是为了帮助人们集成KGTP到他们自己的内核树,KGTP也提供了patch. KGTP目录中:



安装可以和KGTP一起使用的GDB

早于7.6版本的GDBtracepoint功能有一些bug,而且还有一些功能做的不是很好。

所以如果你的GDB小于7.6请到 https://code.google.com/p/gdbt/ 去安装可以和KGTP一起使用的GDB。这里提供UBUBTU, CentOS, Fedora, Mandriva, RHEL, SLE, openSUSE源。其他系统还可以下载静态编译版本。

如果你有GDB的问题,请根据这里的信息需要帮助或者汇报问题



附录B 如何让GDB连接KGTP

要使用KGTP的功能需要先让GDB连接到KGTP上。

普通Linux

安装KGTP模块

如果你已经安装了KGTP在你的系统中,你可以:

sudo modprobe gtp



或者你可以直接使用KGTP目录里的文件:

cd kgtp/
sudo insmod gtp.ko



处理找不到"/sys/kernel/debug/gtp"的问题

如果你有这个问题,请先确定你的内核config打开了"Debug Filesystem"如果你的系统内核是自己编译的



如果它以及被打开了,请用下面命令mount sysfs

sudo mount -t sysfs none /sys/

也许你可能会得到一些错误例如"sysfs is already mounted on /sys",请忽略他们。



请用下面命令mount debugfs

mount -t debugfs none /sys/kernel/debug/

然后你就找到"/sys/kernel/debug/gtp"



GDB连接到KGTP

装载Linux内核调试镜像到GDB

你可以用下面的命令打开GDB来装载镜像:

gdb Linux内核调试镜像

或者在打开GDB后,用下面的GDB命令来装载镜像:

file Linux内核调试镜像文件

你可以根据“当前Linux内核调试镜像在哪”找到Linux内核调试镜像文件。



注意GDB打开正确的vmlinux文件非常重要。请到 确定Linux内核调试镜像是正确的 去看下如何做。



GDB在本地主机上

sudo gdb ./vmlinux
(gdb) target remote /sys/kernel/debug/gtp
Remote debugging using /sys/kernel/debug/gtp
0x0000000000000000 in ?? ()

然后你就可以用GDB命令调试和跟踪Linux内核了。



如果GDB在远程主机上

ncKGTP接口映射到端口1024上。

sudo su
nc -l 1234 </sys/kernel/debug/gtp >/sys/kernel/debug/gtp
#(nc -l -p 1234 </sys/kernel/debug/gtp >/sys/kernel/debug/gtp 给老版本的nc)

之后,nc会在那里等待连接。



GDB连接1234端口。

gdb-release ./vmlinux
(gdb) target remote xxx.xxx.xxx.xxx:1234

然后你就可以用GDB命令调试和跟踪Linux内核了。



Android

这个视频介绍了使用GDB连接AndroidKGTP的过程,可访问 http://www.tudou.com/programs/view/qCumSPhByFI/ 或者 http://youtu.be/9YMpAvsl37I 进行观看。



安装KGTP模块

第一步 确定ADB已经连接到Android上。



第二步 拷贝KGTP模块到Android上。

sudo adb push gtp.ko /

目录 "/" 可能是只读的。你可以选择其他目录或者用命令"sudo adb shell mount -o rw,remount /"把这个目录remount为可写。



第三步 安装KGTP模块。

adb shell insmod /gtp.ko



处理找不到"/sys/kernel/debug/gtp"的问题

如果你有这个问题,请先确定你的内核config打开了"Debug Filesystem"如果你的系统内核是自己编译的

如果它以及被打开了,请用下面命令mount sysfs

sudo adb shell mount -t sysfs none /sys/

也许你可能会得到一些错误例如"Device or resource busy",请忽略他们。



请用下面命令mount debugfs

sudo adb shell mount -t debugfs none /sys/kernel/debug/



然后你就找到"/sys/kernel/debug/gtp"



GDB连接KGTP

ncKGTP接口映射到1024端口上。

adb forward tcp:1234 tcp:1234
adb shell "nc -l -p 1234 </sys/kernel/debug/gtp >/sys/kernel/debug/gtp"
#(adb shell "nc -l 1234 </sys/kernel/debug/gtp >/sys/kernel/debug/gtp" 给新版本的nc)

之后,nc会在那里等待连接。



GDB连接1234端口。

gdb-release ./vmlinux
(gdb) target remote :1234

然后你就可以用GDB命令调试和跟踪Linux内核了。



附录C 增加模块的符号信息到GDB

有时你需要添加一个Linux内核模块的符号信息到GDB以其调试之。

手动增加符号信息不太容易,所以KGTP包里包含了GDB Python脚本"getmod.py"和程序"getmod"可以帮到你。



如何使用getmod

"getmod" 是用C写的所以你可以把它用在任何地方即使是一个嵌入式环境。

例如:

#下面的命令将把Linux内核模块信息以GDB命令的格式保存到文件/tmp/mi
sudo getmod >/tmp/mi
#GDB那边:
(gdb) source /tmp/mi
add symbol table from file "/lib/modules/2.6.39-rc5+/kernel/fs/nls/nls_iso8859-1.ko" at
.text_addr = 0xf80de000
.note.gnu.build-id_addr = 0xf80de088
.exit.text_addr = 0xf80de074
.init.text_addr = 0xf8118000
.rodata.str1.1_addr = 0xf80de0ac
.rodata_addr = 0xf80de0c0
__mcount_loc_addr = 0xf80de9c0
.data_addr = 0xf80de9e0
.gnu.linkonce.this_module_addr = 0xf80dea00
#这条GDB命令后,所有Linux内核模块信息都被装载进GDB了。
#After this GDB command, all the Linux Kernel module info is loaded into GDB.

如果你使用远程调试或者离线调试,你可以需要修改基本目录。下面是一个例子:

#/home/teawater/kernel/b26GDB所在主机上内核模块所在的路径
sudo ./getmod -r /home/teawater/kernel/b26 >~/tmp/mi



如何使用getmod.py

请 注意 https://code.google.com/p/gdbt/下载的静态编译GDB不能使用getmod.py

在使用getmod.py前连接到KGTP

(gdb) source ~/kgtp/getmod.py

于是这个脚本将自动装载Linux内核模块到GDB中。

101