Skip to content

Commit eb1dd55

Browse files
committed
kdump: initial basic target
This module contains the bare minimum to load an x86_64 kernel crash dump and populate the thread list with thread info and stacks. Signed-off-by: Jeff Mahoney <[email protected]>
1 parent e41e4da commit eb1dd55

5 files changed

Lines changed: 192 additions & 0 deletions

File tree

crash/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
#!/usr/bin/env python
2+
# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79:

crash/kdump/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
#!/usr/bin/env python
2+
# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79:

crash/kdump/target.py

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
#!/usr/bin/env python
2+
# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79:
3+
4+
import gdb
5+
from kdumpfile import kdumpfile
6+
from util import list_for_each_entry
7+
from kdumpfile.exceptions import *
8+
9+
#arch = "i386:x86-64"
10+
#
11+
#setup = {
12+
# 'i386:x86-64' : setup_thread_amd64,
13+
#}
14+
15+
ulong_type = gdb.lookup_type('unsigned long')
16+
rip = gdb.lookup_minimal_symbol("thread_return").value()
17+
18+
def setup_thread_amd64(thread, task):
19+
rsp = task['thread']['sp'].cast(ulong_type.pointer())
20+
rbp = rsp.dereference().cast(ulong_type.pointer())
21+
rbx = (rbp - 1).dereference()
22+
r12 = (rbp - 2).dereference()
23+
r13 = (rbp - 3).dereference()
24+
r14 = (rbp - 4).dereference()
25+
r15 = (rbp - 5).dereference()
26+
27+
# The two pushes that don't have CFI info
28+
# rsp += 2
29+
30+
# ex = in_exception_stack(rsp)
31+
# if ex:
32+
# print "EXCEPTION STACK: pid %d" % task['pid']
33+
34+
thread.registers['rsp'].value = rsp
35+
thread.registers['rbp'].value = rbp
36+
thread.registers['rip'].value = rip
37+
thread.registers['rbx'].value = rbx
38+
thread.registers['r12'].value = r12
39+
thread.registers['r13'].value = r13
40+
thread.registers['r14'].value = r14
41+
thread.registers['r15'].value = r15
42+
thread.registers['cs'].value = 2*8
43+
thread.registers['ss'].value = 3*8
44+
45+
def symbol_func(symname):
46+
ms = gdb.lookup_minimal_symbol(symname)
47+
if not ms:
48+
print ("Cannot lookup symbol %s" % symname)
49+
raise RuntimeError("Cannot lookup symbol %s" % symname)
50+
return long(ms.value())
51+
52+
class Target(gdb.Target):
53+
def __init__(self, fil):
54+
if isinstance(fil, str):
55+
fil = file(fil)
56+
self.fil = fil
57+
print "kdump (%s)" % fil
58+
self.kdump = kdumpfile(fil)
59+
self.kdump.symbol_func = symbol_func
60+
self.kdump.vtop_init()
61+
super(Target, self).__init__()
62+
gdb.execute('set print thread-events 0')
63+
self.setup_tasks()
64+
65+
def setup_tasks(self):
66+
init_task = gdb.lookup_global_symbol('init_task')
67+
task_list = init_task.value()['tasks']
68+
69+
self.pid_to_task_struct = {}
70+
71+
for task in list_for_each_entry(task_list, init_task.type, 'tasks'):
72+
thread = gdb.selected_inferior().new_thread((1, task['pid'], 0), task)
73+
thread.name = task['comm'].string()
74+
75+
gdb.selected_inferior().executing = False
76+
77+
def to_xfer_partial(self, obj, annex, readbuf, writebuf, offset, ln):
78+
ret = -1
79+
if obj == self.TARGET_OBJECT_MEMORY:
80+
try:
81+
r = self.kdump.read (self.kdump.KDUMP_KVADDR, offset, ln)
82+
readbuf[:] = r
83+
ret = ln
84+
except EOFException, e:
85+
raise gdb.TargetXferEof(str(e))
86+
except NoDataException, e:
87+
raise gdb.TargetXferUnavailable(str(e))
88+
else:
89+
raise IOError("Unknown obj type")
90+
return ret
91+
92+
def to_thread_alive(self, ptid):
93+
return 1
94+
95+
def to_pid_to_str(self, ptid):
96+
return "pid %d" % ptid[1]
97+
98+
def to_fetch_registers(self, register):
99+
thread = gdb.selected_thread()
100+
setup_thread_amd64(thread, thread.info)
101+
return True
102+
103+
def to_prepare_to_store(self, thread):
104+
pass
105+
106+
# We don't need to store anything; The regcache is already written.
107+
def to_store_registers(self, thread):
108+
pass
109+
110+
def to_has_execution(self, ptid):
111+
return False

crash/kdump/util.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
#!/usr/bin/env python
2+
# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79:
3+
4+
import gdb
5+
6+
charp = gdb.lookup_type('char').pointer()
7+
8+
def resolve_type(val):
9+
if isinstance(val, str):
10+
gdbtype = gdb.lookup_gdbtype(val)
11+
elif isinstance(val, gdb.Value):
12+
gdbtype = val.gdbtype
13+
else:
14+
gdbtype = val
15+
return gdbtype
16+
17+
def offsetof(val, member):
18+
gdbtype = resolve_type(val)
19+
if not isinstance(val, gdb.Type):
20+
raise TypeError("offsetof requires gdb.Type or a string/value that can be used to lookup a gdb.Type")
21+
22+
return gdbtype[member].bitpos >> 3
23+
24+
def container_of(val, gdbtype, member):
25+
gdbtype = resolve_type(gdbtype)
26+
offset = offsetof(gdbtype, member)
27+
return (val.cast(charp) - offset).cast(gdbtype.pointer()).dereference()
28+
29+
list_head_type = gdb.lookup_type("struct list_head")
30+
def list_for_each(list_head):
31+
if list_head.type == list_head_type.pointer():
32+
list_head = list_head.dereference()
33+
elif list_head.type != list_head_type:
34+
raise gdb.GdbError("Must be struct list_head not %s" % list_head.type)
35+
36+
node = list_head['next'].dereference()
37+
while node.address != list_head.address:
38+
yield node.address
39+
node = node['next'].dereference()
40+
41+
def list_for_each_entry(list_head, gdbtype, member):
42+
for node in list_for_each(list_head):
43+
if node.type != list_head_type.pointer():
44+
raise TypeError("Type %s found. Expected struct list_head *." % node.type)
45+
yield container_of(node, gdbtype, member)
46+
47+
def per_cpu(symbol, cpu):
48+
if isinstance(symbol, str):
49+
symbol = gdb.lookup_global_symbol(symbol).value()
50+
elif isinstance(symbol, gdb.Symbol):
51+
symbol = symbol.value()
52+
else:
53+
raise TypeError("Must be string or gdb.Symbol")
54+
55+
percpu_addr = symbol.address.cast(charp) + per_cpu_offset[cpu]
56+
return percpu_addr.cast(symbol.type.pointer()).dereference()

setup.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#!/usr/bin/env python
2+
# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79:
3+
4+
from setuptools import setup, find_packages
5+
6+
setup(
7+
name = "crash",
8+
version = "0.1",
9+
packages = find_packages(),
10+
package_data = {
11+
'' : [ "*.dist" "*.txt" ],
12+
},
13+
14+
install_requires = [],
15+
16+
author = "Jeff Mahoney",
17+
author_email = "[email protected]",
18+
description = "Python Linux Kernel Crash dump forensic tools",
19+
license = "GPL v2 only",
20+
21+
)

0 commit comments

Comments
 (0)