1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/tools/rb/fix-linux-stack.pl Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,259 @@ 1.4 +#!/usr/bin/perl 1.5 +# vim:sw=4:ts=4:et: 1.6 +# This Source Code Form is subject to the terms of the Mozilla Public 1.7 +# License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 +# file, You can obtain one at http://mozilla.org/MPL/2.0/. 1.9 + 1.10 +# $Id: fix-linux-stack.pl,v 1.16 2008/05/05 21:51:11 dbaron%dbaron.org Exp $ 1.11 +# 1.12 +# This script uses addr2line (part of binutils) to process the output of 1.13 +# nsTraceRefcnt's Linux stack walking code. This is useful for two 1.14 +# things: 1.15 +# (1) Getting line number information out of 1.16 +# |nsTraceRefcnt::WalkTheStack|'s output in debug builds. 1.17 +# (2) Getting function names out of |nsTraceRefcnt::WalkTheStack|'s 1.18 +# output on optimized builds (where it mostly prints UNKNOWN 1.19 +# because only a handful of symbols are exported from component 1.20 +# libraries). 1.21 +# 1.22 +# Use the script by piping output containing stacks (such as raw stacks 1.23 +# or make-tree.pl balance trees) through this script. 1.24 + 1.25 +use strict; 1.26 +use IPC::Open2; 1.27 +use File::Basename; 1.28 + 1.29 +# XXX Hard-coded to gdb defaults (works on Fedora). 1.30 +my $global_debug_dir = '/usr/lib/debug'; 1.31 + 1.32 +# We record several things for each file encountered. 1.33 +# 1.34 +# - {pipe_read}, {pipe_write}: these constitute a bidirectional pipe to an 1.35 +# addr2line process that gives symbol information for a file. 1.36 +# 1.37 +# - {cache}: this table holds the results of lookups that we've done 1.38 +# previously for (pre-adjustment) addresses, which lets us avoid redundant 1.39 +# calls to addr2line. 1.40 +# 1.41 +# - {address_adjustment}: addr2line wants offsets relative to the base address 1.42 +# for shared libraries, but it wants addresses including the base address 1.43 +# offset for executables. This holds the appropriate address adjustment to 1.44 +# add to an offset within file. See bug 230336. 1.45 +# 1.46 +my %file_infos; 1.47 + 1.48 +sub set_address_adjustment($$) { 1.49 + my ($file, $file_info) = @_; 1.50 + 1.51 + # find out if it's an executable (as opposed to a shared library) 1.52 + my $elftype; 1.53 + open(ELFHDR, '-|', 'readelf', '-h', $file); 1.54 + while (<ELFHDR>) { 1.55 + if (/^\s*Type:\s+(\S+)/) { 1.56 + $elftype = $1; 1.57 + last; 1.58 + } 1.59 + } 1.60 + close(ELFHDR); 1.61 + 1.62 + # If it's an executable, make adjustment the base address. 1.63 + # Otherwise, leave it zero. 1.64 + my $adjustment = 0; 1.65 + if ($elftype eq 'EXEC') { 1.66 + open(ELFSECS, '-|', 'readelf', '-S', $file); 1.67 + while (<ELFSECS>) { 1.68 + if (/^\s*\[\s*\d+\]\s+\.text\s+\w+\s+(\w+)\s+(\w+)\s+/) { 1.69 + # Subtract the .text section's offset within the 1.70 + # file from its base address. 1.71 + $adjustment = hex($1) - hex($2); 1.72 + last; 1.73 + } 1.74 + } 1.75 + close(ELFSECS); 1.76 + } 1.77 + 1.78 + $file_info->{address_adjustment} = $adjustment; 1.79 +} 1.80 + 1.81 +# Files sometimes contain a link to a separate object file that contains 1.82 +# the debug sections of the binary, removed so that a smaller file can 1.83 +# be shipped, but kept separately so that it can be obtained by those 1.84 +# who want it. 1.85 +# See http://sources.redhat.com/gdb/current/onlinedocs/gdb_16.html#SEC154 1.86 +# for documentation of debugging information in separate files. 1.87 +# On Fedora distributions, these files can be obtained by installing 1.88 +# *-debuginfo RPM packages. 1.89 +sub separate_debug_file_for($) { 1.90 + my ($file) = @_; 1.91 + # We can read the .gnu_debuglink section using either of: 1.92 + # objdump -s --section=.gnu_debuglink $file 1.93 + # readelf -x .gnu_debuglink $file 1.94 + # Since readelf prints things backwards on little-endian platforms 1.95 + # for some versions only (backwards on Fedora Core 6, forwards on 1.96 + # Fedora 7), use objdump. 1.97 + 1.98 + # See if there's a .gnu_debuglink section 1.99 + my $have_debuglink = 0; 1.100 + open(ELFSECS, '-|', 'readelf', '-S', $file); 1.101 + while (<ELFSECS>) { 1.102 + if (/^\s*\[\s*\d+\]\s+\.gnu_debuglink\s+\w+\s+(\w+)\s+(\w+)\s+/) { 1.103 + $have_debuglink = 1; 1.104 + last; 1.105 + } 1.106 + } 1.107 + close(ELFSECS); 1.108 + return '' unless ($have_debuglink); 1.109 + 1.110 + # Determine the endianness of the shared library. 1.111 + my $endian = ''; 1.112 + open(ELFHDR, '-|', 'readelf', '-h', $file); 1.113 + while (<ELFHDR>) { 1.114 + if (/^\s*Data:\s+.*(little|big) endian.*$/) { 1.115 + $endian = $1; 1.116 + last; 1.117 + } 1.118 + } 1.119 + close(ELFHDR); 1.120 + if ($endian ne 'little' && $endian ne 'big') { 1.121 + print STDERR "Warning: could not determine endianness of $file.\n"; 1.122 + return ''; 1.123 + } 1.124 + 1.125 + 1.126 + # Read the debuglink section as an array of words, in hexidecimal. 1.127 + open(DEBUGLINK, '-|', 'objdump', '-s', '--section=.gnu_debuglink', $file); 1.128 + my @words; 1.129 + while (<DEBUGLINK>) { 1.130 + if ($_ =~ /^ [0-9a-f]* ([0-9a-f ]{8}) ([0-9a-f ]{8}) ([0-9a-f ]{8}) ([0-9a-f ]{8}).*/) { 1.131 + push @words, $1, $2, $3, $4; 1.132 + } 1.133 + } 1.134 + close(DEBUGLINK); 1.135 + 1.136 + while (@words[$#words] eq ' ') { 1.137 + pop @words; 1.138 + } 1.139 + 1.140 + if ($#words < 1) { 1.141 + print STDERR "Warning: .gnu_debuglink section in $file too short.\n"; 1.142 + return ''; 1.143 + } 1.144 + 1.145 + my @chars; 1.146 + while ($#words >= 0) { 1.147 + my $w = shift @words; 1.148 + if ($w =~ /^([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/) { 1.149 + push @chars, $1, $2, $3, $4; 1.150 + } else { 1.151 + print STDERR "Warning: malformed objdump output for $file.\n"; 1.152 + return ''; 1.153 + } 1.154 + } 1.155 + 1.156 + my @hash_bytes = map(hex, @chars[$#chars - 3 .. $#chars]); 1.157 + $#chars -= 4; 1.158 + 1.159 + my $hash; 1.160 + if ($endian eq 'little') { 1.161 + $hash = ($hash_bytes[3] << 24) | ($hash_bytes[2] << 16) | ($hash_bytes[1] << 8) | $hash_bytes[0]; 1.162 + } else { 1.163 + $hash = ($hash_bytes[0] << 24) | ($hash_bytes[1] << 16) | ($hash_bytes[2] << 8) | $hash_bytes[3]; 1.164 + } 1.165 + 1.166 + # The string ends with a null-terminator and then 0 to three bytes 1.167 + # of padding to fill the current 32-bit unit. (This padding is 1.168 + # usually null bytes, but I've seen null-null-H, on Ubuntu x86_64.) 1.169 + my $terminator = 1; 1.170 + while ($chars[$terminator] ne '00') { 1.171 + if ($terminator == $#chars) { 1.172 + print STDERR "Warning: missing null terminator in " . 1.173 + ".gnu_debuglink section of $file.\n"; 1.174 + return ''; 1.175 + } 1.176 + ++$terminator; 1.177 + } 1.178 + if ($#chars - $terminator > 3) { 1.179 + print STDERR "Warning: Excess padding in .gnu_debuglink section " . 1.180 + "of $file.\n"; 1.181 + return ''; 1.182 + } 1.183 + $#chars = $terminator - 1; 1.184 + 1.185 + my $basename = join('', map { chr(hex($_)) } @chars); 1.186 + 1.187 + # Now $basename and $hash represent the information in the 1.188 + # .gnu_debuglink section. 1.189 + #printf STDERR "%x: %s\n", $hash, $basename; 1.190 + 1.191 + my @possible_results = ( 1.192 + dirname($file) . $basename, 1.193 + dirname($file) . '.debug/' . $basename, 1.194 + $global_debug_dir . dirname($file) . '/' . $basename 1.195 + ); 1.196 + foreach my $result (@possible_results) { 1.197 + if (-f $result) { 1.198 + # XXX We should check the hash. 1.199 + return $result; 1.200 + } 1.201 + } 1.202 + 1.203 + return ''; 1.204 +} 1.205 + 1.206 +sub get_file_info($) { 1.207 + my ($file) = @_; 1.208 + my $file_info = $file_infos{$file}; 1.209 + unless (defined $file_info) { 1.210 + my $debug_file = separate_debug_file_for($file); 1.211 + $debug_file = $file if ($debug_file eq ''); 1.212 + 1.213 + my $pid = open2($file_info->{pipe_read}, $file_info->{pipe_write}, 1.214 + '/usr/bin/addr2line', '-C', '-f', '-e', $debug_file); 1.215 + 1.216 + set_address_adjustment($file, $file_info); 1.217 + 1.218 + $file_infos{$file} = $file_info; 1.219 + } 1.220 + return $file_info; 1.221 +} 1.222 + 1.223 +# Ignore SIGPIPE as a workaround for addr2line crashes in some situations. 1.224 +$SIG{PIPE} = 'IGNORE'; 1.225 + 1.226 +select STDOUT; $| = 1; # make STDOUT unbuffered 1.227 +while (<>) { 1.228 + my $line = $_; 1.229 + if ($line =~ /^([ \|0-9-]*)(.*) ?\[([^ ]*) \+(0x[0-9A-F]{1,8})\](.*)$/) { 1.230 + my $before = $1; # allow preservation of balance trees 1.231 + my $badsymbol = $2; 1.232 + my $file = $3; 1.233 + my $address = hex($4); 1.234 + my $after = $5; # allow preservation of counts 1.235 + 1.236 + if (-f $file) { 1.237 + my $file_info = get_file_info($file); 1.238 + my $result = $file_info->{cache}->{$address}; 1.239 + if (not defined $result) { 1.240 + my $address2 = $address + $file_info->{address_adjustment}; 1.241 + my $out = $file_info->{pipe_write}; 1.242 + my $in = $file_info->{pipe_read}; 1.243 + printf {$out} "0x%X\n", $address2; 1.244 + chomp(my $symbol = <$in>); 1.245 + chomp(my $fileandline = <$in>); 1.246 + if (!$symbol || $symbol eq '??') { $symbol = $badsymbol; } 1.247 + if (!$fileandline || $fileandline eq '??:0') { 1.248 + $fileandline = $file; 1.249 + } 1.250 + $result = "$symbol ($fileandline)"; 1.251 + $file_info->{cache}->{$address} = $result; 1.252 + } 1.253 + print "$before$result$after\n"; 1.254 + } else { 1.255 + print STDERR "Warning: File \"$file\" does not exist.\n"; 1.256 + print $line; 1.257 + } 1.258 + 1.259 + } else { 1.260 + print $line; 1.261 + } 1.262 +}