Back to home page

LXR

 
 

    


0001 #!/usr/bin/perl
0002 
0003 use File::Basename;
0004 use Math::BigInt;
0005 use Getopt::Long;
0006 
0007 # Copyright 2008, Intel Corporation
0008 #
0009 # This file is part of the Linux kernel
0010 #
0011 # This program file is free software; you can redistribute it and/or modify it
0012 # under the terms of the GNU General Public License as published by the
0013 # Free Software Foundation; version 2 of the License.
0014 #
0015 # Authors:
0016 #   Arjan van de Ven <arjan@linux.intel.com>
0017 
0018 
0019 my $cross_compile = "";
0020 my $vmlinux_name = "";
0021 my $modulefile = "";
0022 
0023 # Get options
0024 Getopt::Long::GetOptions(
0025     'cross-compile|c=s' => \$cross_compile,
0026     'module|m=s'        => \$modulefile,
0027     'help|h'        => \&usage,
0028 ) || usage ();
0029 my $vmlinux_name = $ARGV[0];
0030 if (!defined($vmlinux_name)) {
0031     my $kerver = `uname -r`;
0032     chomp($kerver);
0033     $vmlinux_name = "/lib/modules/$kerver/build/vmlinux";
0034     print "No vmlinux specified, assuming $vmlinux_name\n";
0035 }
0036 my $filename = $vmlinux_name;
0037 
0038 # Parse the oops to find the EIP value
0039 
0040 my $target = "0";
0041 my $function;
0042 my $module = "";
0043 my $func_offset = 0;
0044 my $vmaoffset = 0;
0045 
0046 my %regs;
0047 
0048 
0049 sub parse_x86_regs
0050 {
0051     my ($line) = @_;
0052     if ($line =~ /EAX: ([0-9a-f]+) EBX: ([0-9a-f]+) ECX: ([0-9a-f]+) EDX: ([0-9a-f]+)/) {
0053         $regs{"%eax"} = $1;
0054         $regs{"%ebx"} = $2;
0055         $regs{"%ecx"} = $3;
0056         $regs{"%edx"} = $4;
0057     }
0058     if ($line =~ /ESI: ([0-9a-f]+) EDI: ([0-9a-f]+) EBP: ([0-9a-f]+) ESP: ([0-9a-f]+)/) {
0059         $regs{"%esi"} = $1;
0060         $regs{"%edi"} = $2;
0061         $regs{"%esp"} = $4;
0062     }
0063     if ($line =~ /RAX: ([0-9a-f]+) RBX: ([0-9a-f]+) RCX: ([0-9a-f]+)/) {
0064         $regs{"%eax"} = $1;
0065         $regs{"%ebx"} = $2;
0066         $regs{"%ecx"} = $3;
0067     }
0068     if ($line =~ /RDX: ([0-9a-f]+) RSI: ([0-9a-f]+) RDI: ([0-9a-f]+)/) {
0069         $regs{"%edx"} = $1;
0070         $regs{"%esi"} = $2;
0071         $regs{"%edi"} = $3;
0072     }
0073     if ($line =~ /RBP: ([0-9a-f]+) R08: ([0-9a-f]+) R09: ([0-9a-f]+)/) {
0074         $regs{"%r08"} = $2;
0075         $regs{"%r09"} = $3;
0076     }
0077     if ($line =~ /R10: ([0-9a-f]+) R11: ([0-9a-f]+) R12: ([0-9a-f]+)/) {
0078         $regs{"%r10"} = $1;
0079         $regs{"%r11"} = $2;
0080         $regs{"%r12"} = $3;
0081     }
0082     if ($line =~ /R13: ([0-9a-f]+) R14: ([0-9a-f]+) R15: ([0-9a-f]+)/) {
0083         $regs{"%r13"} = $1;
0084         $regs{"%r14"} = $2;
0085         $regs{"%r15"} = $3;
0086     }
0087 }
0088 
0089 sub reg_name
0090 {
0091     my ($reg) = @_;
0092     $reg =~ s/r(.)x/e\1x/;
0093     $reg =~ s/r(.)i/e\1i/;
0094     $reg =~ s/r(.)p/e\1p/;
0095     return $reg;
0096 }
0097 
0098 sub process_x86_regs
0099 {
0100     my ($line, $cntr) = @_;
0101     my $str = "";
0102     if (length($line) < 40) {
0103         return ""; # not an asm istruction
0104     }
0105 
0106     # find the arguments to the instruction
0107     if ($line =~ /([0-9a-zA-Z\,\%\(\)\-\+]+)$/) {
0108         $lastword = $1;
0109     } else {
0110         return "";
0111     }
0112 
0113     # we need to find the registers that get clobbered,
0114     # since their value is no longer relevant for previous
0115     # instructions in the stream.
0116 
0117     $clobber = $lastword;
0118     # first, remove all memory operands, they're read only
0119     $clobber =~ s/\([a-z0-9\%\,]+\)//g;
0120     # then, remove everything before the comma, thats the read part
0121     $clobber =~ s/.*\,//g;
0122 
0123     # if this is the instruction that faulted, we haven't actually done
0124     # the write yet... nothing is clobbered.
0125     if ($cntr == 0) {
0126         $clobber = "";
0127     }
0128 
0129     foreach $reg (keys(%regs)) {
0130         my $clobberprime = reg_name($clobber);
0131         my $lastwordprime = reg_name($lastword);
0132         my $val = $regs{$reg};
0133         if ($val =~ /^[0]+$/) {
0134             $val = "0";
0135         } else {
0136             $val =~ s/^0*//;
0137         }
0138 
0139         # first check if we're clobbering this register; if we do
0140         # we print it with a =>, and then delete its value
0141         if ($clobber =~ /$reg/ || $clobberprime =~ /$reg/) {
0142             if (length($val) > 0) {
0143                 $str = $str . " $reg => $val ";
0144             }
0145             $regs{$reg} = "";
0146             $val = "";
0147         }
0148         # now check if we're reading this register
0149         if ($lastword =~ /$reg/ || $lastwordprime =~ /$reg/) {
0150             if (length($val) > 0) {
0151                 $str = $str . " $reg = $val ";
0152             }
0153         }
0154     }
0155     return $str;
0156 }
0157 
0158 # parse the oops
0159 while (<STDIN>) {
0160     my $line = $_;
0161     if ($line =~ /EIP: 0060:\[\<([a-z0-9]+)\>\]/) {
0162         $target = $1;
0163     }
0164     if ($line =~ /RIP: 0010:\[\<([a-z0-9]+)\>\]/) {
0165         $target = $1;
0166     }
0167     if ($line =~ /EIP is at ([a-zA-Z0-9\_]+)\+0x([0-9a-f]+)\/0x[a-f0-9]/) {
0168         $function = $1;
0169         $func_offset = $2;
0170     }
0171     if ($line =~ /RIP: 0010:\[\<[0-9a-f]+\>\]  \[\<[0-9a-f]+\>\] ([a-zA-Z0-9\_]+)\+0x([0-9a-f]+)\/0x[a-f0-9]/) {
0172         $function = $1;
0173         $func_offset = $2;
0174     }
0175 
0176     # check if it's a module
0177     if ($line =~ /EIP is at ([a-zA-Z0-9\_]+)\+(0x[0-9a-f]+)\/0x[a-f0-9]+\W\[([a-zA-Z0-9\_\-]+)\]/) {
0178         $module = $3;
0179     }
0180     if ($line =~ /RIP: 0010:\[\<[0-9a-f]+\>\]  \[\<[0-9a-f]+\>\] ([a-zA-Z0-9\_]+)\+(0x[0-9a-f]+)\/0x[a-f0-9]+\W\[([a-zA-Z0-9\_\-]+)\]/) {
0181         $module = $3;
0182     }
0183     parse_x86_regs($line);
0184 }
0185 
0186 my $decodestart = Math::BigInt->from_hex("0x$target") - Math::BigInt->from_hex("0x$func_offset");
0187 my $decodestop = Math::BigInt->from_hex("0x$target") + 8192;
0188 if ($target eq "0") {
0189     print "No oops found!\n";
0190     usage();
0191 }
0192 
0193 # if it's a module, we need to find the .ko file and calculate a load offset
0194 if ($module ne "") {
0195     if ($modulefile eq "") {
0196         $modulefile = `modinfo -F filename $module`;
0197         chomp($modulefile);
0198     }
0199     $filename = $modulefile;
0200     if ($filename eq "") {
0201         print "Module .ko file for $module not found. Aborting\n";
0202         exit;
0203     }
0204     # ok so we found the module, now we need to calculate the vma offset
0205     open(FILE, $cross_compile."objdump -dS $filename |") || die "Cannot start objdump";
0206     while (<FILE>) {
0207         if ($_ =~ /^([0-9a-f]+) \<$function\>\:/) {
0208             my $fu = $1;
0209             $vmaoffset = Math::BigInt->from_hex("0x$target") - Math::BigInt->from_hex("0x$fu") - Math::BigInt->from_hex("0x$func_offset");
0210         }
0211     }
0212     close(FILE);
0213 }
0214 
0215 my $counter = 0;
0216 my $state   = 0;
0217 my $center  = -1;
0218 my @lines;
0219 my @reglines;
0220 
0221 sub InRange {
0222     my ($address, $target) = @_;
0223     my $ad = "0x".$address;
0224     my $ta = "0x".$target;
0225     my $delta = Math::BigInt->from_hex($ad) - Math::BigInt->from_hex($ta);
0226 
0227     if (($delta > -4096) && ($delta < 4096)) {
0228         return 1;
0229     }
0230     return 0;
0231 }
0232 
0233 
0234 
0235 # first, parse the input into the lines array, but to keep size down,
0236 # we only do this for 4Kb around the sweet spot
0237 
0238 open(FILE, $cross_compile."objdump -dS --adjust-vma=$vmaoffset --start-address=$decodestart --stop-address=$decodestop $filename |") || die "Cannot start objdump";
0239 
0240 while (<FILE>) {
0241     my $line = $_;
0242     chomp($line);
0243     if ($state == 0) {
0244         if ($line =~ /^([a-f0-9]+)\:/) {
0245             if (InRange($1, $target)) {
0246                 $state = 1;
0247             }
0248         }
0249     }
0250     if ($state == 1) {
0251         if ($line =~ /^([a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9]+)\:/) {
0252             my $val = $1;
0253             if (!InRange($val, $target)) {
0254                 last;
0255             }
0256             if ($val eq $target) {
0257                 $center = $counter;
0258             }
0259         }
0260         $lines[$counter] = $line;
0261 
0262         $counter = $counter + 1;
0263     }
0264 }
0265 
0266 close(FILE);
0267 
0268 if ($counter == 0) {
0269     print "No matching code found \n";
0270     exit;
0271 }
0272 
0273 if ($center == -1) {
0274     print "No matching code found \n";
0275     exit;
0276 }
0277 
0278 my $start;
0279 my $finish;
0280 my $codelines = 0;
0281 my $binarylines = 0;
0282 # now we go up and down in the array to find how much we want to print
0283 
0284 $start = $center;
0285 
0286 while ($start > 1) {
0287     $start = $start - 1;
0288     my $line = $lines[$start];
0289     if ($line =~ /^([a-f0-9]+)\:/) {
0290         $binarylines = $binarylines + 1;
0291     } else {
0292         $codelines = $codelines + 1;
0293     }
0294     if ($codelines > 10) {
0295         last;
0296     }
0297     if ($binarylines > 20) {
0298         last;
0299     }
0300 }
0301 
0302 
0303 $finish = $center;
0304 $codelines = 0;
0305 $binarylines = 0;
0306 while ($finish < $counter) {
0307     $finish = $finish + 1;
0308     my $line = $lines[$finish];
0309     if ($line =~ /^([a-f0-9]+)\:/) {
0310         $binarylines = $binarylines + 1;
0311     } else {
0312         $codelines = $codelines + 1;
0313     }
0314     if ($codelines > 10) {
0315         last;
0316     }
0317     if ($binarylines > 20) {
0318         last;
0319     }
0320 }
0321 
0322 
0323 my $i;
0324 
0325 
0326 # start annotating the registers in the asm.
0327 # this goes from the oopsing point back, so that the annotator
0328 # can track (opportunistically) which registers got written and
0329 # whos value no longer is relevant.
0330 
0331 $i = $center;
0332 while ($i >= $start) {
0333     $reglines[$i] = process_x86_regs($lines[$i], $center - $i);
0334     $i = $i - 1;
0335 }
0336 
0337 $i = $start;
0338 while ($i < $finish) {
0339     my $line;
0340     if ($i == $center) {
0341         $line =  "*$lines[$i] ";
0342     } else {
0343         $line =  " $lines[$i] ";
0344     }
0345     print $line;
0346     if (defined($reglines[$i]) && length($reglines[$i]) > 0) {
0347         my $c = 60 - length($line);
0348         while ($c > 0) { print " "; $c = $c - 1; };
0349         print "| $reglines[$i]";
0350     }
0351     if ($i == $center) {
0352         print "<--- faulting instruction";
0353     }
0354     print "\n";
0355     $i = $i +1;
0356 }
0357 
0358 sub usage {
0359     print <<EOT;
0360 Usage:
0361   dmesg | perl $0 [OPTION] [VMLINUX]
0362 
0363 OPTION:
0364   -c, --cross-compile CROSS_COMPILE Specify the prefix used for toolchain.
0365   -m, --module MODULE_DIRNAME       Specify the module filename.
0366   -h, --help                Help.
0367 EOT
0368     exit;
0369 }