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