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