Fix compilation with old g++ 3.3.5 and debian-sarge.
[wvstreams.git] / utils / wvcrashread
blob20d0e6e4e4ff6ed7012243d761809686abd1bbd0
1 #!/usr/bin/perl
3 # Worldvisions Weaver Software:
4 # Copyright (C) 2005 Net Integration Technologies, Inc.
6 # Post-process WvCrash output using GDB, to try to figure out exactly
7 # which line things died upon.
9 # The advantage of using this programme over a plain stack dump is
10 # that it is clever enough to figure out relative offsets to a
11 # function, so you don't need the exact same object file as the one
12 # that crashed. With extra cleverness, we even span symbol manging
13 # across different versions of G++.
15 use strict;
16 use warnings;
18 use Cwd;
19 use File::Basename;
20 use File::Find ();
21 use FileHandle;
22 use IPC::Open2;
24 my $DEBUG = $ENV{DEBUG};
25 sub debug
27 print(STDERR @_) if $DEBUG;
30 sub find_programme($)
32 my $target = shift(@_);
34 return $target if (-f $target && -x $target);
36 my $result;
37 my $endtime = time() + 20; # Stop searching after 20 seconds.
38 my $cwd = cwd(); # Save the working dir so we can go back.
40 my $wanted = sub {
41 if ($_ eq $target && -f $_ && -x $_) {
42 $result = $File::Find::name;
43 die; # Break out of the File::Find::find.
45 elsif (time() > $endtime){
46 die; # Took too long.
50 # Traverse desired filesystems
51 eval {
52 File::Find::find({wanted => $wanted,
53 follow_fast => 1,
54 follow_skip => 2}, '.');
56 chdir($cwd);
57 return $result;
60 sub gdb_init($)
62 my ($programme) = @_;
64 my $gdb_prompt = qr/(?:\(gdb\) )+/;
65 my $gdb_flush = "echo \\n\n"; # Flush gdb's stdout
67 my $gdb_addr = sub ($$$) {
68 my ($Reader, $Writer, $function) = @_;
70 debug("-> info addr $function\n");
71 print($Writer "info addr $function\n");
72 print($Writer $gdb_flush);
73 my $first = 0;
74 while (<$Reader>)
76 debug("<- $_");
77 if (/^$gdb_prompt\s*Symbol "(.*?)" is .* (0x[0-9A-Fa-f]+)/) {
78 return ($1, $2);
80 last if (/^$gdb_prompt$/ && $first);
81 $first = 1;
84 # Returns the human-readable function name, and the starting address
85 return ($function, undef);
88 my $gdb_line = sub ($$$$)
90 my ($Reader, $Writer, $addr, $offset) = @_;
92 debug("-> info line *$addr+$offset\n");
93 print($Writer "info line *$addr+$offset\n");
94 print($Writer $gdb_flush);
95 my $first = 0;
96 while (<$Reader>) {
97 debug("<- $_");
98 if (/^$gdb_prompt\s*Line ([0-9]+) of "(.*?)" starts at /) {
99 return ($2, $1);
101 last if (/^$gdb_prompt$/ && $first);
102 $first = 1;
105 # Returns the filename and line number.
106 return (undef, undef);
109 # Initialise GDB
110 my ($Reader, $Writer);
111 debug("gdb '$programme'\n");
112 my $pid = open2($Reader, $Writer, "gdb '$programme' 2>&1") or die;
113 print($Writer "set width 2000\n");
114 print($Writer "set height 20000\n");
115 print($Writer "break main\n");
116 print($Writer "run\n");
117 print($Writer $gdb_flush);
118 while (<$Reader>) {
119 debug("<- $_");
120 last if (/^$gdb_prompt$/);
123 return sub ($) {
124 my ($string) = @_;
126 # Parse the input string.
127 my ($binary, $absolute) = ($string =~ /^(.*?)([\[\(].*)/);
128 my ($function, $offset) = ($absolute =~ /^\((.*?)\+(.*?)\)/);
129 $absolute =~ s/.*\[(.*?)\].*/$1/;
131 my ($file, $line) = ("--", "--");
133 unless (defined($function)) {
134 $function = "file: $binary";
136 else {
137 my $addr;
139 # Try with the mangled function name
140 ($function, $addr) = &$gdb_addr($Reader, $Writer, $function);
142 unless (defined($addr))
144 # Try with c++filt mangled function
145 my $filtered = `c++filt '$function'` or return ($function,
146 undef, undef);
147 chomp($filtered);
148 # Turn () into (void) for old GDB
149 $filtered =~ s/(?<!operator)\(\)/(void)/g;
150 ($function, $addr) = &$gdb_addr($Reader, $Writer, $filtered);
152 unless (defined($addr))
154 # Try turning (void) into () for new GDB
155 $function =~ s/\(void\)/()/g;
156 ($function, $addr) = &$gdb_addr($Reader, $Writer,
157 $function);
160 return ($function, undef, undef) unless (defined($addr));
163 ($file, $line) = &$gdb_line($Reader, $Writer, $addr, $offset);
164 $file = "--" unless defined($file);
165 $line = "--" unless defined($line);
168 return ($function, $file, $line);
172 # Main variables
173 my $programme; # Base name of the programme
174 my $programme_path; # Relative path to the location of programme
175 my $signum; # Signal number that it died on
176 my $gdb_parse; # Handle to GDB conversation
178 # Main programme
179 my $state = "begin";
180 while (my $string = <>)
182 chomp($string);
184 # We drive a state machine here, to figure out what we should do next.
185 STATE: for ($state)
187 if (/begin/)
189 undef $programme;
190 undef $signum;
191 # We will want to skip anything that isn't the beginning
192 # of a WvCrash file.
193 if ($string =~ / dying on signal \d+/)
195 $state = "new";
196 goto STATE;
199 elsif (/new/)
201 # Extract the information out of the first line of the header.
202 if ($string =~ /^(.*?) dying on signal (\d+)(.*)?/)
204 $programme = $1;
205 $signum = $2;
206 my $signame = $3 || "";
207 my $version = "";
208 if ($programme =~ s/\s+\((.*)\)//) {
209 $version = " ($1)" if $1;
211 $programme = basename($programme); # Relative path
212 print("$programme$version dying on signal $signum$signame\n");
213 if ($programme_path = find_programme($programme)) {
214 $state = "header";
216 else {
217 $state = "missing";
221 elsif (/header/)
223 # We don't actually have much of a header right now, so this
224 # merely transitions to the backtrace.
225 if ($string =~ /^Backtrace:/)
227 unless (defined($programme) && defined($signum)) {
228 $state = "corrupt";
230 else
232 $gdb_parse = gdb_init($programme_path);
233 $state = "backtrace";
236 # Echo back header information. It's not important to parse,
237 # but it might be nice to display.
238 print "$string\n";
240 elsif (/backtrace/)
242 # Keep reading backtrace information until we stop seeing
243 # stack traces.
244 if ($string =~ /\[0x[0-9A-Fa-f]+\]$/)
246 my ($function, $file, $line) = &$gdb_parse($string);
248 # Eat up some extra spaces
249 $function =~ s/,\s+/,/g;
250 $function =~ s/\s+\*/*/g;
251 $function =~ s/\s+&/&/g;
252 $function =~ s/\s+&/&/g;
253 $function =~ s/\s+\(/\(/g;
255 $state = "backtrace";
256 if (not defined($file)) {
257 printf("%-40s --:--\n", $function);
259 else {
260 printf("%-40s %s:%s\n", $function, $file, $line);
263 else
265 $state = "begin";
266 goto STATE;
269 elsif (/missing/)
271 print(STDERR
272 "Could not find the '$programme' program! ",
273 "Aborting.\n");
274 exit(1);
276 elsif (/corrupt/)
278 print(STDERR "Unrecognized WvCrash output. Aborting.\n");
279 exit(1);
281 else
283 die("Internal wvcrashread error. Aborting.");