对于这篇冗长的帖子道歉,我在以较短的方式制定它时遇到了麻烦。此外,这可能更适合Unix& Linux Stack Exchange,但我首先在SO尝试,因为有一个ftrace
标记。
无论如何 - 我想观察使用function_graph
在完整ftrace
捕获的上下文中执行用户程序的机器指令。一个问题是我需要这个旧内核:
$ uname -a
Linux mypc 2.6.38-16-generic #67-Ubuntu SMP Thu Sep 6 18:00:43 UTC 2012 i686 i686 i386 GNU/Linux
...在这个版本中,没有UPROBES
- 正如Uprobes in 3.5 [LWN.net]所述,它应该能够做到这一点。 (只要我不必修补原始内核,我就会愿意尝试用树构建的内核模块,正如User-Space Probes (Uprobes) [chunghwan.com]似乎证明的那样;但是我可以从0: Inode based uprobes [LWN.net]看,2.6可能需要一个完整的补丁)
但是,在此版本中,有/sys/kernel/debug/kprobes
和/sys/kernel/debug/tracing/kprobe_events
;和Documentation/trace/kprobetrace.txt意味着可以直接在地址上设置kprobe;即使我无法在任何地方找到如何使用它的例子。
在任何情况下,我仍然不确定要使用哪些地址 - 作为一个小例子,让我们说我想跟踪main
wtest.c
函数的开头计划(包括在下面)。我可以这样做来编译并获得一个机器指令汇编列表:
$ gcc -g -O0 wtest.c -o wtest
$ objdump -S wtest | less
...
08048474 <main>:
int main(void) {
8048474: 55 push %ebp
8048475: 89 e5 mov %esp,%ebp
8048477: 83 e4 f0 and $0xfffffff0,%esp
804847a: 83 ec 30 sub $0x30,%esp
804847d: 65 a1 14 00 00 00 mov %gs:0x14,%eax
8048483: 89 44 24 2c mov %eax,0x2c(%esp)
8048487: 31 c0 xor %eax,%eax
char filename[] = "/tmp/wtest.txt";
...
return 0;
804850a: b8 00 00 00 00 mov $0x0,%eax
}
...
我会通过这个脚本设置ftrace日志记录:
sudo bash -c '
KDBGPATH="/sys/kernel/debug/tracing"
echo function_graph > $KDBGPATH/current_tracer
echo funcgraph-abstime > $KDBGPATH/trace_options
echo funcgraph-proc > $KDBGPATH/trace_options
echo 0 > $KDBGPATH/tracing_on
echo > $KDBGPATH/trace
echo 1 > $KDBGPATH/tracing_on ; ./wtest ; echo 0 > $KDBGPATH/tracing_on
cat $KDBGPATH/trace > wtest.ftrace
'
你可以看到debugging - Observing a hard-disk write in kernel space (with drivers/modules) - Unix & Linux Stack Exchange {(1}}中ftrace
的一部分(其他复杂的)登录(我从中得到了这个例子)。
基本上,当ftrace
的第一条指令 - 比如0x8048474,0x8048475,0x8048477,0x804847a,0x804847d,0x8048483的指令时,我希望在main
日志中打印输出。 0x8048487 - 由(任何)CPU执行。问题是,据Anatomy of a Program in Memory : Gustavo Duarte我可以理解,这些地址是虚拟地址,从流程本身的角度来看(我收集的是,相同的视角显示/proc/PID/maps
)...显然,krpobe_event
我需要一个实际地址?
所以,我的想法是:如果我能找到与程序反汇编的虚拟地址相对应的物理地址(比如编写一个内核模块,它会接受pid和地址,并通过procfs返回物理地址),我可以将地址设置为一种&#34;跟踪点&#34;在上面的脚本中通过/sys/kernel/debug/tracing/kprobe_events
- 并希望将它们放在ftrace
日志中。原则上这可行吗?
有一个问题,我在Linux(ubuntu), C language: Virtual to Physical Address Translation - Stack Overflow上找到了:
在用户代码中,您无法知道与虚拟地址对应的物理地址。这是信息根本不在内核之外导出。它甚至可以随时改变,特别是如果内核决定更换你的部分进程的内存 ...
使用systemcall / procfs将虚拟地址传递给内核并使用vmalloc_to_pfn。通过procfs / registers返回物理地址。
然而,vmalloc_to_pfn
似乎并不重要:
VA:0xf8ab87fc PA使用vmalloc_to_pfn:0x36f7f7fc。但我实际上期待:0x136f7f7fc ...
物理地址介于4到5 GB之间。但我无法得到确切的物理地址,我只能得到切断的32位地址。有没有其他方法可以获得真实的物理地址?
所以,我不确定如何可靠地提取物理地址,以便kprobes跟踪它们 - 特别是因为&#34;它甚至可以随时改变#34;。但是在这里,我希望由于程序规模小而且微不足道,程序在跟踪时不会交换,从而可以获得适当的捕获。 (所以即使我必须多次运行上面的调试脚本,只要我希望获得&#34;正确的&#34;捕获10次(甚至100次),我和# 39;好吧。)。
请注意,我希望通过ftrace
输出,以便时间戳在同一个域中表示(有关时间戳问题的说明,请参阅Reliable Linux kernel timestamps (or adjustment thereof) with both usbmon and ftrace? - Stack Overflow)。因此,即使我能提出一个gdb
脚本,从用户空间运行和跟踪程序(同时获得ftrace
捕获) - 我想避免因为gdb
本身的开销将显示在ftrace
日志中。
所以,总结一下:
trace_printk
来生成ftrace
日志(或者即使没有这个,处理函数名称本身也应该显示在ftrace
日志中),并且它不会&# 39;似乎会有太多的开销...... 实际上,在2007年的帖子Jim Keniston - utrace-based uprobes: systemtap mailing list中,有一个11. Uprobes Example
(添加到Documentation/uprobes.txt
),这似乎就是一个内核模块注册处理函数。不幸的是,它使用linux/uprobes.h
;我的kprobes.h
只有/usr/src/linux-headers-2.6.38-16/include/linux/
。此外,在我的系统上,即使systemtap
抱怨CONFIG_UTRACE
未启用(请参阅this comment)...所以,如果有其他任何方法我可以用来获取调试像我想要的那样跟踪,无需重新编译内核来获取探测器,知道它会很棒......
wtest.c
:
#include <stdio.h>
#include <fcntl.h> // O_CREAT, O_WRONLY, S_IRUSR
int main(void) {
char filename[] = "/tmp/wtest.txt";
char buffer[] = "abcd";
int fd;
mode_t perms = S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH;
fd = open(filename, O_RDWR|O_CREAT, perms);
write(fd,buffer,4);
close(fd);
return 0;
}
答案 0 :(得分:1)
显然,使用3.5+内核的内置增强功能会更容易;但鉴于我的内核2.6.38的uprobes是一个非常深入的补丁(我无法在一个单独的内核模块中真正隔离,以避免修补内核),这是我可以注意到的2.6.38上的独立模块。 (由于我仍然不确定很多事情,我仍然希望看到一个可以纠正这篇文章中任何误解的答案。)
我想我到了某个地方,但不是kprobes
。我不确定,但似乎我设法让物理地址正确;但是,kprobes
文档是特定的,当使用&#34; @ADDR:在ADDR获取内存(ADDR应该在内核中)&#34 ;;我得到的物理地址低于0xc0000000的内核边界(但是,0xc0000000通常与虚拟内存布局一起?)。
所以我使用了硬件断点 - 模块在下面,但是需要注意的是 - 它随机行为,偶尔会导致内核哎呀!通过编译模块,并在bash
中运行:
$ sudo bash -c 'KDBGPATH="/sys/kernel/debug/tracing" ;
echo function_graph > $KDBGPATH/current_tracer ; echo funcgraph-abstime > $KDBGPATH/trace_options
echo funcgraph-proc > $KDBGPATH/trace_options ; echo 8192 > $KDBGPATH/buffer_size_kb ;
echo 0 > $KDBGPATH/tracing_on ; echo > $KDBGPATH/trace'
$ sudo insmod ./callmodule.ko && sleep 0.1 && sudo rmmod callmodule && \
tail -n25 /var/log/syslog | tee log.txt && \
sudo cat /sys/kernel/debug/tracing/trace >> log.txt
...我得到一份日志。我想跟踪main()
的{{1}}的前两条说明,对我来说是:
wtest
...在虚拟地址0x08048474和0x08048475。在$ objdump -S wtest/wtest | grep -A3 'int main'
int main(void) {
8048474: 55 push %ebp
8048475: 89 e5 mov %esp,%ebp
8048477: 83 e4 f0 and $0xfffffff0,%esp
输出中,我可以得到,说:
syslog
...意味着它将虚拟地址0x08048474映射到物理地址0x639ec474。但是,物理不用于硬件断点 - 我们可以直接向...
[ 1106.383011] callmodule: parent task a: f40a9940 c: kworker/u:1 p: [14] s: stopped
[ 1106.383017] callmodule: - wtest [9404]
[ 1106.383023] callmodule: Trying to walk page table; addr task 0xEAE90CA0 ->mm ->start_code: 0x08048000 ->end_code: 0x080485F4
[ 1106.383029] callmodule: walk_ 0x8048000 callmodule: Valid pgd : Valid pud: Valid pmd: page frame struct is @ f63e5d80; *virtual (page_address) @ (null) (is_vmalloc_addr 0 virt_addr_valid 0 virt_to_phys 0x40000000) page_to_pfn 639ec page_to_phys 0x639ec000
[ 1106.383049] callmodule: walk_ 0x80483c0 callmodule: Valid pgd : Valid pud: Valid pmd: page frame struct is @ f63e5d80; *virtual (page_address) @ (null) (is_vmalloc_addr 0 virt_addr_valid 0 virt_to_phys 0x40000000) page_to_pfn 639ec page_to_phys 0x639ec000
[ 1106.383067] callmodule: walk_ 0x8048474 callmodule: Valid pgd : Valid pud: Valid pmd: page frame struct is @ f63e5d80; *virtual (page_address) @ (null) (is_vmalloc_addr 0 virt_addr_valid 0 virt_to_phys 0x40000000) page_to_pfn 639ec page_to_phys 0x639ec000
[ 1106.383083] callmodule: physaddr : (0x080483c0 ->) 0x639ec3c0 : (0x08048474 ->) 0x639ec474
[ 1106.383106] callmodule: 0x08048474 id [3]
[ 1106.383113] callmodule: 0x08048475 id [4]
[ 1106.383118] callmodule: (( 0x08048000 is_vmalloc_addr 0 virt_addr_valid 0 ))
[ 1106.383130] callmodule: cont pid task a: eae90ca0 c: wtest p: [9404] s: runnable
[ 1106.383147] initcall callmodule_init+0x0/0x1000 [callmodule] returned with preemption imbalance
[ 1106.518074] callmodule: < exit
提供虚拟地址;但是,我们也需要提供该过程的register_user_hw_breakpoint
。有了这个,我可以在task_struct
输出中得到类似的内容:
ftrace
...其中与程序集对应的跟踪由断点ID标记。值得庆幸的是,正如预期的那样,它们是正确的;但是,...
597.907256 | 1) wtest-5339 | | handle_mm_fault() {
...
597.907310 | 1) wtest-5339 | + 35.627 us | }
597.907311 | 1) wtest-5339 | + 46.245 us | }
597.907312 | 1) wtest-5339 | + 56.143 us | }
597.907313 | 1) wtest-5339 | 1.039 us | up_read();
597.907317 | 1) wtest-5339 | 1.285 us | native_get_debugreg();
597.907319 | 1) wtest-5339 | 1.075 us | native_set_debugreg();
597.907322 | 1) wtest-5339 | 1.129 us | native_get_debugreg();
597.907324 | 1) wtest-5339 | 1.189 us | native_set_debugreg();
597.907329 | 1) wtest-5339 | | () {
597.907333 | 1) wtest-5339 | | /* callmodule: hwbp hit: id [3] */
597.907334 | 1) wtest-5339 | 5.567 us | }
597.907336 | 1) wtest-5339 | 1.123 us | native_set_debugreg();
597.907339 | 1) wtest-5339 | 1.130 us | native_get_debugreg();
597.907341 | 1) wtest-5339 | 1.075 us | native_set_debugreg();
597.907343 | 1) wtest-5339 | 1.075 us | native_get_debugreg();
597.907345 | 1) wtest-5339 | 1.081 us | native_set_debugreg();
597.907348 | 1) wtest-5339 | | () {
597.907350 | 1) wtest-5339 | | /* callmodule: hwbp hit: id [4] */
597.907351 | 1) wtest-5339 | 3.033 us | }
597.907352 | 1) wtest-5339 | 1.105 us | native_set_debugreg();
597.907358 | 1) wtest-5339 | 1.315 us | down_read_trylock();
597.907360 | 1) wtest-5339 | 1.123 us | _cond_resched();
597.907362 | 1) wtest-5339 | 1.027 us | find_vma();
597.907364 | 1) wtest-5339 | | handle_mm_fault() {
...
还在其间捕获了一些调试命令。无论如何,这是我想看到的。
以下是关于该模块的一些注意事项:
ftrace
中,那么我使用一个指向地址的函数指针;否则从源kallsyms
(就其本身而言,似乎有点不可靠),并将状态设置为SIGSTOP
)。
__TASK_STOPPED
挂在进程列表中很久就会自然终止,所以我猜这有效wtest
到达地址(内部通过页面)帧号); LDD3 ch.15有助于理解pfn和物理地址之间的关系。
page_to_phys()
的程序集输出计算偏移量 - 我不是100%确定这是正确的,尽管objdump
既不是0x08048000
也不是is_vmalloc_addr
;我想,这应该告诉我,一个人既没有使用virt_addr_valid
也没有vmalloc_to_pfn()
来到达其实际地址!?virt_to_page()
设置kprobes
有点棘手(需要复制功能)
ftrace
(例如0x639ec474),总是以&#34; 结果无法插入探测器(-22)&#34; < / LI>
kprobe
函数的kallsyms
地址(0xc10bcf60);这似乎有效 - 因为它引发致命的&#34; BUG:调度而原子&#34; (显然,我们并不打算在module_init中设置断点?)。 Bug是致命的,因为它使tracing_on()
目录从kprobes
调试目录消失了ftrace
不会使其显示在kprobe
日志中 - 它也需要启用;启用的必要代码就在那里 - 但由于之前的错误,我从未尝试过它ftrace
,HW_BREAKPOINT_X
需要设置为attr.bp_len
sizeof(long)
printk
变量 - 来自_init或来自处理程序 - 某些东西变得严重搞砸了,无论我接下来要打印什么变量,我都得到值0x5(或0x48)为它(?!)attr
< / LI>
就随机性而言,我认为这是因为该过程不是以停止状态开始的;当它停止时,它最终处于不同的状态(或者很可能,我在某处失去了一些锁定)。无论如何,你也可以期待bp->id
:
syslog
...也就是说,即使使用适当的任务指针(通过start_code判断),也只获得0x0作为物理地址。有时你会得到相同的结果,但[ 1661.815114] callmodule: Trying to walk page table; addr task 0xEAF68CA0 ->mm ->start_code: 0x08048000 ->end_code: 0x080485F4
[ 1661.815319] callmodule: walk_ 0x8048000 callmodule: Valid pgd : Valid pud: Valid pmd: page frame struct is @ f5772000; *virtual (page_address) @ c0000000 (is_vmalloc_addr 0 virt_addr_valid 1 virt_to_phys 0x0) page_to_pfn 0 page_to_phys 0x0
[ 1661.815837] callmodule: walk_ 0x80483c0 callmodule: Valid pgd : Valid pud: Valid pmd: page frame struct is @ f5772000; *virtual (page_address) @ c0000000 (is_vmalloc_addr 0 virt_addr_valid 1 virt_to_phys 0x0) page_to_pfn 0 page_to_phys 0x0
[ 1661.816846] callmodule: walk_ 0x8048474 callmodule: Valid pgd : Valid pud: Valid pmd: page frame struct is @ f5772000; *virtual (page_address) @ c0000000 (is_vmalloc_addr 0 virt_addr_valid 1 virt_to_phys 0x0) page_to_pfn 0 page_to_phys 0x0
。有时,即使pid可以,也无法获得start_code: 0x00000000 ->end_code: 0x00000000
:
task_struct
嗯,希望有人会评论并澄清这个模块[ 833.380417] callmodule:c: pid 7663
[ 833.380424] callmodule: everything all right; pid 7663 (7663)
[ 833.380430] callmodule: p is NULL - exiting
[ 833.516160] callmodule: < exit
的一些行为
希望这有助于某人,
干杯!
:)
:
Makefile
EXTRA_CFLAGS=-g -O0
obj-m += callmodule.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
:
callmodule.c