update decode-panic for 10.6
[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 warnings;
15 use strict;
16
17 my $panic_file = "/Library/Logs/panic.log";
18 my %crash_info;
19 my $backtrace;
20 my $kextload   = "/sbin/kextload";
21 my $kextutil   = "/usr/bin/kextutil";
22 my $kextprog;
23 my $gdb        = "/usr/bin/gdb";
24 my $gdbarch    = "";
25 my $gdb_file   = "gdb.input";
26 my $temp_dir   = tempdir( "afsdebugXXXXXX", DIR => File::Spec->tmpdir,
27                         TMPDIR => 1, CLEANUP => 1 );
28 my $dump_file  = "/var/db/openafs/logs/crash.dump";
29
30 my $option_quiet;
31 my $option_help;
32 my $result = GetOptions ("input=s"  => \$panic_file,
33                          "output=s" => \$dump_file,
34                          "quiet"    => \$option_quiet,
35                          "help"     => \$option_help
36                      );
37
38 if ( !$result ) {
39     pod2usage(-message => "Syntax error.",
40               -exitval => 2,
41               -verbose => 1,
42               -output  => \*STDERR);
43     
44     exit;
45 }
46
47 if ($option_help) {
48     pod2usage(-message => "",
49               -exitval => 2,
50               -verbose => 3,
51               -output  => \*STDERR);
52     exit;
53 }
54
55 # check for necessary programs & panic file
56 for my $program ( $gdb, $kextload ) {
57     if ( ! -x $program ) {
58         if ( $option_quiet ) {
59             exit 1;
60         } else {
61             croak "Can't find $program!\n"
62         }
63     }
64 }
65
66 if ( -x $kextutil ) {
67     $kextprog = $kextutil;
68 } else {
69     $kextprog = $kextload;
70 }
71
72 croak "Can't find panic file: $panic_file!\n" if ( ! -r $panic_file );
73
74 read_panic( $panic_file, \%crash_info );
75
76 generate_symbol_files( $crash_info{"afs_kernel_address"}, $temp_dir );
77
78 write_gdb_input_file( $temp_dir, $gdb_file, $crash_info{ "backtrace" } );
79
80 if ($crash_info{"kernel_version"} =~ /X86_64/ ) {
81     $gdbarch="-a x86_64";
82 } else {
83     if ($crash_info{"kernel_version"} =~ /I386/ ) {
84         $gdbarch="-a i386";
85     } else {
86         if ($crash_info{"kernel_version"} =~ /PPC/ ) {
87             $gdbarch="-a ppc";
88         }
89     }
90 }
91
92 my $gdb_output = `$gdb $gdbarch /mach_kernel -batch -x $temp_dir/$gdb_file`;
93 croak "gdb failed!\n" if $CHILD_ERROR;
94
95 write_dump_file( $dump_file, \%crash_info, $gdb_output );
96
97 # read the panic file and parse out the addresses
98 sub read_panic {
99
100     my $filename      = shift;
101     my $hash_ref      = shift;
102
103     my $kernel_line;
104     my $line;
105     my @panic_section_positions = ( 0 );
106
107     
108     my $panic_fh = IO::File->new( $filename, '<' )
109         or croak "Can't open backtrace file $filename: $OS_ERROR\n";
110
111     # find the last panic section as denoted by "*********"
112     while ( $line = <$panic_fh> ) {
113         chomp $line;
114         if ( $line eq "*********" ) {
115             # skip a line
116             $line = <$panic_fh>;
117             push @panic_section_positions, $panic_fh->tell;
118         }
119     }
120
121     # ignore the empty last section
122     if ( @panic_section_positions > 2 ) {
123         pop @panic_section_positions
124     }
125
126     # Seek to last full panic section
127     # or the beginning of the file if appropriate
128     $panic_fh->seek( $panic_section_positions[-1], 0 );
129
130     $hash_ref->{ "date" } = <$panic_fh>;
131     chomp $hash_ref->{ "date" };
132
133     while ( $line = <$panic_fh> ) {
134         chomp $line;
135     
136         #skip lines until "Backtrace" is seen
137         $line =~ /^\s*(Backtrace,|Backtrace:|Backtrace \()/;
138         $backtrace = $1;
139         last if $backtrace;
140     }
141     
142     if ( !$backtrace ) {
143         if ( $option_quiet ) {
144             exit 1;
145         } else {
146             croak "Couldn't find a backtrace in $filename\n";
147         }
148     }
149     
150     # gather the backtrace addresses
151     if ( $backtrace eq "Backtrace:" ) {
152         # ppc format panic
153         while ( $line = <$panic_fh> ) {
154             chomp $line;
155             last if $line !~ /^\s*(0x[0-9a-fA-F]{8})/;
156             my @memory_addresses = split /\s+/, $line;
157
158             # add non-empty array elements to the list
159             push @{ $hash_ref->{ "backtrace" } },
160                 grep { /0x/ } @memory_addresses;
161         }
162     } else {
163         # intel format panic
164         while ( $line = <$panic_fh> ) {
165             chomp $line;
166             last if $line !~ /^\s*0x[0-9a-fA-F]{8} : (0x[0-9a-fA-F]*)/;
167             push @{ $hash_ref->{ "backtrace" } }, $1;
168         }
169     }
170
171     # now we need the address for the afs kernel module
172     while ( $line = <$panic_fh> ) {
173         chomp $line;
174         next if ( $line !~ /org\.openafs\.filesystems\.afs/ );
175
176         $kernel_line = $line;
177         $line =~ /\@(0x[0-9a-fA-F]+)/;
178         $hash_ref->{ "afs_kernel_address" } = $1;
179         $kernel_line =~ /^\s+([^@]+)@.+/;
180         $hash_ref->{ "afs_info" } = $1;
181
182         last;
183     }
184
185     # grab the kernel version
186     while ( $line = <$panic_fh> ) {
187         chomp $line;
188         next if ( $line !~ /^Darwin Kernel Version/ );
189         $hash_ref->{ "kernel_version" } = $line;
190     }
191     
192     $panic_fh->close()
193         or croak "Can't close file $filename: $OS_ERROR\n";
194     
195     if ( !$kernel_line ) {
196         if ( $option_quiet ) {
197             exit 1;
198         } else {
199             croak "No OpenAFS reference found in latest panic!";
200         }
201     }
202 }
203
204 # generate the symbol files that will be read by gdb
205 sub generate_symbol_files {
206     my $kernel_address   = shift;
207     my $symbol_write_dir = shift;
208
209     system( $kextprog,
210             "-s",  $temp_dir,
211             "-a", 'org.openafs.filesystems.afs@' . $kernel_address,
212             "-n", "/Library/OpenAFS/Tools/root.client/usr/vice/etc/afs.kext/" );
213     
214     if ( $CHILD_ERROR ) {
215         # error
216         croak "kextload failed to run: $OS_ERROR\n";
217     }
218 }
219
220
221 sub write_gdb_input_file {
222
223     my $write_dir     = shift;
224     my $filename      = shift;
225     my $backtrace_ref = shift;
226     
227     my @symbol_files = ( $write_dir . "/org.openafs.filesystems.afs.sym" );
228         
229     my $fh = IO::File->new( $write_dir . "/" . $filename, '>' )
230         or croak "Can't open gdb file $filename for writing: $OS_ERROR\n";
231
232     for my $symbol ( @symbol_files ) {
233         print $fh "add-symbol-file $symbol\n";
234     }
235
236     print $fh "set print asm-demangle on\n";
237
238     for my $address ( @{ $backtrace_ref } ) {
239         print $fh "x/i $address\n";
240     }
241
242    $fh->close()
243         or croak "Can't close file $filename: $OS_ERROR\n";
244 }
245
246 # write out the pertinent details to a file.
247 sub write_dump_file {
248     my $filename = shift;
249     my $hash_ref = shift;
250     my $output   = shift;
251
252     my $log_dir  = dirname $filename;
253
254     if ( ! -d $log_dir ) {
255         mkdir $log_dir, 0755;
256         croak "Can't create directory $log_dir: $OS_ERROR\n" if $CHILD_ERROR;
257     }
258
259     croak "Can't write to folder $log_dir." if ( ! -w $log_dir );
260
261     my $fh = IO::File->new( $filename, '>', 0664 )
262         or croak "Can't open dump file $filename for writing: $OS_ERROR\n";
263     
264     print $fh "Panic Date:      ", $hash_ref->{ "date" }, "\n";
265     print $fh "Kernel Version:  ", $hash_ref->{ "kernel_version" }, "\n";
266     print $fh "OpenAFS Version: ", $hash_ref->{ "afs_info" }, "\n";
267     print $fh "=============\n";
268     print $fh $output;
269     
270     $fh->close()
271         or croak "Can't close file $filename: $OS_ERROR\n";
272 }
273
274 __END__
275
276 =head1 NAME
277
278 decode-panic - decode a Mac OS panic log to show source line numbers
279
280 =head1 VERSION
281
282 This documentation refers to decode-panic version $Revision$
283
284 =head1 SYNOPSIS
285  
286    decode-panic [-i <input panic log>] [-o <output dump file>] [-q]
287
288 =head1 OPTIONS
289
290    -i The path to the panic log that should be read
291    -o The path to where the decoded panic log should be written
292    -q Quiet mode - don't complain if there is a problem.
293    -h print full help
294
295 =head1 DESCRIPTION
296
297 It parses the panic log for Mac OS X kernel panics that are caused by
298 openafs in order to produce a human-readable backtrace.
299
300 This program uses crash isolation procedure as outlined in
301 http://developer.apple.com/technotes/tn2002/tn2063.html#IsolatingCrash
302
303 Here is an example file that is fed to gdb:
304
305    add-symbol-file /tmp/afsdebugt8dGOb/org.openafs.filesystems.afs.sym
306    set print asm-demangle on
307    x/i 0x2ED1F7C0
308    x/i 0x2ED0D1A4
309
310 Panic logs can be found in /Library/Logs/panic.log in 10.4 (Tiger), 
311 /Library/Logs/PanicReporter/YYYY-MM-DD-HHMMSS.panic in 10.5 (Leopard),
312 and /Library/Logs/DiagnosticReports/Kernel_YYYY-MM-DD-HHMMSS.panic in 10.6
313 (SnowLeopard).
314
315 =head1 DEPENDENCIES
316
317 This program needs gdb and kextload; Starting in SnowLeopard, it needs kextutil.
318
319 =head1 BUGS AND LIMITATIONS
320
321 decode-panic clobbers the output file.
322
323 =head1 AUTHOR
324
325 Copyright 2008. Jason Edgecombe <jason@rampaginggeek.com>
326
327 This documentation is covered by the BSD License as written in the
328 doc/LICENSE file in the OpenAFS source tree. This program was written by
329 Jason Edgecombe for OpenAFS.