3 # decode as much as possible an application printk core dump
4 # to speed debugging, takes an optional romfs-inst.log that is usually
5 # created in your images directory in order to provide symbols by name
6 # rather than just addresses. Can handle multiple core dumps in the input
9 # the output will do the following:
11 # 1. Find the PC, if possible and print the symbol and/or address within
12 # the application or library as required to debug further.
14 # 2. Find the return address, if possible and print the symbol and/or
15 # address within the application or library as required to debug further.
17 # 3. for every value on the stack, see if it lies within the application
18 # and if so print the text/data address and symbol for it if found. The
19 # stack is printed in ascending order so you get a rudimentary stack
22 # david_mccullough@securecomputing.com
25 package require cmdline
27 {m.arg "" "lsmod output"}
30 "\[options\] core-dump-text-file \[romfs-inst.log\]\n" \
31 " Decode as much as possible from a file containing SH\n" \
32 " or ARM core dump traces. The coredump text can be\n" \
33 " prefixed with misc junk, ie., syslog infomation.\n" \
35 array set params [::cmdline::getoptions argv $options $usage]
37 if {[llength $argv] < 1} {
38 puts [::cmdline::usage $options $usage]
42 # some globals, "ra" or return address may not be possible on all archs
45 # parse the printk coredump output, ignoring any leading garbage as found
48 proc load_coredump {filename} {
49 set f [open $filename]
54 while {[gets $f line] >= 0} {
55 if {[regexp -nocase {Internal error: Oops:} $line dummy]
56 || [regexp -nocase {WARNING: at} $line dummy]
57 || [regexp -nocase {Oops\[.*\]:} $line dummy]
58 || [regexp -nocase {snapdog: expired} $line dummy]
59 || [regexp -nocase {Unhandled kernel unaligned access\[.*\]:} $line dummy]} {
62 if {[info exists ::segments(lsmod)]} {
63 set ::segments($::coredumps) $::segments(lsmod)
65 set ::segments($::coredumps) {}
68 if {[info exists ::segments(vmlinux)]} {
69 lappend ::segments($::coredumps) $::segments(vmlinux)
71 set ::pc($::coredumps) 0
72 set ::ra($::coredumps) 0
73 set ::stack($::coredumps) {}
74 set ::backtrace($::coredumps) {}
78 if {[regexp -nocase {STACK DUMP} $line dummy]} {
81 set ::pc($::coredumps) 0
82 set ::ra($::coredumps) 0
83 set ::backtrace($::coredumps) {}
88 if {$stackdump && [regexp {^.*(0x)*[0-9a-f]+:([0-9a-fA-F ]+)$} $line dummy dummy2 addrs]} {
89 append ::stack($::coredumps) $addrs
93 if {$stackdump && [regexp {^ *([ [:xdigit:]]+)$} $line dummy addrs]} {
94 append ::stack($::coredumps) "$addrs "
99 # things are backwards in a 2.4 oops at least
100 if {[regexp -nocase {Stack:} $line dummy]} {
105 if {[regexp -nocase {Stack : ([ [:xdigit:]]+)$} $line dummy addrs]} {
106 append ::stack($::coredumps) $addrs
110 if {[regexp -nocase {Backtrace:} $line dummy]} {
118 # The SH program counter
119 if {[regexp {PC *: *([0-9a-f]+)} $line dummy val]} {
120 set ::pc($::coredumps) 0x$val
122 # The ARM program counter
123 if {[regexp {pc *: *\[<([0-9a-f]+)>\]} $line dummy val]} {
124 set ::pc($::coredumps) 0x$val
126 # The i386 program counter
127 if {[regexp {EIP:[ 0-9a-fA-F]*:*\[<([0-9a-f]+)>\]} $line dummy val]} {
128 set ::pc($::coredumps) 0x$val
130 # The MIPS program counter
131 if {[regexp {epc *: *([0-9a-f]+)} $line dummy val]} {
132 set ::pc($::coredumps) 0x$val
134 # The SH return address
135 if {[regexp {PR *: *([0-9a-f]+)} $line dummy val]} {
136 set ::ra($::coredumps) 0x$val
138 # The ARM return address
139 if {[regexp {lr *: *[[]<([0-9a-f]+)>\]} $line dummy val]} {
140 set ::ra($::coredumps) 0x$val
142 # The MIPS return address register
143 if {[regexp {^\$24:.* ([0-9a-f]+)$} $line dummy val]} {
144 set ::ra($::coredumps) 0x$val
146 # The MIPS64 return address register
147 if {[regexp {ra *: *([[:xdigit:]]+)} $line dummy val]} {
148 set ::ra($::coredumps) 0x$val
150 # check for an executable dump segment
151 if {[regexp {([0-9a-f]+)-([0-9a-f]+) r[-w][-x]p .* (/[^ ]*)\W*$} $line dummy from to segment]} {
152 lappend ::segments($::coredumps) [list $segment 0x$from 0x$to]
155 if {[regexp {^Function entered at \[<([0-9a-f]+)>\] from \[\<([0-9a-f]+)>\]$} $line dummy at from]} {
156 lappend ::backtrace($::coredumps) [list 0x$at 0x$from]
159 if {[regexp {^(Call Trace:)?\[<([[:xdigit:]]+)>\] 0x([[:xdigit:]]+) *$} $line dummy dummy at from]} {
160 lappend ::backtrace($::coredumps) [list 0x$at 0x$from]
166 # load the lsmod output, converting the data into segments
167 proc load_lsmod {filename} {
168 set f [open $filename]
169 while {[gets $f line] >= 0} {
170 if {[regexp {^([^ ]+)\s+(\d+)\s+.*\s+(0x[0-9a-f]{8})(?:\s+.*)?$} $line dummy name size from]} {
171 set to [format 0x%x [expr $from + $size]]
172 lappend ::segments(lsmod) [list ${name}.ko $from $to]
178 # load the romfs log, converting the data into actually executable names
179 proc load_romfslog {filename} {
180 if {[regexp -nocase {vmlinux} $filename dummy]} {
181 set ::binaries(vmlinux) $filename
184 set f [open $filename]
185 while {[gets $f line] >= 0} {
186 if {[regexp {^([^ ]+)\W*/.*/romfs/(.*)$} $line dummy src bin]} {
187 set ::binaries([string tolower /$bin]) $src
188 if {[regexp {.*/(.+)} $bin dummy file]} {
189 set ::binaries([string tolower $file]) $src
196 # load the symbols from an executable
197 proc load_syms {filename} {
198 if {[info exists ::syms($filename)]} {
201 set f [open "|nm -nv -C $filename"]
203 while {[gets $f line] >= 0} {
204 if {[regexp {^0*([[:xdigit:]]+)\W+\w+\W+([^$].*)$} $line dummy addr sym]} {
205 if {[string length $sym] > 1} {
206 lappend s [list 0x$addr $sym]
211 set ::syms($filename) $s
213 set f [open "|strings -t x $filename"]
215 while {[gets $f line] >= 0} {
216 if {[regexp {^[0 ]*([[:xdigit:]]+)[ ]+(.*)$} $line dummy addr str]} {
217 lappend s [list 0x$addr $str]
221 set ::strings($filename) $s
224 proc load_segment {seg} {
225 if {![info exists ::binaries([string tolower $seg])]} {
228 set filename $::binaries([string tolower $seg])
230 if {![info exists ::syms($filename)]} {
233 set from [lindex [lindex $::syms($filename) 0] 0]
234 set to [lindex [lindex $::syms($filename) end] 0]
235 set ::segments($seg) [list $seg $from $to]
238 # find a symbol in the currently loaded data if possible
239 proc find_sym {seg addr} {
240 if {![info exists ::binaries([string tolower $seg])]} {
243 set filename $::binaries([string tolower $seg])
245 if {![info exists ::syms($filename)]} {
249 foreach sym $::syms($filename) {
250 set val [lindex $sym 0]
254 set last [lindex $sym 1]
259 # find a symbol in the currently loaded data if possible
260 proc find_str {seg addr} {
261 if {![info exists ::binaries([string tolower $seg])]} {
264 set filename $::binaries([string tolower $seg])
266 if {![info exists ::syms($filename)]} {
271 foreach str $::strings($filename) {
272 set val [lindex $str 0]
276 set last "[lindex $str 1]"
277 if {$addr - $val < [string length $last]} {
278 set ret "\"[string range $last [expr $addr - $val] [string length $last]]\""
284 # given an address find the part of the executable (app/lib) it is in
285 proc find_segment {segments addr} {
286 foreach segment $segments {
287 set seg [lindex $segment 0]
288 set from [lindex $segment 1]
289 set to [lindex $segment 2]
290 if {$addr >= $from && $addr < $to} {
291 if {[regexp {\.[ks]o} $seg dummy]} {
292 set saddr [expr $addr - $from]
296 set sym "[find_sym $seg $saddr]"
297 set str "[find_str $seg $saddr]"
298 return "[format %x $saddr]($sym) in $seg ($from - $to) $str"
304 # process a printk core dump output giving as much info as possible
305 proc examine_coredump {filename} {
306 load_coredump $filename
308 for {set i 1} {$i <= $::coredumps} {incr i} {
309 puts "---------------------- dump $i -------------------"
310 puts "Possible PC: [find_segment $::segments($i) $::pc($i)]"
311 puts "Possible caller: [find_segment $::segments($i) $::ra($i)]"
313 puts "Possible Backtrace:"
314 foreach addr [split $::stack($i)] {
315 set val [find_segment $::segments($i) 0x$addr]
321 foreach addr [split $::stack($i)] {
322 if {[regexp {([0-9A-Fa-f]{2})([0-9A-Fa-f]{2})([0-9A-Fa-f]{2})([0-9A-Fa-f]{2})} $addr all one two three four]} {
323 foreach char "$one $two $three $four" {
324 if {"0x$char" > 0x1f && "0x$char" < 0x7f} {
325 puts -nonewline [format "%c" 0x$char]
333 foreach backtrace $::backtrace($i) {
334 set at [find_segment $::segments($i) [lindex $backtrace 0]]
335 set from [find_segment $::segments($i) [lindex $backtrace 1]]
337 set at [lindex $backtrace 0]
340 set from [lindex $backtrace 1]
342 puts "Function entered at $at from $from"
349 set coredump [lindex $argv 0]
350 foreach file [lrange $argv 1 end] {
353 if {$params(m) != ""} {
354 load_lsmod $params(m)
356 puts [examine_coredump $coredump]