Import Upstream version 1.8.5
[hcoop/debian/openafs.git] / src / packaging / MacOS / decode-panic
1 #!/usr/bin/perl
2
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.
5
6 use Carp;
7 use English qw( -no_match_vars ) ;
8 use File::Basename;
9 use File::Temp qw( tempdir );
10 use Getopt::Long;
11 use IO::Dir;
12 use IO::File;
13 use Pod::Usage;
14 use bigint qw/hex/;
15 use warnings;
16 use strict;
17
18 my $panic_file = "/Library/Logs/panic.log";
19 my %crash_info;
20 my $backtrace;
21 my $debugkit = "";
22 my $archive = "";
23 my $dmgutil = "";
24 my $kextload = "/sbin/kextload";
25 my $kextutil = "/usr/bin/kextutil";
26 my $kextprog;
27 my $gdb = "/usr/bin/gdb";
28 my $gdbarch = "";
29 my $kextarch = "";
30 my $gdb_file = "gdb.input";
31 my $temp_dir = tempdir( "afsdebugXXXXXX", DIR => File::Spec->tmpdir,
32 TMPDIR => 1, CLEANUP => 1 );
33 my $dump_file = "/var/db/openafs/logs/crash.dump";
34 my $kernel = "/mach_kernel";
35 my $kextpath = "/Library/OpenAFS/Tools/root.client/usr/vice/etc/afs.kext";
36 my $sysextpath = "";
37
38 my $option_quiet;
39 my $option_verbose;
40 my $option_help;
41 my $result = GetOptions ("input=s" => \$panic_file,
42 "output=s" => \$dump_file,
43 "sysexts=s" => \$sysextpath,
44 "kernel=s" => \$kernel,
45 "debugkit=s" => \$debugkit,
46 "archive=s"=> \$archive,
47 "util=s" => \$dmgutil,
48 "verbose" => \$option_verbose,
49 "quiet" => \$option_quiet,
50 "help" => \$option_help
51 );
52
53 if ( !$result ) {
54 pod2usage(-message => "Syntax error.",
55 -exitval => 2,
56 -verbose => 1,
57 -output => \*STDERR);
58
59 exit;
60 }
61
62 if ($option_help) {
63 pod2usage(-message => "",
64 -exitval => 2,
65 -verbose => 3,
66 -output => \*STDERR);
67 exit;
68 }
69
70 # check for necessary programs & panic file
71 for my $program ( $gdb, $kextload ) {
72 if ( ! -x $program ) {
73 if ( $option_quiet ) {
74 exit 1;
75 } else {
76 croak "Can't find $program!\n"
77 }
78 }
79 }
80
81 if ( -x $kextutil ) {
82 $kextprog = $kextutil;
83 } else {
84 $kextprog = $kextload;
85 }
86
87 croak "Can't find panic file: $panic_file!\n" if ( ! -r $panic_file );
88
89 $crash_info{"warning"} = "";
90
91 read_panic( $panic_file, \%crash_info );
92
93 if ($crash_info{"kernel_version"} =~ /X86_64/ ) {
94 $gdbarch="-a x86_64";
95 $kextarch="x86_64";
96 } else {
97 if ($crash_info{"kernel_version"} =~ /I386/ ) {
98 $gdbarch="-a i386";
99 $kextarch="i386";
100 } else {
101 if ($crash_info{"kernel_version"} =~ /PPC/ ) {
102 $gdbarch="-a ppc";
103 $kextarch="ppc";
104 }
105 }
106 }
107
108 if (-d $debugkit && -f $dmgutil ) {
109 extract_kernel( $crash_info{"kernel_version"}, $temp_dir, $debugkit, $dmgutil );
110 $kernel = "$temp_dir/mach_kernel";
111 }
112
113 if (-d $archive && -f $dmgutil ) {
114 extract_openafs( $crash_info{"afs_info"}, $temp_dir, $archive, $crash_info{"kernel_version"}, $dmgutil );
115 if (-d "$temp_dir/Library/OpenAFS/Debug/afs.kext" ) {
116 $kextpath = "$temp_dir/Library/OpenAFS/Debug/afs.kext";
117 } else {
118 $kextpath = "$temp_dir/Library/OpenAFS/Tools/root.client/usr/vice/etc/afs.kext";
119 }
120 }
121
122 generate_symbol_files( $crash_info{"afs_kernel_address"}, $crash_info{"kernel_shift"}, $temp_dir, $kextarch , $kernel, $kextpath, $sysextpath);
123
124 write_gdb_input_file( $temp_dir, $gdb_file, $crash_info{ "backtrace" }, $crash_info{ "kernel_shift" } );
125
126 # needed so we can put the sym file where the kext is. ick.
127 if ($kextprog eq $kextutil) {
128 `cp -R $kextpath $temp_dir`;
129 }
130
131 if ($option_verbose) {
132 print "$gdb $gdbarch $kernel -batch -x $temp_dir/$gdb_file\n";
133 my $gdbi_fh = IO::File->new( $temp_dir . "/" . $gdb_file, '<' )
134 or croak "Can't open backtrace file $gdb_file: $OS_ERROR\n";
135 while (my $line = <$gdbi_fh> ) {
136 print $line;
137 }
138 $gdbi_fh->close()
139 or croak "Can't close file $gdb_file: $OS_ERROR\n";
140 }
141 my $gdb_output = `$gdb $gdbarch $kernel -batch -x $temp_dir/$gdb_file`;
142 croak "gdb failed!\n" if $CHILD_ERROR;
143
144 write_dump_file( $dump_file, \%crash_info, $gdb_output );
145
146 sub extract_openafs {
147 my $oversion = shift;
148 my $tempdir = shift;
149 my $oarchive = shift;
150 my $kversion = shift;
151 my $hdutil = shift;
152
153 $kversion =~ /Darwin Kernel Version ([0-9]+).[0-9]+.[0-9]+:/;
154 my $major = $1;
155 $major -= 4;
156
157 $oversion =~ /org.openafs.filesystems.afs\(([0-9.abcf]+)\)/;
158 my $vers = $1;
159 $vers =~ s/fc/pre/;
160 $vers =~ s/f/pre/;
161 my $dmgvers = $vers;
162 if ($vers =~ /([0-9]+)f([0-9]+)/) {
163 $dmgvers = "$1" . "." . "$2";
164 $vers = "$1";
165 }
166
167 my $odmg = "$oarchive/$vers/macos-10.${major}/OpenAFS-$vers-\*.dmg";
168 if ($option_verbose) {
169 print "$hdutil $odmg extractall OpenAFS.pkg $tempdir/OpenAFS.pkg\n";
170 }
171 `$hdutil $odmg extractall OpenAFS.pkg $tempdir/OpenAFS.pkg`;
172 if ($option_verbose) {
173 print "cd $tempdir && gzcat $tempdir/OpenAFS.pkg/Contents/Archive.pax.gz | pax -r ./Library/OpenAFS/Tools/root.client/usr/vice/etc/afs.kext\n";
174 }
175 `cd $tempdir && gzcat $tempdir/OpenAFS.pkg/Contents/Archive.pax.gz | pax -r ./Library/OpenAFS/Tools/root.client/usr/vice/etc/afs.kext`;
176 if ($option_verbose) {
177 print "$hdutil $odmg extractall OpenAFS-debug-extension.pkg $tempdir/OpenAFS-debug-extension.pkg\n";
178 }
179 `$hdutil $odmg extractall OpenAFS-debug-extension.pkg $tempdir/OpenAFS-debug-extension.pkg`;
180 if (-f "$tempdir/OpenAFS-debug-extension.pkg/Contents/Archive.pax.gz" ) {
181 if ($option_verbose) {
182 print "cd $tempdir && gzcat $tempdir/OpenAFS-debug-extension.pkg/Contents/Archive.pax.gz | pax -r ./Library/OpenAFS/Debug/afs.kext.dSYM\n";
183 }
184 `cd $tempdir && gzcat $tempdir/OpenAFS-debug-extension.pkg/Contents/Archive.pax.gz | pax -r ./Library/OpenAFS/Debug/afs.kext.dSYM`;
185 if ($option_verbose) {
186 print "cd $tempdir && gzcat $tempdir/OpenAFS-debug-extension.pkg/Contents/Archive.pax.gz | pax -r ./Library/OpenAFS/Debug/afs.kext\n";
187 }
188 `cd $tempdir && gzcat $tempdir/OpenAFS-debug-extension.pkg/Contents/Archive.pax.gz | pax -r ./Library/OpenAFS/Debug/afs.kext`;
189 }
190 }
191
192 sub extract_kernel {
193 my $kversion = shift;
194 my $tempdir = shift;
195 my $debugarchive = shift;
196 my $hdutil = shift;
197
198 $kversion =~ /Darwin Kernel Version ([0-9]+).([0-9]+).[0-9]+:/;
199 my $minor = $2;
200 my $major = $1;
201 $major -= 4;
202 my $kdk = "$debugarchive/kernel_debug_kit_10.${major}.${minor}_\*.dmg";
203 if ($option_verbose) {
204 print "$hdutil $kdk extractall System.kext $tempdir/System.kext\n";
205 }
206 `$hdutil $kdk extractall System.kext $tempdir/System.kext`;
207 if ($option_verbose) {
208 print "$hdutil $kdk extractall mach_kernel.dSYM $tempdir/mach_kernel.dSYM\n";
209 }
210 `$hdutil $kdk extractall mach_kernel.dSYM $tempdir/mach_kernel.dSYM`;
211 if ($option_verbose) {
212 print "$hdutil $kdk extract mach_kernel $tempdir/mach_kernel\n";
213 }
214 `$hdutil $kdk extract mach_kernel $tempdir/mach_kernel`;
215 }
216
217
218 # read the panic file and parse out the addresses
219 sub read_panic {
220
221 my $filename = shift;
222 my $hash_ref = shift;
223
224 my $kernel_line;
225 my $line;
226 my @panic_section_positions = ( 0 );
227
228 my $panic_fh = IO::File->new( $filename, '<' )
229 or croak "Can't open backtrace file $filename: $OS_ERROR\n";
230
231 # find the last panic section as denoted by "*********"
232 while ( $line = <$panic_fh> ) {
233 chomp $line;
234 if ( $line eq "*********" ) {
235 # skip a line
236 $line = <$panic_fh>;
237 push @panic_section_positions, $panic_fh->tell;
238 }
239 }
240
241 # ignore the empty last section
242 if ( @panic_section_positions > 2 ) {
243 pop @panic_section_positions
244 }
245
246 # Seek to last full panic section
247 # or the beginning of the file if appropriate
248 $panic_fh->seek( $panic_section_positions[-1], 0 );
249
250 $hash_ref->{ "date" } = <$panic_fh>;
251 chomp $hash_ref->{ "date" };
252
253 while ( $line = <$panic_fh> ) {
254 chomp $line;
255
256 #skip lines until "Backtrace" is seen
257 $line =~ /^\s*(Backtrace,|Backtrace:|Backtrace \()/;
258 $backtrace = $1;
259 last if $backtrace;
260 }
261
262 if ( !$backtrace ) {
263 if ( $option_quiet ) {
264 exit 1;
265 } else {
266 croak "Couldn't find a backtrace in $filename\n";
267 }
268 }
269
270 # gather the backtrace addresses
271 if ( $backtrace eq "Backtrace:" ) {
272 # ppc format panic
273 while ( $line = <$panic_fh> ) {
274 chomp $line;
275 last if $line !~ /^\s*(0x[0-9a-fA-F]+)/;
276 my @memory_addresses = split /\s+/, $line;
277
278 # add non-empty array elements to the list
279 push @{ $hash_ref->{ "backtrace" } },
280 grep { /0x/ } @memory_addresses;
281 }
282 } else {
283 # intel format panic
284 while ( $line = <$panic_fh> ) {
285 chomp $line;
286 last if $line !~ /^\s*0x[0-9a-fA-F]+ : (0x[0-9a-fA-F]*)/;
287 push @{ $hash_ref->{ "backtrace" } }, $1;
288 }
289 }
290
291 # now we need the address for the afs kernel module
292 while ( $line = <$panic_fh> ) {
293 chomp $line;
294 last if ($line =~ /^BSD\s+process/ );
295 next if ( $line !~ /org\.openafs\.filesystems\.afs/ );
296
297 $kernel_line = $line;
298 $line =~ /\@(0x[0-9a-fA-F]+)/;
299 $hash_ref->{ "afs_kernel_address" } = $1;
300 $kernel_line =~ /^\s+([^@]+)@.+/;
301 $hash_ref->{ "afs_info" } = $1;
302
303 last;
304 }
305
306 $hash_ref->{ "kernel_shift" } = "0x0";
307 # grab the kernel version
308 while ( $line = <$panic_fh> ) {
309 chomp $line;
310 if ( $line =~ /^Darwin Kernel Version/ ) {
311 $hash_ref->{ "kernel_version" } = $line;
312 next;
313 }
314 if ($line =~ /^Kernel slide:\s+(0x[0-9a-fA-F]+)/ ) {
315 $hash_ref->{ "kernel_shift" } = $1;
316 next;
317 }
318 next if (! $hash_ref->{ "kernel_version" });
319 last if ($line =~ /^\s*$/)
320 }
321
322 if (! $kernel_line ) {
323 #unloaded?
324 while ( $line = <$panic_fh> ) {
325 chomp $line;
326 last if ( $line =~ /^loaded\s+kexts:/ );
327 next if ( $line !~ /org\.openafs\.filesystems\.afs/ );
328 $kernel_line = $line;
329 $line =~ /org\.openafs\.filesystems\.afs\s+([^@]+)\s+\(addr\s+(0x[0-9a-fA-F]+),/;
330 $hash_ref->{ "afs_kernel_address" } = $2;
331 $hash_ref->{ "afs_info" } = "org.openafs.filesystems.afs(" . $1 . ")\@0x" . $2;
332 $hash_ref->{ "warning" } = "MODULE WAS UNLOADED!\n";
333 }
334 }
335
336 $panic_fh->close()
337 or croak "Can't close file $filename: $OS_ERROR\n";
338
339 if ( !$kernel_line ) {
340 if ( $option_quiet ) {
341 exit 1;
342 } else {
343 croak "No OpenAFS reference found in latest panic!";
344 }
345 }
346 }
347
348 # generate the symbol files that will be read by gdb
349 sub generate_symbol_files {
350 my $kernel_address = shift;
351 my $kernel_shift = shift;
352 my $symbol_write_dir = shift;
353 my $kextarch = shift;
354 my $kernel = shift;
355 my $kext = shift;
356 my $sysext = shift;
357
358 my $kaddress = sprintf("0x%x", hex($kernel_address) - hex($kernel_shift));
359
360 if ($kextprog eq $kextload) {
361 if ($kernel eq "/mach_kernel") {
362 if ($option_verbose) {
363 print "$kextprog -k $kernel -s $temp_dir -a org.openafs.filesystems.afs\@${kaddress} -n $kext\n";
364 }
365 system( $kextprog,
366 "-k", $kernel,
367 "-s", $temp_dir,
368 "-a", 'org.openafs.filesystems.afs@' . $kaddress,
369 "-n", $kext );
370 } else {
371 if ($option_verbose) {
372 print "$kextprog -c -e -r $temp_dir -k $kernel -s $temp_dir -a org.openafs.filesystems.afs\@${kaddress} -n $kext\n";
373 }
374 system( $kextprog,
375 "-c", "-e",
376 "-r", $temp_dir,
377 "-k", $kernel,
378 "-s", $temp_dir,
379 "-a", 'org.openafs.filesystems.afs@' . $kaddress,
380 "-n", $kext );
381 }
382 } else {
383 if ($kernel eq "/mach_kernel") {
384 if ($option_verbose) {
385 print "$kextprog -k $kernel -s $temp_dir -arch $kextarch -a org.openafs.filesystems.afs\@${kaddress} -n $kext\n";
386 }
387 system( $kextprog,
388 "-k", $kernel,
389 "-s", $temp_dir,
390 "-arch", $kextarch,
391 "-a", 'org.openafs.filesystems.afs@' . $kaddress,
392 "-n", $kext );
393 } else {
394 if ($option_verbose) {
395 if ($sysextpath) {
396 print "$kextprog -c -e -r $sysextpath -r $temp_dir -k $kernel -s $temp_dir -arch $kextarch -a org.openafs.filesystems.afs\@${kaddress} -n $kext\n";
397 } else {
398 print "$kextprog -c -e -r $temp_dir -k $kernel -s $temp_dir -arch $kextarch -a org.openafs.filesystems.afs\@${kaddress} -n $kext\n";
399 }
400 }
401 if ($sysextpath) {
402 system( $kextprog,
403 "-c", "-e",
404 "-r", $temp_dir,
405 "-r", $sysextpath,
406 "-k", $kernel,
407 "-s", $temp_dir,
408 "-arch", $kextarch,
409 "-a", 'org.openafs.filesystems.afs@' . $kaddress,
410 "-n", $kext );
411 } else {
412 system( $kextprog,
413 "-c", "-e",
414 "-r", $temp_dir,
415 "-k", $kernel,
416 "-s", $temp_dir,
417 "-arch", $kextarch,
418 "-a", 'org.openafs.filesystems.afs@' . $kaddress,
419 "-n", $kext );
420 }
421 }
422 }
423 if ( $CHILD_ERROR ) {
424 # error
425 croak "kextload failed to run: $OS_ERROR\n";
426 }
427 }
428
429
430 sub write_gdb_input_file {
431
432 my $write_dir = shift;
433 my $filename = shift;
434 my $backtrace_ref = shift;
435 my $kernel_shift = shift;
436
437 my @symbol_files = ( $write_dir . "/org.openafs.filesystems.afs.sym" );
438
439 my $fh = IO::File->new( $write_dir . "/" . $filename, '>' )
440 or croak "Can't open gdb file $filename for writing: $OS_ERROR\n";
441
442 if ($kextprog eq $kextutil) {
443 print $fh "add-kext " . $write_dir . "/afs.kext\n";
444 } else {
445 for my $symbol ( @symbol_files ) {
446 print $fh "add-symbol-file $symbol\n";
447 }
448 }
449
450 print $fh "set print asm-demangle on\n";
451
452 for my $address ( @{ $backtrace_ref } ) {
453 my $kaddress = sprintf("0x%x", hex($address) - hex($kernel_shift));
454 print $fh "x/i $kaddress\n";
455 }
456
457 $fh->close()
458 or croak "Can't close file $filename: $OS_ERROR\n";
459 }
460
461 # write out the pertinent details to a file.
462 sub write_dump_file {
463 my $filename = shift;
464 my $hash_ref = shift;
465 my $output = shift;
466
467 my $log_dir = dirname $filename;
468
469 if ( ! -d $log_dir ) {
470 mkdir $log_dir, 0755;
471 croak "Can't create directory $log_dir: $OS_ERROR\n" if $CHILD_ERROR;
472 }
473
474 croak "Can't write to folder $log_dir." if ( ! -w $log_dir );
475
476 my $fh = IO::File->new( $filename, '>', 0664 )
477 or croak "Can't open dump file $filename for writing: $OS_ERROR\n";
478
479 print $fh "Panic Date: ", $hash_ref->{ "date" }, "\n";
480 print $fh "Kernel Version: ", $hash_ref->{ "kernel_version" }, "\n";
481 print $fh "OpenAFS Version: ", $hash_ref->{ "afs_info" }, "\n";
482 print $fh $hash_ref->{ "warning" };
483 print $fh "=============\n";
484 print $fh $output;
485
486 $fh->close()
487 or croak "Can't close file $filename: $OS_ERROR\n";
488 }
489
490 __END__
491
492 =head1 NAME
493
494 decode-panic - decode a Mac OS panic log to show source line numbers
495
496 =head1 VERSION
497
498 This documentation refers to decode-panic version $Revision$
499
500 =head1 SYNOPSIS
501
502 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]
503
504 =head1 OPTIONS
505
506 -i The path to the panic log that should be read
507 -o The path to where the decoded panic log should be written
508 -k The path to the kernel image corresponding to the panic
509 -d The path to a directory containing kernel debug kit dmgs
510 -a The path to an archive of OpenAFS installer dmgs
511 -u The path to the hdutil dmg utility program
512 -q Quiet mode - don't complain if there is a problem.
513 -v Verbose mode - print all commands.
514 -h print full help
515
516 =head1 DESCRIPTION
517
518 This tool parses the panic log for Mac OS X kernel panics that are caused by
519 openafs in order to produce a human-readable backtrace.
520
521 This program uses crash isolation procedure as outlined in
522 http://developer.apple.com/technotes/tn2002/tn2063.html#IsolatingCrash
523
524 Here is an example file that is fed to gdb:
525
526 add-symbol-file /tmp/afsdebugt8dGOb/org.openafs.filesystems.afs.sym
527 set print asm-demangle on
528 x/i 0x2ED1F7C0
529 x/i 0x2ED0D1A4
530
531 Panic logs can be found in /Library/Logs/panic.log in 10.4 (Tiger),
532 /Library/Logs/PanicReporter/YYYY-MM-DD-HHMMSS.panic in 10.5 (Leopard),
533 and /Library/Logs/DiagnosticReports/Kernel_YYYY-MM-DD-HHMMSS.panic in 10.6
534 (SnowLeopard).
535
536 =head1 DEPENDENCIES
537
538 This program needs gdb and kextload; Starting in SnowLeopard, it needs kextutil.
539
540 Batch decoding requires a directory of Kernel Debug Kit DMGs, a directory of
541 OpenAFS installer DMGs, and the DMG extraction utility currently available
542 in source form at http://www.dementia.org/~shadow/dmgutil-0.1.tar.gz
543
544 =head1 BUGS AND LIMITATIONS
545
546 decode-panic clobbers the output file.
547
548 =head1 AUTHOR
549
550 Copyright 2008-2010. Jason Edgecombe <jason@rampaginggeek.com> and others.
551
552 This documentation is covered by the BSD License as written in the
553 doc/LICENSE file in the OpenAFS source tree. This program was originally
554 written by Jason Edgecombe for OpenAFS.