上一篇介绍了通过利用GDB和VBoxDBG很轻松的定位到了缺页(Page fault)中断处理函数,本篇将要介绍一个更灵活的工具,那就是GDB Python extension.
内核本身提供了几个GDB Python extension,位于script/gdb目录下。继续引用上一篇的例子。对x86平台来说,linux内核256个中断向量表是定义如下:
struct gate_struct {
u16 offset_low;
u16 segment;
struct idt_bits bits;
u16 offset_middle;
#ifdef CONFIG_X86_64
u32 offset_high;
u32 reserved;
#endif
} __attribute__((packed));
gate_desc idt_table[IDT_ENTRIES] __page_aligned_bss;
这个结构是和根据CPU手册定义的,我们可以通过gdb命令查看具体内容:
root@derek-test1:~/vagrant/kernel/obj/x86_64# gdb vmlinux
(gdb) target remote :1234
Remote debugging using :1234
kgdb_breakpoint () at /root/vagrant/linux/kernel/debug/debug_core.c:1073
1073 wmb(); /* Sync point after breakpoint */
(gdb) p/x idt_table[14]
$1 ={offset_low = 0x12c0, segment = 0x10, bits = {ist = 0x0, zero = 0x0, type = 0xe, dpl = 0x0, p = 0x1}, offset_middle = 0x8180, offset_high = 0xffffffff,
reserved = 0x0}
上一篇我们是通过VBoxGDB的DI命令直接获取到中断处理函数的地址的。通过GDB打印,则需要手工计算。
计算方法是: (offset_high<<32) | (offset_middle<<16) | offset_low,在这个例子中,我们可以得到处理函数地址是:0xffffffff818012c0. 现在我们可以用GDB找到源代码的位置:
(gdb) info symbol 0xffffffff818012c0
page_fault in section .text
(gdb) info line page_fault
Line 1158 of "/root/vagrant/linux/arch/x86/entry/entry_64.S" starts at address 0xffffffff81801273 <general_protection+3>
and ends at 0xffffffff818012c3 <page_fault+3>.
每次手工计算太麻烦,我们可以写一个GDB Python extension,然后一个命令就得到类似上面的输出。我们要实现的命令是lx-idt,用法如下:
(gdb) source /root/vagrant/linux/scripts/gdb/linux/idt.py
(gdb) lx-idt idt_table 14
interrupt: 0x0e: handler: page_fault @ 0xffffffff818012c0
symbol and line for /root/vagrant/linux/arch/x86/entry/entry_64.S, line 1158
(gdb) info line 0xffffffff818012c0
lx-idt接受2个参数,第一参数是中断向量表的名字,第二个是中断向量,这样我们就不用手工计算了。这个extension文件名为idt.py,第一行通过source命令加载它,现在来看看它的实现吧:
import gdb
from linux import utils
def x86_dump_idt(name, nr):
try:
idt=gdb.parse_and_eval(name)
except:
raise gdb.GdbError("symbol {} not found".format(name))
h = idt[nr]['offset_high'].cast(gdb.lookup_type('long'))
m = idt[nr]['offset_middle'].cast(gdb.lookup_type('long'))
l = idt[nr]['offset_low'].cast(gdb.lookup_type('long'))
addr = (m<<16) | l
sym = gdb.execute("info symbol 0x{:08x}{:08x}".format(int(h), int(addr)), True, True).split(" ")[0]
gdb.write("interrupt: 0x{:02x}: ".format(int(nr)))
gdb.write("handler: {} ".format(sym))
gdb.write('@ 0x{:08x}'.format(int(h)))
gdb.write('{:08x}'.format(int(addr)))
addr = (h<<32)|(m<<16) | l
gdb.write('\n')
r=gdb.find_pc_line(int(addr))
print(r)
class LxIDT(gdb.Command):
def __init__(self):
super(LxIDT, self).__init__("lx-idt", gdb.COMMAND_DATA)
def invoke(self, arg, from_tty):
if not utils.is_target_arch("x86"):
return
argv = gdb.string_to_argv(arg)
if len(argv) != 2:
raise gdb.GdbError("lx-idt takes 2 argument")
x86_dump_idt(argv[0], gdb.parse_and_eval(argv[1]))
LxIDT()
GDB Python extension的格式是固定的,
- class名LxIDT可以任意命名,但必须继承gdb.Command。
- 构造函数必须调用super(LxIDT, self).init("lx-idt", gdb.COMMAND_DATA), "lx-idt"就是命令。当在gdb中敲入lx-idt时,就会调用下面的invoke函数。
- invoke函数,参数保存在arg里面,lx-idx接受2个参数,这里做一些检查,然后调用x86_dump_idt,
- x86_dump_idt,接受2个参数,id_table是内核代码中,中断向量表的名字,nr是要查看的中断向量。
- 调用gdb.parse_and_eval(name)得到id_table的地址,保存在idt中。
- 访问idt[nr]['offset_high'], idt[nr]['offset_middle'], idt[nr]['offset_low'],并计算中断向量处理函数的地址。
- 调用gdb python API, gdb.execute和gdb.find_pc_line.
网友评论