Linux Kernel GDB tracepoint module
(KGTP)
Update in 2014-07-22
目录
这个函数确实存在但是设置tracepoint到上面会失败如何处理? 16
如何处理错误 "Unsupported operator (null) (52) in expression." 18
Enable 和 disable tracepoint 24
如何处理错误 "No such file or directory." 或者 "没有那个文件或目录." 26
kprobes-optimization和tracepoint的执行速度 37
特殊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
用 $ignore_error 和 $last_errno 忽略tstart的错误 49
使用 $cooked_clock 和 $cooked_rdtsc 取得不包含KGTP运行时间的时间信息 50
使用 $xtime_sec 和 $xtime_nsec 取得 timespec 51
通过$bt收集栈并用GDB命令backtrace进行分析 53
用 $dump_stack 输出栈分析到printk里 58
如何用watch tracepoint控制硬件断点记录内存访问 61
使用while-stepping让Linux内核做单步 67
/sys/kernel/debug/gtpframe和离线调试 76
如何使用 /sys/kernel/debug/gtpframe_pipe 78
在tracepoint收集系统调用的从内核到用户层的的栈信息(可用来做backtrace) 86
定义一个per_cpu perf event trace状态变量 97
用$p_pe_en打开和关闭一个CPU上所有的perf event 100
用来帮助设置和取得perf event trace状态变量的GDB脚本 101
处理Linux内核调试镜像地址信息和Linux内核执行时不同的问题 114
处理找不到"/sys/kernel/debug/gtp"的问题 127
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是一个能在产品系统上实时分析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.py将在本机上自动配置和启动KGTP和GDB。
#第一次使用这个脚本需要等一段时间因为有一些包需要下载。
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的问题。
或者汇报到QQ群317654748。
KGTP小组将尽全力帮助你。
请到 https://code.google.com/p/kgtp/issues/list 访问旧问题列表。
这个表是给在使用过GDB调试程序的人准备的,他可以帮助你理解和记住KGTP的功能。
功能 |
GDB调试普通程序 |
GDB控制KGTP调试Linux内核 |
准备工作 |
系统里安装了GDB。 |
|
Attach |
使用命令"gdb -p pid"或者GDB命令"attach pid"可以attach系统中的某个程序. |
|
Breakpoints |
GDB命令"b place_will_stop",让程序在执行这个命令后执行,则程序将停止在设置这个断点的地方。 |
KGTP不支持断点但是支持tracepoint。Tracepoints可以被看作一种特殊的断点。其可以设置在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-stepping让Linux内核做单步 |
Backtrace |
GDB可以用命令"backtrace"打印全部调用栈。 |
KGTP也可以。如何 backtrace (stack dump) |
Watchpoint |
GDB可以用watchpoint让程序在某些内存访问发生的时候停止。 |
KGTP可以用watch tracepoint记录内存访问。 如何用watch tracepoint控制硬件断点记录内存访问 |
调用函数 |
GDB可以用命令"call function(xx,xx)"调用程序中的函数。 |
在GDB连到KGTP上以后,如果没有用GDB命令"tfind"选择一条trace帧缓存里面的条目,GDB就处于 普通模式。于是你可以直接访问内存(Linux内核或者用户程序)的值和trace状态变量的值。
如果你选择了一个trace帧条目,可以用GDB命令"tfind -1"返回到普通模式。请到在普通模式直接访问当前值取得GDB命令"tfind"的更多信息。
例如你可以用下面的命令访问"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
你可以使用和访问内存一样的命令访问TSV。 请到 如何使用trace状态变量 取得更多TSV的信息。
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。
trace命令非常类似break命令,它的参数可以是文件行,函数名或者一个地址。trace将定义一个或者多个地址定义一个tracepoint,KGTP将在这个点做一些动作。
这是一些使用trace命令的例子:
(gdb) trace foo.c:121 // 一个文件和行号
(gdb) trace +2 // 2行以后
(gdb) trace my_function // 函数的第一行
(gdb) trace *my_function // 函数的第一个地址
(gdb) trace *0x2117c4 // 一个地址
GCC为了提高程序执行效率会inline一些static函数。因为目标文件没有inline函数的符号,所以你不能设置tracepoint在函数名上。
你可以用"trace 文件:行号"在其上设置断点。
http://sourceware.org/gdb/current/onlinedocs/gdb/Tracepoint-Conditions.html
和breakpoint一样,我们可以设置tracepoint的触发条件。而且因为条件检查是在KGTP执行的,所以速度比breakpoint的条件检查快很多。
例如:
(gdb) trace handle_irq if (irq == 47)
tracepoint 1的动作将只在irq是47的时候才被执行。
你还可以用GDB命令"condition"设置tracepoint的触发条件。GDB命令"condition N COND"将设置tracepoint N只有条件COND为真的时候执行。
例如:
(gdb) trace handle_irq
(gdb) condition 1 (irq == 47)
GDB命令"info tracepoint"将显示tracepoint的ID。
$bpnum的值是最后一个GDB tracepoint的ID,所以你可以不取得tracepoint的ID就用condition来设置最后设置的tracepoint的条件,例如:
(gdb) trace handle_irq
(gdb) condition $bpnum (irq == 47)
如果你使用关于字符串的条件tracepoint,你在调用"tstart"的时候可能得到这个出错信息。
你可以转化char为int来处理这个问题,例如:
(gdb) p/x 'A'
$4 = 0x41
(gdb) condition 1 (buf[0] == 0x41)
这个命令将设置一组action当tracepoint num触发的时候执行。如果没有设置num则将设置action到最近创建的tracepoint上(因此你可以定义一个tracepoint然后直接输入actions而不需要参数)。然后就要在后面输入action,最后以end为结束。到目前为止,支持的action有collect,teval和while-stepping。
当tracepoint触发的时候,收集表达式的值。这个命令可接受用逗号分割的一组列表,这些列表除了可以是全局,局部或者本地变量,还可以是下面的这些参数:
$regs 收集全部寄存器。
$args 收集函数参数。
$locals 收集全部局部变量。
请 注意 collect 一个指针(collect ptr)将只能collect这个指针的地址. 在指针前面增加一个 * 将会让action collect指针指向的数据(collect *ptr)。
当tracepoint触发的时候,执行指定的表达式。这个命令可接受用逗号分割的一组列表。表达式的结果将被删除,所以最主要的作用是把值设置到trace状态变量中 (see 普通trace状态变量),而不用像collect一样把这些值存到trace帧中。
请到 使用while-stepping让Linux内核做单步 去看如何使用它。
tracepoint只有在用下面的GDB命令启动后才可以执行action:
(gdb) tstart
它可以用下面的命令停止:
(gdb) tstop
和breakpoint一样,tracepoint可以使用GDB命令 "enable" 和 "disable"。但是请 注意 它们只在tracepoint停止的时候有效。
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 取得它的详细信息。
当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/"但是vmlinux让GDB在"/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取得他们的介绍。
/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 {
你可以用GDB命令"info tracepoints"显示所有的tracepoint。
你可以用GDB命令"save tracepoints filename"保存所有的设置tracepoint的命令到文件filename里。于是你可以在之后用GDB命令"source filename"设置重新这些tracepoint。
GDB命令"delete id"将删除tracepoint id。如果"delete"没有参数,则删除所有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
下面是记录内核调用函数"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>
在用"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
请用GDB命令"tstatus"。
http://sourceware.org/gdb/current/onlinedocs/gdb/Starting-and-Stopping-Trace-Experiments.html
帧缓存默认情况下不是循环缓存。当缓存满了的时候,tracepoint将停止。
下面的命令将设置trace缓存为循环缓存,当缓存满了的时候,其将自动删除最早的数据并继续trace。
(gdb) set circular-trace-buffer on
http://sourceware.org/gdb/current/onlinedocs/gdb/Starting-and-Stopping-Trace-Experiments.html
默认情况下,当GDB断开KGTP的时候将自动停止tracepoint并删除trace帧。
下面的命令将打开KGTP disconnect-trace。在设置之后,当GDB断开KGTP的时候,KGTP将不停止tracepoint。GDB重新连到KGTP的时候,其可以继续控制KGTP。
(gdb) set disconnected-tracing on
因为tracepoint是和Linux内核一起执行,所以它的速度将影响到系统执行的速度。
KGTP tracepoint是基于Linux内核kprobe。因为普通kprobe是基于断点指令,所以它的速度不是很快。
但是如果你的arch是X86_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的速度就请避免使用这些功能。
http://sourceware.org/gdb/current/onlinedocs/gdb/Trace-State-Variables.html
trace状态变量简称TSV。
TSV可以在tracepoint action和condition中被访问,并且可以直接被GDB命令访问。
请 注意 GDB 7.2.1和更晚的版本直接访问trace状态变量,比他们老的GDB只能通过命令"info tvariables"取得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状态变量是一种特殊的trace状态变量。 当一个tracepoint action访问到其的时候,其将自动访问这个CPU的Per_cpu trace状态变量。
它有两个优点:
1. 访问Per_cpu trace状态变量的tracepoint actions不存在竞态条件问题,所以其不需要对trace状态变量加锁。所以其在多核的机器上速度更快。
2. 写针对记录某个CPU的tracepoint actions比普通trace状态变量更容易。
Per_cpu trace状态变量有两种类型:
"per_cpu_"+string
或者
"p_"+string
例如:
(gdb) tvariable $p_count
在tracepoint action中访问这个trace状态变量的时候,其将返回这个变量在这个action运行的CPU上的值。
"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
这个例子定义了一个记录每个CPU调用多少次vfs_read的tracepoint。
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
这个例子记录了每个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
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状态变量写更好。
$self_trace和前面介绍的特殊trace状态变量不同,它是用来控制tracepoint的行为的。
默认情况下,tracepoint被触发后,如果current_task是KGTP自己的进程(GDB,netcat,getframe或者其他访问KGTP接口的进程)的时候,其将不执行任何actions。
如果你想让tracepoint actions和任何task的时候都执行,请包含一个包含一个访问到$self_trace的命令到actions中,也就是说增加下面的命令到actions中:
>teval $self_trace=0
有时,因为内核是用优化编译的,所以在函数结尾设置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.
当KGTP在tstart取得错误,这个命令将失败。
但有时我们需要忽略这个错误信息并让KGTP工作。例如:如果你在inline函数spin_lock设置tracepoint,这个tracepoint将被设置到很多地址上,有一些地址不能设置kprobe,于是它就会让tstart出错。这时你就可以用"$ignore_error"忽略这些错误。
最后一个错误信息将存在"$last_errno"中。
(gdb) tvariable $ignore_error=1
这个命令将打开忽略。
(gdb) tvariable $ignore_error=0
这个命令将关闭忽略。
访问这两个trace状态变量可以取得不包含KGTP运行时间的时间信息,于是我们可以取得一段代码更真实的执行时间即使这个tracepoint的action比较复杂。
访问trace状态变量将返回用getnstimeofday取得的timespec时间信息。
$xtime_sec 将返回timespec秒的部分。
$xtime_nsec 将返回timespec纳秒的部分。
每次你的程序做一个函数调用的时候, 这次调用的信息就会生成。这些信息包括调用函数的地址,调用参数,局部变量的值。这些信息被存储在我们称为栈帧的地方,栈帧是从调用栈中分配而来。
因为这个方法更快(只在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命令:
bt 是GDB命令 backtrace 的别名,这个命令将打印stack中的信息:每一行是一个调用栈。
up n 是向上移动n个帧。如果n是正数,则向外到更高的帧,一直到这个栈最大的一行。n的默认值是1。
down n 是向下移动n个帧。如果n是正数,则向内到更低的帧,一直到最新创建的那个栈帧。n的默认值是1。
is move n frames down the stack. For positive numbers n, this advances toward the innermost frame, to lower frame numbers, to frames that were created more recently. n defaults to one. 你可以把 down 缩写为 do。
frame n 是选择帧n。帧0是最近创建的帧,帧1调用这个帧的帧。所以最高的帧是main。
你还可以看到当你用up,down或者frame来选择调用栈帧的时候,你可以输出不同帧的参数和局部变量。
要取得更多关于如何使用GDB分析调用栈的信息,请到http://sourceware.org/gdb/current/onlinedocs/gdb/Stack.html。
如果你只想取得当前函数的调用函数的栈,可以用$_ret。
请 注意 使用$_ret的tracepoint不能设置在函数的第一个地址上。
例如:
(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。
因为这个方法需要在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
在前面的章节,你可以看到如果想取得Linux内核的信息,你需要用tracepoint "collect" action来保存信息到tracepoint帧中并用GDB tfind命来来分析这些数据帧。
但是有时我们希望直接取得这些数据,所以KGTP提供了一种直接取得这些数据的方法。
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_format,collect printk将按照这里设置的格式进行输出。 这些格式是:
0 这是默认值。
如果collect的长度是1,2,4,8则其将输出一个无符号十进制数。
如果不是,则其将输出十六进制字串。
1 输出值是有符号十进制数。
2 输出值是无符号十进制数。
3 输出值是无符号十六进制数。
4 输出值是字符串。
5 输出值是十六进制字串。
如果要输出一个全局变量,需要将其先设置到$printk_tmp中。
下面是一个显示调用vfs_readdir时的计数,pid,jiffies_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 可以通过设置一些特殊的trace状态变量设置硬件断点来记录内存访问。
请 注意 watch tracepoint现在只有X86和X86_64支持。而且因为Linux 2.6.26和更老版本有一些IPI的问题,只有Linux 2.6.27和更新版本上可以正常使用动态watch tracepoint。
名称 |
普通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 tracepoint的ID到$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。 |
取得这个tracepoint设置到$watch_type里的值。 |
设置watch tracepoint的类型。 |
取得这个watch tracepoint的类型。 |
设置watch tracepoint的默认类型。 |
取得这个watch tracepoint在实际执行中的类型。 |
$watch_size |
当这个tracepoint要设置一个动态watch
tracepoint的时候,设置watch长度到$watch_size。 |
取得这个tracepoint设置到$watch_size里的值。 |
设置watch tracepoint的长度。 |
取得这个watch tracepoint的长度。 |
设置watch tracepoint的默认长度。 |
取得这个watch tracepoint在实际执行中的长度。 |
$watch_start |
设置地址到动态watch tracepoint($watch_set_addr或者$watch_set_id设置)中并让其开始工作。 |
取得这次开始的返回值。 |
不支持 |
不支持 |
不支持 |
不支持 |
$watch_stop |
设置地址到$watch_stop将让一个watch这个地址的动态watch tracepoint停止。 |
取得这次停止的返回值。 |
不支持 |
不支持 |
不支持 |
不支持 |
$watch_trace_num |
不支持 |
不支持 |
不支持 |
不支持 |
不支持 |
设置这个动态watch tracepoint的tracepoint的号码。 |
$watch_trace_addr |
不支持 |
不支持 |
不支持 |
不支持 |
不支持 |
设置这个动态watch tracepoint的tracepoint的地址。 |
$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。下面是一个监视jiffies_64写的例子:
#静态watch tracepoint从tracepoint的地址中取得要监视的地址
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。下面是一个监视函数function get_empty_filp中f->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_pos和f->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_pos和file->f_op。
请 注意 while-stepping现在只有X86和X86_64支持。
介绍使用while-stepping的视频 http://www.codepark.us/a/12。
while-stepping 是一种可以包含actions的特殊tracepoint action。
当一个actions中包含了“while-stepping n”的tracepoint执行的时候,其将做n次单步并执行while-stepping的actions。例如:
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开始的这步的计数。
不同step的数据将会被记录到不同的traceframe中,你可以用tfind (用tfind选择trace帧缓存里面的条目) 选择他们。
或者你可以将KGTP切换到回放模式,这样GDB可以用执行和反向执行命令选择一个while-stepping tracepoint的traceframe。例如:
用tfind选择一个while-stepping的traceframe。
(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命令tstart,tstop,tfind或者quit可以自动关闭回放模式。
有时GDB会这样输出信息:
inode has been optimized out of existence.
res has been optimized out of existence.
这是因为inode和res的值被优化掉了。内核用-O2编译的所以你有时会碰到这个问题。
有两个方法处理这个问题:
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是一个当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
于是你可以在当前目录找到文件gtpstart和gtpstop,把他们拷贝到你想调试的主机上。
在被调试主机上,先拷贝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"。
这个接口提供和"gtpframe"同样的数据,但是可以在KGTP tracepoint运行的时候也可以使用。在数据读出之后,其将自动从trace帧里删除类似ftrace "trace_pipe"。
#连接到接口上
(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就是这样的例子。
sudo cat /sys/kernel/debug/gtpframe_pipe > g
于是所有帧信息都被存入了文件"g"。
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.
为了锁安全,KGTP默认将自动忽略读/sys/kernel/debug/gtpframe_pipe的任务。
如果你真希望trace这个任务而且确定这是安全的,你可以使用"tstart"之前使用下面的命令:
(gdb) tvariable $pipe_trace=1
于是KGTP将不再忽略读/sys/kernel/debug/gtpframe_pipe的任务。
KGTP可以在不停止用户程序的情况下,访问内存和trace这个应用层程序。
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" 来attach上task的pid。
因此让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。
KGTP用Linux内核功能 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)
$current 是一个特殊trace状态变量。当一个tracepoint的action 访问其的时候,tracepoint将收集当前task的寄存器和内存值而不是内核中的值。
一般来说,tracepoint通过 task_pt_regs 取得寄存器的值。于是在tracepoint actions中collect $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寄存器指针的特殊函数(例如:X86的do_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 ?? ()
这个脚本可以通过记录并分析中断处理时候的取得的PC值从而得到Linux kernel或者用户层程序的热点代码。
请到 http://code.google.com/p/kgtp/wiki/hotcode 去看如何使用它。
KGTP支持用C写的插件,插件将被编译成LKM。
#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。
name 特殊trace状态变量的名字.
val 特殊trace状态变量的初始值.
hooks 函数指针。如果这个功能不支持,函数指针就设置为NULL。
返回值 成功返回gtp_var指针。失败则返回用IS_ERR和PTR_ERR可以处理的错误码。
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);
};
gdb_set_val 在GDB设置TSV值的时候调用,请 注意 TSV只能被GDB命令"tvariable $xxx=1"设置而且只有在GDB命令"tstart"的时候才会被发到KGTP。
unused 是无用的,只用来让这个指针可以和agent_set_val共享函数。
var 是指向gtp_var的指针,于是当多个TSV共享一个函数的时候,这个值可以用来判定哪个TSV被访问了。
val 是GDB设置来的值。
返回值 错误返回-1,正确返回0。
gdb_get_val 在GDB取TSV值的时候被调用。请 注意 取TSV值和设置TSV不同,设置任何时候都会直接从KGTP里取,并且取值的GDB命令和访问一个普通的GDB内部变量一样。例如:"p $xxx"。
unused 和gdb_set_val作用相同。
var 和gdb_set_val作用相同。
val 用来返回值的指针。
返回值 和gdb_set_val作用相同。
agent_set_val 在tracepoint action(teval_expr1,_expr2,_...)设置TSV的时候调用。
gts 是指向tracepoint会话结构的指针。
var 和gdb_set_val作用相同。
val action设置的值。
返回值 和gdb_set_val作用相同。
agent_get_val will be called when tracepoint action(collect expr1, expr2, ...或者 teval expr1, expr2, ...) get the TSV.
gts 和agent_set_val作用相同。
var 和gdb_set_val作用相同。
val 和gdb_get_val作用相同。
返回值 和gdb_set_val作用相同。
extern int gtp_plugin_var_del(struct gtp_var *var);
当rmmod插件模块的时候,用这个函数删除gtp_plugin_var_add增加的TSV。
KGTP目录里的plugin_example.c是KGTP plugin的例子,可以用"make P=1"直接编译其。其将增加四个TSV到KGTP中。
$test1 什么也不支持。
$test2 支持被GDB或者tracepoint action读写。
$test3 只支持tracepoint action写,当设置一个值到里面的时候,其将找到这个值对应的符号并打印出来。例如 "teval $test3=(int64_t)$rip"。
$test4 只支持tracepoint action写,当设置值的时候其将打印当前tracepoint的地址的符号。
insmod plugin_example.ko
让GDB连上KGTP并使用其。
断开GDB. 如果 GDB断开的时候不要停止tracepoint 中的选项设置为打开,则设置其为关闭。
rmmod plugin_example.ko
请 注意 KGTP支持加入多个插件。
性能计数器是大部分现代CPU都有的特殊硬件寄存器。这些寄存器对一些硬件事件进行计数:例如指令执行数量,cachemisses数量,分支预测失败数,而且这些计数不会让应用程序或者内核变慢。其还可以设置到达一定的值的时候发生中断,这些就可以用来分析在某CPU上执行程序的性能。
Linux性能计数器子系统perf event可以用来取得性能计数器的值。你可以用KGTP perf event trace状态变量访问这些值。
请读内核目录里的tools/perf/design.txt文件取得perf event的更多信息。
访问一个性能计数器需要定义下面的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 trace状态变量一样。
"p_pe_"+perf_event type+string+CPU_id
请 注意 如果定义一个per_cpu perf event trace状态变量,就不需要在定义cpu id("pe_cpu")因为KGTP已经取得了CPU的ID。
类型可以是:
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的类型和配置。
我认为取得一段代码的性能计数器信息比较好的办法是在函数开头打开计数器在函数结束的时候关闭计数器。你可以用"pe_en"设置他们,但是如果你有多个perf event trace状态变量的时候,这样会让tracepoint action很大。$p_pe_en就是处理这种问题的。 你可以打开所有perf event trace状态变量在当前CPU上用下面的action:
>teval $p_pe_en=1
设置$p_pe_en为0来关闭他们。
>teval $p_pe_en=0
下面这个GDB脚本定义了2个命令dpe和spe来帮助定义和显示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
#定义3个perf event trace状态变量PERF_COUNT_HW_CPU_CYCLES,PERF_COUNT_HW_CACHE_MISSES和PERF_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
要使用KGTP,你需要打开下面这些内核选项:
General setup --->
[*] Kprobes
[*] Enable loadable module support --->
Kernel hacking --->
[*] Debug Filesystem
[*] Compile the kernel with debug info
如果你改了Linux内核config的任何项目,请重新编译你的内核。
默认的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内核软件包。
1) 增加调试源到Ubuntu源列表。
在命令行按照下面的命令创建文件 /etc/apt/sources.list.d/ddebs.list:
echo "deb http://ddebs.ubuntu.com $(lsb_release -cs) main restricted universe multiverse" | \
sudo tee -a /etc/apt/sources.list.d/ddebs.list
稳定版本(不能是alpha或者betas)需要用命令行增加下面几行:
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
导入调试符号签名key:
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内核调试镜像。
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/-.*//')
请 注意 当内核更新的时候这一步 安装内核源码 需要再做一次。
使用下面的命令:
sudo debuginfo-install kernel
或者:
sudo yum --enablerepo=fedora-debuginfo install kernel-debuginfo
于是你可以在"/usr/lib/debug/lib/modules/$(uname -r)/vmlinux"找到内核调试镜像。
sudo yum install kernel-devel-$(uname -r)
请 注意 在升级过内核包之后,你可能需要重新调用这个命令。
需要安装Linux内核调试镜像和Linux内核源码包。
因为GDB从Linux内核调试镜像里取得地址信息和调试信息,所以使用正确的Linux内核调试镜像是非常重要的。所以在使用KGTP前,请先做检查。
有2个方法进行检查,我建议2个方法都做一次来确保Linux内核调试镜像是正确的。
请 注意 如果你确定使用了正确的Linux内核调试镜像但是不能通过这个两个方法。请看 处理Linux内核调试镜像地址信息和Linux内核执行时不同的问题 。
在UBUNTU中,你可以在"/usr/lib/debug/boot/vmlinux-$(uname -r)"找到它。
在Fedora中,你可以在"/usr/lib/debug/lib/modules/$(uname -r)/vmlinux"找到它。
如果你自己编译的内核,内核编译目录中的文件“vmlinux”是调试镜像。
在运行着要trace的内核的系统上,用下面的命令取得sys_read和sys_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的地址是0xffffffff8117a520,sys_write的地址是0xffffffff8117a5b0。
之后我们用GDB从Linux内核调试镜像中取得sys_read和sys_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_read和sys_write的地址一样,所以Linux内核调试镜像是正确的。
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_banner是Linux内核调试镜像里的内核信息。
之后,根据 让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_banner是KGTP正在trace的内核的内核信息,如果相同,则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。
请到 https://github.com/teawater/kgtp/archive/master.zip 取得KGTP的最新版本。
请到 https://github.com/teawater/kgtp/archive/release.zip 取得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 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/bamd64。 Compiler 设置为 x86_64-glibc_std-gcc。
cd kgtp/
make
在一些编译环境中(例如Android)将出现一些编译应用程序getmod或者getframe的错误。请忽略这些错误并使用目录中的gtp.ko。
或者用下面的命令处理:
cd kgtp/
make kgtp.ko
如果在Fedora上得到出错信息"/usr/bin/ld: cannot find -lc",请用下面的命令处理:
sudo yum install glibc-static
当编译KGTP的时候,如果一些功能不被Linux内核支持,会输入一些warning。一些编译环境会使warning转为error。要关掉这些warning,使用下面的命令处理:‘
cd kgtp/
make NO_WARNING=1
或者:
cd kgtp/
export NO_WARNING=1
make
大部分时候,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可以直接在编译目录里insmod,所以不编译后不安装也可以直接使用(见 如何让GDB连接KGTP)。但是如果需要也可以将其安装到系统中。 安装:
cd kgtp/
sudo make install
卸载:
cd kgtp/
sudo make uninstall
如果你需要的话,你还可以让DKMS来使用KGTP。
下面的命令将拷贝KGTP的文件到DKMS需要的目录中。
cd kgtp/
sudo make dkms
于是你可以用DKMS命令控制KGTP。请到 http://linux.dell.com/dkms/manpage.html 去看如何使用DKMS。
大多数时候,你不需要KGTP patch,因为KGTP以一个LKM的形式编译安装更为方便。但是为了帮助人们集成KGTP到他们自己的内核树,KGTP也提供了patch. 在KGTP目录中:
gtp_3.7_to_upstream.patch 是给Linux kernel 从3.7到upstream的patch。
gtp_3.0_to_3.6.patch 是给Linux kernel 从3.0到3.6的patch。
gtp_2.6.39.patch 是给Linux kernel 2.6.39的patch。
gtp_2.6.33_to_2.6.38.patch 是给Linux kernel 从2.6.33到2.6.38的patch。
gtp_2.6.20_to_2.6.32.patch 是给Linux kernel 从2.6.20到2.6.32的patch。
gtp_older_to_2.6.19.patch 是给Linux kernel 2.6.19以及更早版本的patch。
早于7.6版本的GDB的tracepoint功能有一些bug,而且还有一些功能做的不是很好。
所以如果你的GDB小于7.6请到 https://code.google.com/p/gdbt/ 去安装可以和KGTP一起使用的GDB。这里提供UBUBTU, CentOS, Fedora, Mandriva, RHEL, SLE, openSUSE源。其他系统还可以下载静态编译版本。
如果你有GDB的问题,请根据这里的信息需要帮助或者汇报问题。
要使用KGTP的功能需要先让GDB连接到KGTP上。
如果你已经安装了KGTP在你的系统中,你可以:
sudo modprobe gtp
或者你可以直接使用KGTP目录里的文件:
cd kgtp/
sudo insmod gtp.ko
如果你有这个问题,请先确定你的内核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来装载镜像:
gdb Linux内核调试镜像
或者在打开GDB后,用下面的GDB命令来装载镜像:
file Linux内核调试镜像文件
你可以根据“当前Linux内核调试镜像在哪”找到Linux内核调试镜像文件。
请 注意 让GDB打开正确的vmlinux文件非常重要。请到 确定Linux内核调试镜像是正确的 去看下如何做。
sudo gdb ./vmlinux
(gdb) target remote /sys/kernel/debug/gtp
Remote debugging using /sys/kernel/debug/gtp
0x0000000000000000 in ?? ()
然后你就可以用GDB命令调试和跟踪Linux内核了。
用nc把KGTP接口映射到端口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内核了。
这个视频介绍了使用GDB连接Android上KGTP的过程,可访问 http://www.tudou.com/programs/view/qCumSPhByFI/ 或者 http://youtu.be/9YMpAvsl37I 进行观看。
第一步 确定ADB已经连接到Android上。
第二步 拷贝KGTP模块到Android上。
sudo adb push gtp.ko /
目录 "/" 可能是只读的。你可以选择其他目录或者用命令"sudo adb shell mount -o rw,remount /"把这个目录remount为可写。
第三步 安装KGTP模块。
adb shell insmod /gtp.ko
如果你有这个问题,请先确定你的内核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"。
用nc将KGTP接口映射到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内核了。
有时你需要添加一个Linux内核模块的符号信息到GDB以其调试之。
手动增加符号信息不太容易,所以KGTP包里包含了GDB Python脚本"getmod.py"和程序"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/b26是GDB所在主机上内核模块所在的路径
sudo ./getmod -r /home/teawater/kernel/b26 >~/tmp/mi
请 注意 https://code.google.com/p/gdbt/下载的静态编译GDB不能使用getmod.py。
在使用getmod.py前连接到KGTP。
(gdb) source ~/kgtp/getmod.py
于是这个脚本将自动装载Linux内核模块到GDB中。