tools/rb/fix-linux-stack.pl

Tue, 06 Jan 2015 21:39:09 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Tue, 06 Jan 2015 21:39:09 +0100
branch
TOR_BUG_9701
changeset 8
97036ab72558
permissions
-rwxr-xr-x

Conditionally force memory storage according to privacy.thirdparty.isolate;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.

michael@0 1 #!/usr/bin/perl
michael@0 2 # vim:sw=4:ts=4:et:
michael@0 3 # This Source Code Form is subject to the terms of the Mozilla Public
michael@0 4 # License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 5 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
michael@0 6
michael@0 7 # $Id: fix-linux-stack.pl,v 1.16 2008/05/05 21:51:11 dbaron%dbaron.org Exp $
michael@0 8 #
michael@0 9 # This script uses addr2line (part of binutils) to process the output of
michael@0 10 # nsTraceRefcnt's Linux stack walking code. This is useful for two
michael@0 11 # things:
michael@0 12 # (1) Getting line number information out of
michael@0 13 # |nsTraceRefcnt::WalkTheStack|'s output in debug builds.
michael@0 14 # (2) Getting function names out of |nsTraceRefcnt::WalkTheStack|'s
michael@0 15 # output on optimized builds (where it mostly prints UNKNOWN
michael@0 16 # because only a handful of symbols are exported from component
michael@0 17 # libraries).
michael@0 18 #
michael@0 19 # Use the script by piping output containing stacks (such as raw stacks
michael@0 20 # or make-tree.pl balance trees) through this script.
michael@0 21
michael@0 22 use strict;
michael@0 23 use IPC::Open2;
michael@0 24 use File::Basename;
michael@0 25
michael@0 26 # XXX Hard-coded to gdb defaults (works on Fedora).
michael@0 27 my $global_debug_dir = '/usr/lib/debug';
michael@0 28
michael@0 29 # We record several things for each file encountered.
michael@0 30 #
michael@0 31 # - {pipe_read}, {pipe_write}: these constitute a bidirectional pipe to an
michael@0 32 # addr2line process that gives symbol information for a file.
michael@0 33 #
michael@0 34 # - {cache}: this table holds the results of lookups that we've done
michael@0 35 # previously for (pre-adjustment) addresses, which lets us avoid redundant
michael@0 36 # calls to addr2line.
michael@0 37 #
michael@0 38 # - {address_adjustment}: addr2line wants offsets relative to the base address
michael@0 39 # for shared libraries, but it wants addresses including the base address
michael@0 40 # offset for executables. This holds the appropriate address adjustment to
michael@0 41 # add to an offset within file. See bug 230336.
michael@0 42 #
michael@0 43 my %file_infos;
michael@0 44
michael@0 45 sub set_address_adjustment($$) {
michael@0 46 my ($file, $file_info) = @_;
michael@0 47
michael@0 48 # find out if it's an executable (as opposed to a shared library)
michael@0 49 my $elftype;
michael@0 50 open(ELFHDR, '-|', 'readelf', '-h', $file);
michael@0 51 while (<ELFHDR>) {
michael@0 52 if (/^\s*Type:\s+(\S+)/) {
michael@0 53 $elftype = $1;
michael@0 54 last;
michael@0 55 }
michael@0 56 }
michael@0 57 close(ELFHDR);
michael@0 58
michael@0 59 # If it's an executable, make adjustment the base address.
michael@0 60 # Otherwise, leave it zero.
michael@0 61 my $adjustment = 0;
michael@0 62 if ($elftype eq 'EXEC') {
michael@0 63 open(ELFSECS, '-|', 'readelf', '-S', $file);
michael@0 64 while (<ELFSECS>) {
michael@0 65 if (/^\s*\[\s*\d+\]\s+\.text\s+\w+\s+(\w+)\s+(\w+)\s+/) {
michael@0 66 # Subtract the .text section's offset within the
michael@0 67 # file from its base address.
michael@0 68 $adjustment = hex($1) - hex($2);
michael@0 69 last;
michael@0 70 }
michael@0 71 }
michael@0 72 close(ELFSECS);
michael@0 73 }
michael@0 74
michael@0 75 $file_info->{address_adjustment} = $adjustment;
michael@0 76 }
michael@0 77
michael@0 78 # Files sometimes contain a link to a separate object file that contains
michael@0 79 # the debug sections of the binary, removed so that a smaller file can
michael@0 80 # be shipped, but kept separately so that it can be obtained by those
michael@0 81 # who want it.
michael@0 82 # See http://sources.redhat.com/gdb/current/onlinedocs/gdb_16.html#SEC154
michael@0 83 # for documentation of debugging information in separate files.
michael@0 84 # On Fedora distributions, these files can be obtained by installing
michael@0 85 # *-debuginfo RPM packages.
michael@0 86 sub separate_debug_file_for($) {
michael@0 87 my ($file) = @_;
michael@0 88 # We can read the .gnu_debuglink section using either of:
michael@0 89 # objdump -s --section=.gnu_debuglink $file
michael@0 90 # readelf -x .gnu_debuglink $file
michael@0 91 # Since readelf prints things backwards on little-endian platforms
michael@0 92 # for some versions only (backwards on Fedora Core 6, forwards on
michael@0 93 # Fedora 7), use objdump.
michael@0 94
michael@0 95 # See if there's a .gnu_debuglink section
michael@0 96 my $have_debuglink = 0;
michael@0 97 open(ELFSECS, '-|', 'readelf', '-S', $file);
michael@0 98 while (<ELFSECS>) {
michael@0 99 if (/^\s*\[\s*\d+\]\s+\.gnu_debuglink\s+\w+\s+(\w+)\s+(\w+)\s+/) {
michael@0 100 $have_debuglink = 1;
michael@0 101 last;
michael@0 102 }
michael@0 103 }
michael@0 104 close(ELFSECS);
michael@0 105 return '' unless ($have_debuglink);
michael@0 106
michael@0 107 # Determine the endianness of the shared library.
michael@0 108 my $endian = '';
michael@0 109 open(ELFHDR, '-|', 'readelf', '-h', $file);
michael@0 110 while (<ELFHDR>) {
michael@0 111 if (/^\s*Data:\s+.*(little|big) endian.*$/) {
michael@0 112 $endian = $1;
michael@0 113 last;
michael@0 114 }
michael@0 115 }
michael@0 116 close(ELFHDR);
michael@0 117 if ($endian ne 'little' && $endian ne 'big') {
michael@0 118 print STDERR "Warning: could not determine endianness of $file.\n";
michael@0 119 return '';
michael@0 120 }
michael@0 121
michael@0 122
michael@0 123 # Read the debuglink section as an array of words, in hexidecimal.
michael@0 124 open(DEBUGLINK, '-|', 'objdump', '-s', '--section=.gnu_debuglink', $file);
michael@0 125 my @words;
michael@0 126 while (<DEBUGLINK>) {
michael@0 127 if ($_ =~ /^ [0-9a-f]* ([0-9a-f ]{8}) ([0-9a-f ]{8}) ([0-9a-f ]{8}) ([0-9a-f ]{8}).*/) {
michael@0 128 push @words, $1, $2, $3, $4;
michael@0 129 }
michael@0 130 }
michael@0 131 close(DEBUGLINK);
michael@0 132
michael@0 133 while (@words[$#words] eq ' ') {
michael@0 134 pop @words;
michael@0 135 }
michael@0 136
michael@0 137 if ($#words < 1) {
michael@0 138 print STDERR "Warning: .gnu_debuglink section in $file too short.\n";
michael@0 139 return '';
michael@0 140 }
michael@0 141
michael@0 142 my @chars;
michael@0 143 while ($#words >= 0) {
michael@0 144 my $w = shift @words;
michael@0 145 if ($w =~ /^([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/) {
michael@0 146 push @chars, $1, $2, $3, $4;
michael@0 147 } else {
michael@0 148 print STDERR "Warning: malformed objdump output for $file.\n";
michael@0 149 return '';
michael@0 150 }
michael@0 151 }
michael@0 152
michael@0 153 my @hash_bytes = map(hex, @chars[$#chars - 3 .. $#chars]);
michael@0 154 $#chars -= 4;
michael@0 155
michael@0 156 my $hash;
michael@0 157 if ($endian eq 'little') {
michael@0 158 $hash = ($hash_bytes[3] << 24) | ($hash_bytes[2] << 16) | ($hash_bytes[1] << 8) | $hash_bytes[0];
michael@0 159 } else {
michael@0 160 $hash = ($hash_bytes[0] << 24) | ($hash_bytes[1] << 16) | ($hash_bytes[2] << 8) | $hash_bytes[3];
michael@0 161 }
michael@0 162
michael@0 163 # The string ends with a null-terminator and then 0 to three bytes
michael@0 164 # of padding to fill the current 32-bit unit. (This padding is
michael@0 165 # usually null bytes, but I've seen null-null-H, on Ubuntu x86_64.)
michael@0 166 my $terminator = 1;
michael@0 167 while ($chars[$terminator] ne '00') {
michael@0 168 if ($terminator == $#chars) {
michael@0 169 print STDERR "Warning: missing null terminator in " .
michael@0 170 ".gnu_debuglink section of $file.\n";
michael@0 171 return '';
michael@0 172 }
michael@0 173 ++$terminator;
michael@0 174 }
michael@0 175 if ($#chars - $terminator > 3) {
michael@0 176 print STDERR "Warning: Excess padding in .gnu_debuglink section " .
michael@0 177 "of $file.\n";
michael@0 178 return '';
michael@0 179 }
michael@0 180 $#chars = $terminator - 1;
michael@0 181
michael@0 182 my $basename = join('', map { chr(hex($_)) } @chars);
michael@0 183
michael@0 184 # Now $basename and $hash represent the information in the
michael@0 185 # .gnu_debuglink section.
michael@0 186 #printf STDERR "%x: %s\n", $hash, $basename;
michael@0 187
michael@0 188 my @possible_results = (
michael@0 189 dirname($file) . $basename,
michael@0 190 dirname($file) . '.debug/' . $basename,
michael@0 191 $global_debug_dir . dirname($file) . '/' . $basename
michael@0 192 );
michael@0 193 foreach my $result (@possible_results) {
michael@0 194 if (-f $result) {
michael@0 195 # XXX We should check the hash.
michael@0 196 return $result;
michael@0 197 }
michael@0 198 }
michael@0 199
michael@0 200 return '';
michael@0 201 }
michael@0 202
michael@0 203 sub get_file_info($) {
michael@0 204 my ($file) = @_;
michael@0 205 my $file_info = $file_infos{$file};
michael@0 206 unless (defined $file_info) {
michael@0 207 my $debug_file = separate_debug_file_for($file);
michael@0 208 $debug_file = $file if ($debug_file eq '');
michael@0 209
michael@0 210 my $pid = open2($file_info->{pipe_read}, $file_info->{pipe_write},
michael@0 211 '/usr/bin/addr2line', '-C', '-f', '-e', $debug_file);
michael@0 212
michael@0 213 set_address_adjustment($file, $file_info);
michael@0 214
michael@0 215 $file_infos{$file} = $file_info;
michael@0 216 }
michael@0 217 return $file_info;
michael@0 218 }
michael@0 219
michael@0 220 # Ignore SIGPIPE as a workaround for addr2line crashes in some situations.
michael@0 221 $SIG{PIPE} = 'IGNORE';
michael@0 222
michael@0 223 select STDOUT; $| = 1; # make STDOUT unbuffered
michael@0 224 while (<>) {
michael@0 225 my $line = $_;
michael@0 226 if ($line =~ /^([ \|0-9-]*)(.*) ?\[([^ ]*) \+(0x[0-9A-F]{1,8})\](.*)$/) {
michael@0 227 my $before = $1; # allow preservation of balance trees
michael@0 228 my $badsymbol = $2;
michael@0 229 my $file = $3;
michael@0 230 my $address = hex($4);
michael@0 231 my $after = $5; # allow preservation of counts
michael@0 232
michael@0 233 if (-f $file) {
michael@0 234 my $file_info = get_file_info($file);
michael@0 235 my $result = $file_info->{cache}->{$address};
michael@0 236 if (not defined $result) {
michael@0 237 my $address2 = $address + $file_info->{address_adjustment};
michael@0 238 my $out = $file_info->{pipe_write};
michael@0 239 my $in = $file_info->{pipe_read};
michael@0 240 printf {$out} "0x%X\n", $address2;
michael@0 241 chomp(my $symbol = <$in>);
michael@0 242 chomp(my $fileandline = <$in>);
michael@0 243 if (!$symbol || $symbol eq '??') { $symbol = $badsymbol; }
michael@0 244 if (!$fileandline || $fileandline eq '??:0') {
michael@0 245 $fileandline = $file;
michael@0 246 }
michael@0 247 $result = "$symbol ($fileandline)";
michael@0 248 $file_info->{cache}->{$address} = $result;
michael@0 249 }
michael@0 250 print "$before$result$after\n";
michael@0 251 } else {
michael@0 252 print STDERR "Warning: File \"$file\" does not exist.\n";
michael@0 253 print $line;
michael@0 254 }
michael@0 255
michael@0 256 } else {
michael@0 257 print $line;
michael@0 258 }
michael@0 259 }

mercurial