1*4882a593Smuzhiyun#!/usr/bin/env perl 2*4882a593Smuzhiyun# SPDX-License-Identifier: GPL-2.0-only 3*4882a593Smuzhiyun 4*4882a593Smuzhiyunuse File::Basename; 5*4882a593Smuzhiyunuse Math::BigInt; 6*4882a593Smuzhiyunuse Getopt::Long; 7*4882a593Smuzhiyun 8*4882a593Smuzhiyun# Copyright 2008, Intel Corporation 9*4882a593Smuzhiyun# 10*4882a593Smuzhiyun# This file is part of the Linux kernel 11*4882a593Smuzhiyun# 12*4882a593Smuzhiyun# Authors: 13*4882a593Smuzhiyun# Arjan van de Ven <arjan@linux.intel.com> 14*4882a593Smuzhiyun 15*4882a593Smuzhiyun 16*4882a593Smuzhiyunmy $cross_compile = ""; 17*4882a593Smuzhiyunmy $vmlinux_name = ""; 18*4882a593Smuzhiyunmy $modulefile = ""; 19*4882a593Smuzhiyun 20*4882a593Smuzhiyun# Get options 21*4882a593SmuzhiyunGetopt::Long::GetOptions( 22*4882a593Smuzhiyun 'cross-compile|c=s' => \$cross_compile, 23*4882a593Smuzhiyun 'module|m=s' => \$modulefile, 24*4882a593Smuzhiyun 'help|h' => \&usage, 25*4882a593Smuzhiyun) || usage (); 26*4882a593Smuzhiyunmy $vmlinux_name = $ARGV[0]; 27*4882a593Smuzhiyunif (!defined($vmlinux_name)) { 28*4882a593Smuzhiyun my $kerver = `uname -r`; 29*4882a593Smuzhiyun chomp($kerver); 30*4882a593Smuzhiyun $vmlinux_name = "/lib/modules/$kerver/build/vmlinux"; 31*4882a593Smuzhiyun print "No vmlinux specified, assuming $vmlinux_name\n"; 32*4882a593Smuzhiyun} 33*4882a593Smuzhiyunmy $filename = $vmlinux_name; 34*4882a593Smuzhiyun 35*4882a593Smuzhiyun# Parse the oops to find the EIP value 36*4882a593Smuzhiyun 37*4882a593Smuzhiyunmy $target = "0"; 38*4882a593Smuzhiyunmy $function; 39*4882a593Smuzhiyunmy $module = ""; 40*4882a593Smuzhiyunmy $func_offset = 0; 41*4882a593Smuzhiyunmy $vmaoffset = 0; 42*4882a593Smuzhiyun 43*4882a593Smuzhiyunmy %regs; 44*4882a593Smuzhiyun 45*4882a593Smuzhiyun 46*4882a593Smuzhiyunsub parse_x86_regs 47*4882a593Smuzhiyun{ 48*4882a593Smuzhiyun my ($line) = @_; 49*4882a593Smuzhiyun if ($line =~ /EAX: ([0-9a-f]+) EBX: ([0-9a-f]+) ECX: ([0-9a-f]+) EDX: ([0-9a-f]+)/) { 50*4882a593Smuzhiyun $regs{"%eax"} = $1; 51*4882a593Smuzhiyun $regs{"%ebx"} = $2; 52*4882a593Smuzhiyun $regs{"%ecx"} = $3; 53*4882a593Smuzhiyun $regs{"%edx"} = $4; 54*4882a593Smuzhiyun } 55*4882a593Smuzhiyun if ($line =~ /ESI: ([0-9a-f]+) EDI: ([0-9a-f]+) EBP: ([0-9a-f]+) ESP: ([0-9a-f]+)/) { 56*4882a593Smuzhiyun $regs{"%esi"} = $1; 57*4882a593Smuzhiyun $regs{"%edi"} = $2; 58*4882a593Smuzhiyun $regs{"%esp"} = $4; 59*4882a593Smuzhiyun } 60*4882a593Smuzhiyun if ($line =~ /RAX: ([0-9a-f]+) RBX: ([0-9a-f]+) RCX: ([0-9a-f]+)/) { 61*4882a593Smuzhiyun $regs{"%eax"} = $1; 62*4882a593Smuzhiyun $regs{"%ebx"} = $2; 63*4882a593Smuzhiyun $regs{"%ecx"} = $3; 64*4882a593Smuzhiyun } 65*4882a593Smuzhiyun if ($line =~ /RDX: ([0-9a-f]+) RSI: ([0-9a-f]+) RDI: ([0-9a-f]+)/) { 66*4882a593Smuzhiyun $regs{"%edx"} = $1; 67*4882a593Smuzhiyun $regs{"%esi"} = $2; 68*4882a593Smuzhiyun $regs{"%edi"} = $3; 69*4882a593Smuzhiyun } 70*4882a593Smuzhiyun if ($line =~ /RBP: ([0-9a-f]+) R08: ([0-9a-f]+) R09: ([0-9a-f]+)/) { 71*4882a593Smuzhiyun $regs{"%r08"} = $2; 72*4882a593Smuzhiyun $regs{"%r09"} = $3; 73*4882a593Smuzhiyun } 74*4882a593Smuzhiyun if ($line =~ /R10: ([0-9a-f]+) R11: ([0-9a-f]+) R12: ([0-9a-f]+)/) { 75*4882a593Smuzhiyun $regs{"%r10"} = $1; 76*4882a593Smuzhiyun $regs{"%r11"} = $2; 77*4882a593Smuzhiyun $regs{"%r12"} = $3; 78*4882a593Smuzhiyun } 79*4882a593Smuzhiyun if ($line =~ /R13: ([0-9a-f]+) R14: ([0-9a-f]+) R15: ([0-9a-f]+)/) { 80*4882a593Smuzhiyun $regs{"%r13"} = $1; 81*4882a593Smuzhiyun $regs{"%r14"} = $2; 82*4882a593Smuzhiyun $regs{"%r15"} = $3; 83*4882a593Smuzhiyun } 84*4882a593Smuzhiyun} 85*4882a593Smuzhiyun 86*4882a593Smuzhiyunsub reg_name 87*4882a593Smuzhiyun{ 88*4882a593Smuzhiyun my ($reg) = @_; 89*4882a593Smuzhiyun $reg =~ s/r(.)x/e\1x/; 90*4882a593Smuzhiyun $reg =~ s/r(.)i/e\1i/; 91*4882a593Smuzhiyun $reg =~ s/r(.)p/e\1p/; 92*4882a593Smuzhiyun return $reg; 93*4882a593Smuzhiyun} 94*4882a593Smuzhiyun 95*4882a593Smuzhiyunsub process_x86_regs 96*4882a593Smuzhiyun{ 97*4882a593Smuzhiyun my ($line, $cntr) = @_; 98*4882a593Smuzhiyun my $str = ""; 99*4882a593Smuzhiyun if (length($line) < 40) { 100*4882a593Smuzhiyun return ""; # not an asm istruction 101*4882a593Smuzhiyun } 102*4882a593Smuzhiyun 103*4882a593Smuzhiyun # find the arguments to the instruction 104*4882a593Smuzhiyun if ($line =~ /([0-9a-zA-Z\,\%\(\)\-\+]+)$/) { 105*4882a593Smuzhiyun $lastword = $1; 106*4882a593Smuzhiyun } else { 107*4882a593Smuzhiyun return ""; 108*4882a593Smuzhiyun } 109*4882a593Smuzhiyun 110*4882a593Smuzhiyun # we need to find the registers that get clobbered, 111*4882a593Smuzhiyun # since their value is no longer relevant for previous 112*4882a593Smuzhiyun # instructions in the stream. 113*4882a593Smuzhiyun 114*4882a593Smuzhiyun $clobber = $lastword; 115*4882a593Smuzhiyun # first, remove all memory operands, they're read only 116*4882a593Smuzhiyun $clobber =~ s/\([a-z0-9\%\,]+\)//g; 117*4882a593Smuzhiyun # then, remove everything before the comma, thats the read part 118*4882a593Smuzhiyun $clobber =~ s/.*\,//g; 119*4882a593Smuzhiyun 120*4882a593Smuzhiyun # if this is the instruction that faulted, we haven't actually done 121*4882a593Smuzhiyun # the write yet... nothing is clobbered. 122*4882a593Smuzhiyun if ($cntr == 0) { 123*4882a593Smuzhiyun $clobber = ""; 124*4882a593Smuzhiyun } 125*4882a593Smuzhiyun 126*4882a593Smuzhiyun foreach $reg (keys(%regs)) { 127*4882a593Smuzhiyun my $clobberprime = reg_name($clobber); 128*4882a593Smuzhiyun my $lastwordprime = reg_name($lastword); 129*4882a593Smuzhiyun my $val = $regs{$reg}; 130*4882a593Smuzhiyun if ($val =~ /^[0]+$/) { 131*4882a593Smuzhiyun $val = "0"; 132*4882a593Smuzhiyun } else { 133*4882a593Smuzhiyun $val =~ s/^0*//; 134*4882a593Smuzhiyun } 135*4882a593Smuzhiyun 136*4882a593Smuzhiyun # first check if we're clobbering this register; if we do 137*4882a593Smuzhiyun # we print it with a =>, and then delete its value 138*4882a593Smuzhiyun if ($clobber =~ /$reg/ || $clobberprime =~ /$reg/) { 139*4882a593Smuzhiyun if (length($val) > 0) { 140*4882a593Smuzhiyun $str = $str . " $reg => $val "; 141*4882a593Smuzhiyun } 142*4882a593Smuzhiyun $regs{$reg} = ""; 143*4882a593Smuzhiyun $val = ""; 144*4882a593Smuzhiyun } 145*4882a593Smuzhiyun # now check if we're reading this register 146*4882a593Smuzhiyun if ($lastword =~ /$reg/ || $lastwordprime =~ /$reg/) { 147*4882a593Smuzhiyun if (length($val) > 0) { 148*4882a593Smuzhiyun $str = $str . " $reg = $val "; 149*4882a593Smuzhiyun } 150*4882a593Smuzhiyun } 151*4882a593Smuzhiyun } 152*4882a593Smuzhiyun return $str; 153*4882a593Smuzhiyun} 154*4882a593Smuzhiyun 155*4882a593Smuzhiyun# parse the oops 156*4882a593Smuzhiyunwhile (<STDIN>) { 157*4882a593Smuzhiyun my $line = $_; 158*4882a593Smuzhiyun if ($line =~ /EIP: 0060:\[\<([a-z0-9]+)\>\]/) { 159*4882a593Smuzhiyun $target = $1; 160*4882a593Smuzhiyun } 161*4882a593Smuzhiyun if ($line =~ /RIP: 0010:\[\<([a-z0-9]+)\>\]/) { 162*4882a593Smuzhiyun $target = $1; 163*4882a593Smuzhiyun } 164*4882a593Smuzhiyun if ($line =~ /EIP is at ([a-zA-Z0-9\_]+)\+0x([0-9a-f]+)\/0x[a-f0-9]/) { 165*4882a593Smuzhiyun $function = $1; 166*4882a593Smuzhiyun $func_offset = $2; 167*4882a593Smuzhiyun } 168*4882a593Smuzhiyun if ($line =~ /RIP: 0010:\[\<[0-9a-f]+\>\] \[\<[0-9a-f]+\>\] ([a-zA-Z0-9\_]+)\+0x([0-9a-f]+)\/0x[a-f0-9]/) { 169*4882a593Smuzhiyun $function = $1; 170*4882a593Smuzhiyun $func_offset = $2; 171*4882a593Smuzhiyun } 172*4882a593Smuzhiyun 173*4882a593Smuzhiyun # check if it's a module 174*4882a593Smuzhiyun if ($line =~ /EIP is at ([a-zA-Z0-9\_]+)\+(0x[0-9a-f]+)\/0x[a-f0-9]+\W\[([a-zA-Z0-9\_\-]+)\]/) { 175*4882a593Smuzhiyun $module = $3; 176*4882a593Smuzhiyun } 177*4882a593Smuzhiyun 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\_\-]+)\]/) { 178*4882a593Smuzhiyun $module = $3; 179*4882a593Smuzhiyun } 180*4882a593Smuzhiyun parse_x86_regs($line); 181*4882a593Smuzhiyun} 182*4882a593Smuzhiyun 183*4882a593Smuzhiyunmy $decodestart = Math::BigInt->from_hex("0x$target") - Math::BigInt->from_hex("0x$func_offset"); 184*4882a593Smuzhiyunmy $decodestop = Math::BigInt->from_hex("0x$target") + 8192; 185*4882a593Smuzhiyunif ($target eq "0") { 186*4882a593Smuzhiyun print "No oops found!\n"; 187*4882a593Smuzhiyun usage(); 188*4882a593Smuzhiyun} 189*4882a593Smuzhiyun 190*4882a593Smuzhiyun# if it's a module, we need to find the .ko file and calculate a load offset 191*4882a593Smuzhiyunif ($module ne "") { 192*4882a593Smuzhiyun if ($modulefile eq "") { 193*4882a593Smuzhiyun $modulefile = `modinfo -F filename $module`; 194*4882a593Smuzhiyun chomp($modulefile); 195*4882a593Smuzhiyun } 196*4882a593Smuzhiyun $filename = $modulefile; 197*4882a593Smuzhiyun if ($filename eq "") { 198*4882a593Smuzhiyun print "Module .ko file for $module not found. Aborting\n"; 199*4882a593Smuzhiyun exit; 200*4882a593Smuzhiyun } 201*4882a593Smuzhiyun # ok so we found the module, now we need to calculate the vma offset 202*4882a593Smuzhiyun open(FILE, $cross_compile."objdump -dS $filename |") || die "Cannot start objdump"; 203*4882a593Smuzhiyun while (<FILE>) { 204*4882a593Smuzhiyun if ($_ =~ /^([0-9a-f]+) \<$function\>\:/) { 205*4882a593Smuzhiyun my $fu = $1; 206*4882a593Smuzhiyun $vmaoffset = Math::BigInt->from_hex("0x$target") - Math::BigInt->from_hex("0x$fu") - Math::BigInt->from_hex("0x$func_offset"); 207*4882a593Smuzhiyun } 208*4882a593Smuzhiyun } 209*4882a593Smuzhiyun close(FILE); 210*4882a593Smuzhiyun} 211*4882a593Smuzhiyun 212*4882a593Smuzhiyunmy $counter = 0; 213*4882a593Smuzhiyunmy $state = 0; 214*4882a593Smuzhiyunmy $center = -1; 215*4882a593Smuzhiyunmy @lines; 216*4882a593Smuzhiyunmy @reglines; 217*4882a593Smuzhiyun 218*4882a593Smuzhiyunsub InRange { 219*4882a593Smuzhiyun my ($address, $target) = @_; 220*4882a593Smuzhiyun my $ad = "0x".$address; 221*4882a593Smuzhiyun my $ta = "0x".$target; 222*4882a593Smuzhiyun my $delta = Math::BigInt->from_hex($ad) - Math::BigInt->from_hex($ta); 223*4882a593Smuzhiyun 224*4882a593Smuzhiyun if (($delta > -4096) && ($delta < 4096)) { 225*4882a593Smuzhiyun return 1; 226*4882a593Smuzhiyun } 227*4882a593Smuzhiyun return 0; 228*4882a593Smuzhiyun} 229*4882a593Smuzhiyun 230*4882a593Smuzhiyun 231*4882a593Smuzhiyun 232*4882a593Smuzhiyun# first, parse the input into the lines array, but to keep size down, 233*4882a593Smuzhiyun# we only do this for 4Kb around the sweet spot 234*4882a593Smuzhiyun 235*4882a593Smuzhiyunopen(FILE, $cross_compile."objdump -dS --adjust-vma=$vmaoffset --start-address=$decodestart --stop-address=$decodestop $filename |") || die "Cannot start objdump"; 236*4882a593Smuzhiyun 237*4882a593Smuzhiyunwhile (<FILE>) { 238*4882a593Smuzhiyun my $line = $_; 239*4882a593Smuzhiyun chomp($line); 240*4882a593Smuzhiyun if ($state == 0) { 241*4882a593Smuzhiyun if ($line =~ /^([a-f0-9]+)\:/) { 242*4882a593Smuzhiyun if (InRange($1, $target)) { 243*4882a593Smuzhiyun $state = 1; 244*4882a593Smuzhiyun } 245*4882a593Smuzhiyun } 246*4882a593Smuzhiyun } 247*4882a593Smuzhiyun if ($state == 1) { 248*4882a593Smuzhiyun if ($line =~ /^([a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9]+)\:/) { 249*4882a593Smuzhiyun my $val = $1; 250*4882a593Smuzhiyun if (!InRange($val, $target)) { 251*4882a593Smuzhiyun last; 252*4882a593Smuzhiyun } 253*4882a593Smuzhiyun if ($val eq $target) { 254*4882a593Smuzhiyun $center = $counter; 255*4882a593Smuzhiyun } 256*4882a593Smuzhiyun } 257*4882a593Smuzhiyun $lines[$counter] = $line; 258*4882a593Smuzhiyun 259*4882a593Smuzhiyun $counter = $counter + 1; 260*4882a593Smuzhiyun } 261*4882a593Smuzhiyun} 262*4882a593Smuzhiyun 263*4882a593Smuzhiyunclose(FILE); 264*4882a593Smuzhiyun 265*4882a593Smuzhiyunif ($counter == 0) { 266*4882a593Smuzhiyun print "No matching code found \n"; 267*4882a593Smuzhiyun exit; 268*4882a593Smuzhiyun} 269*4882a593Smuzhiyun 270*4882a593Smuzhiyunif ($center == -1) { 271*4882a593Smuzhiyun print "No matching code found \n"; 272*4882a593Smuzhiyun exit; 273*4882a593Smuzhiyun} 274*4882a593Smuzhiyun 275*4882a593Smuzhiyunmy $start; 276*4882a593Smuzhiyunmy $finish; 277*4882a593Smuzhiyunmy $codelines = 0; 278*4882a593Smuzhiyunmy $binarylines = 0; 279*4882a593Smuzhiyun# now we go up and down in the array to find how much we want to print 280*4882a593Smuzhiyun 281*4882a593Smuzhiyun$start = $center; 282*4882a593Smuzhiyun 283*4882a593Smuzhiyunwhile ($start > 1) { 284*4882a593Smuzhiyun $start = $start - 1; 285*4882a593Smuzhiyun my $line = $lines[$start]; 286*4882a593Smuzhiyun if ($line =~ /^([a-f0-9]+)\:/) { 287*4882a593Smuzhiyun $binarylines = $binarylines + 1; 288*4882a593Smuzhiyun } else { 289*4882a593Smuzhiyun $codelines = $codelines + 1; 290*4882a593Smuzhiyun } 291*4882a593Smuzhiyun if ($codelines > 10) { 292*4882a593Smuzhiyun last; 293*4882a593Smuzhiyun } 294*4882a593Smuzhiyun if ($binarylines > 20) { 295*4882a593Smuzhiyun last; 296*4882a593Smuzhiyun } 297*4882a593Smuzhiyun} 298*4882a593Smuzhiyun 299*4882a593Smuzhiyun 300*4882a593Smuzhiyun$finish = $center; 301*4882a593Smuzhiyun$codelines = 0; 302*4882a593Smuzhiyun$binarylines = 0; 303*4882a593Smuzhiyunwhile ($finish < $counter) { 304*4882a593Smuzhiyun $finish = $finish + 1; 305*4882a593Smuzhiyun my $line = $lines[$finish]; 306*4882a593Smuzhiyun if ($line =~ /^([a-f0-9]+)\:/) { 307*4882a593Smuzhiyun $binarylines = $binarylines + 1; 308*4882a593Smuzhiyun } else { 309*4882a593Smuzhiyun $codelines = $codelines + 1; 310*4882a593Smuzhiyun } 311*4882a593Smuzhiyun if ($codelines > 10) { 312*4882a593Smuzhiyun last; 313*4882a593Smuzhiyun } 314*4882a593Smuzhiyun if ($binarylines > 20) { 315*4882a593Smuzhiyun last; 316*4882a593Smuzhiyun } 317*4882a593Smuzhiyun} 318*4882a593Smuzhiyun 319*4882a593Smuzhiyun 320*4882a593Smuzhiyunmy $i; 321*4882a593Smuzhiyun 322*4882a593Smuzhiyun 323*4882a593Smuzhiyun# start annotating the registers in the asm. 324*4882a593Smuzhiyun# this goes from the oopsing point back, so that the annotator 325*4882a593Smuzhiyun# can track (opportunistically) which registers got written and 326*4882a593Smuzhiyun# whos value no longer is relevant. 327*4882a593Smuzhiyun 328*4882a593Smuzhiyun$i = $center; 329*4882a593Smuzhiyunwhile ($i >= $start) { 330*4882a593Smuzhiyun $reglines[$i] = process_x86_regs($lines[$i], $center - $i); 331*4882a593Smuzhiyun $i = $i - 1; 332*4882a593Smuzhiyun} 333*4882a593Smuzhiyun 334*4882a593Smuzhiyun$i = $start; 335*4882a593Smuzhiyunwhile ($i < $finish) { 336*4882a593Smuzhiyun my $line; 337*4882a593Smuzhiyun if ($i == $center) { 338*4882a593Smuzhiyun $line = "*$lines[$i] "; 339*4882a593Smuzhiyun } else { 340*4882a593Smuzhiyun $line = " $lines[$i] "; 341*4882a593Smuzhiyun } 342*4882a593Smuzhiyun print $line; 343*4882a593Smuzhiyun if (defined($reglines[$i]) && length($reglines[$i]) > 0) { 344*4882a593Smuzhiyun my $c = 60 - length($line); 345*4882a593Smuzhiyun while ($c > 0) { print " "; $c = $c - 1; }; 346*4882a593Smuzhiyun print "| $reglines[$i]"; 347*4882a593Smuzhiyun } 348*4882a593Smuzhiyun if ($i == $center) { 349*4882a593Smuzhiyun print "<--- faulting instruction"; 350*4882a593Smuzhiyun } 351*4882a593Smuzhiyun print "\n"; 352*4882a593Smuzhiyun $i = $i +1; 353*4882a593Smuzhiyun} 354*4882a593Smuzhiyun 355*4882a593Smuzhiyunsub usage { 356*4882a593Smuzhiyun print <<EOT; 357*4882a593SmuzhiyunUsage: 358*4882a593Smuzhiyun dmesg | perl $0 [OPTION] [VMLINUX] 359*4882a593Smuzhiyun 360*4882a593SmuzhiyunOPTION: 361*4882a593Smuzhiyun -c, --cross-compile CROSS_COMPILE Specify the prefix used for toolchain. 362*4882a593Smuzhiyun -m, --module MODULE_DIRNAME Specify the module filename. 363*4882a593Smuzhiyun -h, --help Help. 364*4882a593SmuzhiyunEOT 365*4882a593Smuzhiyun exit; 366*4882a593Smuzhiyun} 367