tools/rb/fix-linux-stack.pl

changeset 0
6474c204b198
     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 +}

mercurial