3 # decode-panic - decode a Mac OS panic log to show source line numbers
4 # see the end of the file for full documentation and license.
7 use English qw( -no_match_vars ) ;
9 use File::Temp qw( tempdir );
17 my $panic_file = "/Library/Logs/panic.log";
23 my $kextload = "/sbin/kextload";
24 my $kextutil = "/usr/bin/kextutil";
26 my $gdb = "/usr/bin/gdb";
29 my $gdb_file = "gdb.input";
30 my $temp_dir = tempdir( "afsdebugXXXXXX", DIR => File::Spec->tmpdir,
31 TMPDIR => 1, CLEANUP => 1 );
32 my $dump_file = "/var/db/openafs/logs/crash.dump";
33 my $kernel = "/mach_kernel";
34 my $kextpath = "/Library/OpenAFS/Tools/root.client/usr/vice/etc/afs.kext/";
39 my $result = GetOptions ("input=s" => \$panic_file,
40 "output=s" => \$dump_file,
41 "kernel=s" => \$kernel,
42 "debugkit=s" => \$debugkit,
43 "archive=s"=> \$archive,
44 "util=s" => \$dmgutil,
45 "verbose" => \$option_verbose,
46 "quiet" => \$option_quiet,
47 "help" => \$option_help
51 pod2usage(-message => "Syntax error.",
60 pod2usage(-message => "",
67 # check for necessary programs & panic file
68 for my $program ( $gdb, $kextload ) {
69 if ( ! -x $program ) {
70 if ( $option_quiet ) {
73 croak "Can't find $program!\n"
79 $kextprog = $kextutil;
81 $kextprog = $kextload;
84 croak "Can't find panic file: $panic_file!\n" if ( ! -r $panic_file );
86 $crash_info{"warning"} = "";
88 read_panic( $panic_file, \%crash_info );
90 if ($crash_info{"kernel_version"} =~ /X86_64/ ) {
94 if ($crash_info{"kernel_version"} =~ /I386/ ) {
98 if ($crash_info{"kernel_version"} =~ /PPC/ ) {
105 if (-d $debugkit && -f $dmgutil ) {
106 extract_kernel( $crash_info{"kernel_version"}, $temp_dir, $debugkit, $dmgutil );
107 $kernel = "$temp_dir/mach_kernel";
110 if (-d $archive && -f $dmgutil ) {
111 extract_openafs( $crash_info{"afs_info"}, $temp_dir, $archive, $crash_info{"kernel_version"}, $dmgutil );
112 if (-d "$temp_dir/Library/OpenAFS/Debug/afs.kext" ) {
113 $kextpath = "$temp_dir/Library/OpenAFS/Debug/afs.kext";
115 $kextpath = "$temp_dir/Library/OpenAFS/Tools/root.client/usr/vice/etc/afs.kext";
119 generate_symbol_files( $crash_info{"afs_kernel_address"}, $temp_dir, $kextarch , $kernel, $kextpath);
121 write_gdb_input_file( $temp_dir, $gdb_file, $crash_info{ "backtrace" } );
123 if ($option_verbose) {
124 print "$gdb $gdbarch $kernel -batch -x $temp_dir/$gdb_file\n";
126 my $gdb_output = `$gdb $gdbarch $kernel -batch -x $temp_dir/$gdb_file`;
127 croak "gdb failed!\n" if $CHILD_ERROR;
129 write_dump_file( $dump_file, \%crash_info, $gdb_output );
131 sub extract_openafs {
132 my $oversion = shift;
134 my $oarchive = shift;
135 my $kversion = shift;
138 $kversion =~ /Darwin Kernel Version ([0-9]+).[0-9]+.[0-9]+:/;
142 $oversion =~ /org.openafs.filesystems.afs\(([0-9.]+)\)/;
146 if ($vers =~ /([0-9]+)f([0-9]+)/) {
147 $dmgvers = "$1" . "." . "$2";
151 my $odmg = "$oarchive/$vers/macos-10.${major}/OpenAFS-$vers-\*.dmg";
152 if ($option_verbose) {
153 print "$hdutil $odmg extractall OpenAFS.pkg $tempdir/OpenAFS.pkg\n";
155 `$hdutil $odmg extractall OpenAFS.pkg $tempdir/OpenAFS.pkg`;
156 if ($option_verbose) {
157 print "cd $tempdir && gzcat $tempdir/OpenAFS.pkg/Contents/Archive.pax.gz | pax -r ./Library/OpenAFS/Tools/root.client/usr/vice/etc/afs.kext\n";
159 `cd $tempdir && gzcat $tempdir/OpenAFS.pkg/Contents/Archive.pax.gz | pax -r ./Library/OpenAFS/Tools/root.client/usr/vice/etc/afs.kext`;
160 if ($option_verbose) {
161 print "$hdutil $odmg extractall OpenAFS-debug-extension.pkg $tempdir/OpenAFS-debug-extension.pkg\n";
163 `$hdutil $odmg extractall OpenAFS-debug-extension.pkg $tempdir/OpenAFS-debug-extension.pkg`;
164 if (-f "$tempdir/OpenAFS-debug-extension.pkg/Contents/Archive.pax.gz" ) {
165 if ($option_verbose) {
166 print "cd $tempdir && gzcat $tempdir/OpenAFS-debug-extension.pkg/Contents/Archive.pax.gz | pax -r ./Library/OpenAFS/Debug/afs.kext.dSYM\n";
168 `cd $tempdir && gzcat $tempdir/OpenAFS-debug-extension.pkg/Contents/Archive.pax.gz | pax -r ./Library/OpenAFS/Debug/afs.kext.dSYM`;
169 if ($option_verbose) {
170 print "cd $tempdir && gzcat $tempdir/OpenAFS-debug-extension.pkg/Contents/Archive.pax.gz | pax -r ./Library/OpenAFS/Debug/afs.kext\n";
172 `cd $tempdir && gzcat $tempdir/OpenAFS-debug-extension.pkg/Contents/Archive.pax.gz | pax -r ./Library/OpenAFS/Debug/afs.kext`;
177 my $kversion = shift;
179 my $debugarchive = shift;
182 $kversion =~ /Darwin Kernel Version ([0-9]+).([0-9]+).[0-9]+:/;
186 my $kdk = "$debugarchive/kernel_debug_kit_10.${major}.${minor}_\*.dmg";
187 if ($option_verbose) {
188 print "$hdutil $kdk extractall System.kext $tempdir/System.kext\n";
190 `$hdutil $kdk extractall System.kext $tempdir/System.kext`;
191 if ($option_verbose) {
192 print "$hdutil $kdk extractall mach_kernel.dSYM $tempdir/mach_kernel.dSYM\n";
194 `$hdutil $kdk extractall mach_kernel.dSYM $tempdir/mach_kernel.dSYM`;
195 if ($option_verbose) {
196 print "$hdutil $kdk extract mach_kernel $tempdir/mach_kernel\n";
198 `$hdutil $kdk extract mach_kernel $tempdir/mach_kernel`;
202 # read the panic file and parse out the addresses
205 my $filename = shift;
206 my $hash_ref = shift;
210 my @panic_section_positions = ( 0 );
212 my $panic_fh = IO::File->new( $filename, '<' )
213 or croak "Can't open backtrace file $filename: $OS_ERROR\n";
215 # find the last panic section as denoted by "*********"
216 while ( $line = <$panic_fh> ) {
218 if ( $line eq "*********" ) {
221 push @panic_section_positions, $panic_fh->tell;
225 # ignore the empty last section
226 if ( @panic_section_positions > 2 ) {
227 pop @panic_section_positions
230 # Seek to last full panic section
231 # or the beginning of the file if appropriate
232 $panic_fh->seek( $panic_section_positions[-1], 0 );
234 $hash_ref->{ "date" } = <$panic_fh>;
235 chomp $hash_ref->{ "date" };
237 while ( $line = <$panic_fh> ) {
240 #skip lines until "Backtrace" is seen
241 $line =~ /^\s*(Backtrace,|Backtrace:|Backtrace \()/;
247 if ( $option_quiet ) {
250 croak "Couldn't find a backtrace in $filename\n";
254 # gather the backtrace addresses
255 if ( $backtrace eq "Backtrace:" ) {
257 while ( $line = <$panic_fh> ) {
259 last if $line !~ /^\s*(0x[0-9a-fA-F]+)/;
260 my @memory_addresses = split /\s+/, $line;
262 # add non-empty array elements to the list
263 push @{ $hash_ref->{ "backtrace" } },
264 grep { /0x/ } @memory_addresses;
268 while ( $line = <$panic_fh> ) {
270 last if $line !~ /^\s*0x[0-9a-fA-F]+ : (0x[0-9a-fA-F]*)/;
271 push @{ $hash_ref->{ "backtrace" } }, $1;
275 # now we need the address for the afs kernel module
276 while ( $line = <$panic_fh> ) {
278 last if ($line =~ /^BSD\s+process/ );
279 next if ( $line !~ /org\.openafs\.filesystems\.afs/ );
281 $kernel_line = $line;
282 $line =~ /\@(0x[0-9a-fA-F]+)/;
283 $hash_ref->{ "afs_kernel_address" } = $1;
284 $kernel_line =~ /^\s+([^@]+)@.+/;
285 $hash_ref->{ "afs_info" } = $1;
290 # grab the kernel version
291 while ( $line = <$panic_fh> ) {
293 next if ( $line !~ /^Darwin Kernel Version/ );
294 $hash_ref->{ "kernel_version" } = $line;
298 if (! $kernel_line ) {
300 while ( $line = <$panic_fh> ) {
302 last if ( $line =~ /^loaded\s+kexts:/ );
303 next if ( $line !~ /org\.openafs\.filesystems\.afs/ );
304 $kernel_line = $line;
305 $line =~ /org\.openafs\.filesystems\.afs\s+([^@]+)\s+\(addr\s+(0x[0-9a-fA-F]+),/;
306 $hash_ref->{ "afs_kernel_address" } = $2;
307 $hash_ref->{ "afs_info" } = "org.openafs.filesystems.afs(" . $1 . ")\@0x" . $2;
308 $hash_ref->{ "warning" } = "MODULE WAS UNLOADED!\n";
313 or croak "Can't close file $filename: $OS_ERROR\n";
315 if ( !$kernel_line ) {
316 if ( $option_quiet ) {
319 croak "No OpenAFS reference found in latest panic!";
324 # generate the symbol files that will be read by gdb
325 sub generate_symbol_files {
326 my $kernel_address = shift;
327 my $symbol_write_dir = shift;
328 my $kextarch = shift;
332 if ($kextprog eq $kextload) {
333 if ($kernel eq "/mach_kernel") {
334 if ($option_verbose) {
335 print "$kextprog -k $kernel -s $temp_dir -a org.openafs.filesystems.afs\@${kernel_address} -n $kext\n";
340 "-a", 'org.openafs.filesystems.afs@' . $kernel_address,
343 if ($option_verbose) {
344 print "$kextprog -c -e -r $temp_dir -k $kernel -s $temp_dir -a org.openafs.filesystems.afs\@${kernel_address} -n $kext\n";
351 "-a", 'org.openafs.filesystems.afs@' . $kernel_address,
355 if ($kernel eq "/mach_kernel") {
356 if ($option_verbose) {
357 print "$kextprog -k $kernel -s $temp_dir -arch $kextarch -a org.openafs.filesystems.afs\@${kernel_address} -n $kext\n";
363 "-a", 'org.openafs.filesystems.afs@' . $kernel_address,
366 if ($option_verbose) {
367 print "$kextprog -c -e -r $temp_dir -k $kernel -s $temp_dir -arch $kextarch -a org.openafs.filesystems.afs\@${kernel_address} -n $kext\n";
375 "-a", 'org.openafs.filesystems.afs@' . $kernel_address,
379 if ( $CHILD_ERROR ) {
381 croak "kextload failed to run: $OS_ERROR\n";
386 sub write_gdb_input_file {
388 my $write_dir = shift;
389 my $filename = shift;
390 my $backtrace_ref = shift;
392 my @symbol_files = ( $write_dir . "/org.openafs.filesystems.afs.sym" );
394 my $fh = IO::File->new( $write_dir . "/" . $filename, '>' )
395 or croak "Can't open gdb file $filename for writing: $OS_ERROR\n";
397 for my $symbol ( @symbol_files ) {
398 print $fh "add-symbol-file $symbol\n";
401 print $fh "set print asm-demangle on\n";
403 for my $address ( @{ $backtrace_ref } ) {
404 print $fh "x/i $address\n";
408 or croak "Can't close file $filename: $OS_ERROR\n";
411 # write out the pertinent details to a file.
412 sub write_dump_file {
413 my $filename = shift;
414 my $hash_ref = shift;
417 my $log_dir = dirname $filename;
419 if ( ! -d $log_dir ) {
420 mkdir $log_dir, 0755;
421 croak "Can't create directory $log_dir: $OS_ERROR\n" if $CHILD_ERROR;
424 croak "Can't write to folder $log_dir." if ( ! -w $log_dir );
426 my $fh = IO::File->new( $filename, '>', 0664 )
427 or croak "Can't open dump file $filename for writing: $OS_ERROR\n";
429 print $fh "Panic Date: ", $hash_ref->{ "date" }, "\n";
430 print $fh "Kernel Version: ", $hash_ref->{ "kernel_version" }, "\n";
431 print $fh "OpenAFS Version: ", $hash_ref->{ "afs_info" }, "\n";
432 print $fh $hash_ref->{ "warning" };
433 print $fh "=============\n";
437 or croak "Can't close file $filename: $OS_ERROR\n";
444 decode-panic - decode a Mac OS panic log to show source line numbers
448 This documentation refers to decode-panic version $Revision$
452 decode-panic [-i <input panic log>] [-o <output dump file>] [-k <kernel file>] [-d <kernel debug kit archive>] [-a <openafs package archive>] [-u <path to hdutil>] [-q] [-v]
456 -i The path to the panic log that should be read
457 -o The path to where the decoded panic log should be written
458 -k The path to the kernel image corresponding to the panic
459 -d The path to a directory containing kernel debug kit dmgs
460 -a The path to an archive of OpenAFS installer dmgs
461 -u The path to the hdutil dmg utility program
462 -q Quiet mode - don't complain if there is a problem.
463 -v Verbose mode - print all commands.
468 This tool parses the panic log for Mac OS X kernel panics that are caused by
469 openafs in order to produce a human-readable backtrace.
471 This program uses crash isolation procedure as outlined in
472 http://developer.apple.com/technotes/tn2002/tn2063.html#IsolatingCrash
474 Here is an example file that is fed to gdb:
476 add-symbol-file /tmp/afsdebugt8dGOb/org.openafs.filesystems.afs.sym
477 set print asm-demangle on
481 Panic logs can be found in /Library/Logs/panic.log in 10.4 (Tiger),
482 /Library/Logs/PanicReporter/YYYY-MM-DD-HHMMSS.panic in 10.5 (Leopard),
483 and /Library/Logs/DiagnosticReports/Kernel_YYYY-MM-DD-HHMMSS.panic in 10.6
488 This program needs gdb and kextload; Starting in SnowLeopard, it needs kextutil.
490 Batch decoding requires a directory of Kernel Debug Kit DMGs, a directory of
491 OpenAFS installer DMGs, and the DMG extraction utility currently available
492 in source form at http://www.dementia.org/~shadow/dmgutil-0.1.tar.gz
494 =head1 BUGS AND LIMITATIONS
496 decode-panic clobbers the output file.
500 Copyright 2008-2010. Jason Edgecombe <jason@rampaginggeek.com> and others.
502 This documentation is covered by the BSD License as written in the
503 doc/LICENSE file in the OpenAFS source tree. This program was originally
504 written by Jason Edgecombe for OpenAFS.