162306a36Sopenharmony_ci#!/usr/bin/env perl 262306a36Sopenharmony_ci# SPDX-License-Identifier: GPL-2.0-only 362306a36Sopenharmony_ci 462306a36Sopenharmony_ciuse File::Basename; 562306a36Sopenharmony_ciuse Math::BigInt; 662306a36Sopenharmony_ciuse Getopt::Long; 762306a36Sopenharmony_ci 862306a36Sopenharmony_ci# Copyright 2008, Intel Corporation 962306a36Sopenharmony_ci# 1062306a36Sopenharmony_ci# This file is part of the Linux kernel 1162306a36Sopenharmony_ci# 1262306a36Sopenharmony_ci# Authors: 1362306a36Sopenharmony_ci# Arjan van de Ven <arjan@linux.intel.com> 1462306a36Sopenharmony_ci 1562306a36Sopenharmony_ci 1662306a36Sopenharmony_cimy $cross_compile = ""; 1762306a36Sopenharmony_cimy $vmlinux_name = ""; 1862306a36Sopenharmony_cimy $modulefile = ""; 1962306a36Sopenharmony_ci 2062306a36Sopenharmony_ci# Get options 2162306a36Sopenharmony_ciGetopt::Long::GetOptions( 2262306a36Sopenharmony_ci 'cross-compile|c=s' => \$cross_compile, 2362306a36Sopenharmony_ci 'module|m=s' => \$modulefile, 2462306a36Sopenharmony_ci 'help|h' => \&usage, 2562306a36Sopenharmony_ci) || usage (); 2662306a36Sopenharmony_cimy $vmlinux_name = $ARGV[0]; 2762306a36Sopenharmony_ciif (!defined($vmlinux_name)) { 2862306a36Sopenharmony_ci my $kerver = `uname -r`; 2962306a36Sopenharmony_ci chomp($kerver); 3062306a36Sopenharmony_ci $vmlinux_name = "/lib/modules/$kerver/build/vmlinux"; 3162306a36Sopenharmony_ci print "No vmlinux specified, assuming $vmlinux_name\n"; 3262306a36Sopenharmony_ci} 3362306a36Sopenharmony_cimy $filename = $vmlinux_name; 3462306a36Sopenharmony_ci 3562306a36Sopenharmony_ci# Parse the oops to find the EIP value 3662306a36Sopenharmony_ci 3762306a36Sopenharmony_cimy $target = "0"; 3862306a36Sopenharmony_cimy $function; 3962306a36Sopenharmony_cimy $module = ""; 4062306a36Sopenharmony_cimy $func_offset = 0; 4162306a36Sopenharmony_cimy $vmaoffset = 0; 4262306a36Sopenharmony_ci 4362306a36Sopenharmony_cimy %regs; 4462306a36Sopenharmony_ci 4562306a36Sopenharmony_ci 4662306a36Sopenharmony_cisub parse_x86_regs 4762306a36Sopenharmony_ci{ 4862306a36Sopenharmony_ci my ($line) = @_; 4962306a36Sopenharmony_ci if ($line =~ /EAX: ([0-9a-f]+) EBX: ([0-9a-f]+) ECX: ([0-9a-f]+) EDX: ([0-9a-f]+)/) { 5062306a36Sopenharmony_ci $regs{"%eax"} = $1; 5162306a36Sopenharmony_ci $regs{"%ebx"} = $2; 5262306a36Sopenharmony_ci $regs{"%ecx"} = $3; 5362306a36Sopenharmony_ci $regs{"%edx"} = $4; 5462306a36Sopenharmony_ci } 5562306a36Sopenharmony_ci if ($line =~ /ESI: ([0-9a-f]+) EDI: ([0-9a-f]+) EBP: ([0-9a-f]+) ESP: ([0-9a-f]+)/) { 5662306a36Sopenharmony_ci $regs{"%esi"} = $1; 5762306a36Sopenharmony_ci $regs{"%edi"} = $2; 5862306a36Sopenharmony_ci $regs{"%esp"} = $4; 5962306a36Sopenharmony_ci } 6062306a36Sopenharmony_ci if ($line =~ /RAX: ([0-9a-f]+) RBX: ([0-9a-f]+) RCX: ([0-9a-f]+)/) { 6162306a36Sopenharmony_ci $regs{"%eax"} = $1; 6262306a36Sopenharmony_ci $regs{"%ebx"} = $2; 6362306a36Sopenharmony_ci $regs{"%ecx"} = $3; 6462306a36Sopenharmony_ci } 6562306a36Sopenharmony_ci if ($line =~ /RDX: ([0-9a-f]+) RSI: ([0-9a-f]+) RDI: ([0-9a-f]+)/) { 6662306a36Sopenharmony_ci $regs{"%edx"} = $1; 6762306a36Sopenharmony_ci $regs{"%esi"} = $2; 6862306a36Sopenharmony_ci $regs{"%edi"} = $3; 6962306a36Sopenharmony_ci } 7062306a36Sopenharmony_ci if ($line =~ /RBP: ([0-9a-f]+) R08: ([0-9a-f]+) R09: ([0-9a-f]+)/) { 7162306a36Sopenharmony_ci $regs{"%r08"} = $2; 7262306a36Sopenharmony_ci $regs{"%r09"} = $3; 7362306a36Sopenharmony_ci } 7462306a36Sopenharmony_ci if ($line =~ /R10: ([0-9a-f]+) R11: ([0-9a-f]+) R12: ([0-9a-f]+)/) { 7562306a36Sopenharmony_ci $regs{"%r10"} = $1; 7662306a36Sopenharmony_ci $regs{"%r11"} = $2; 7762306a36Sopenharmony_ci $regs{"%r12"} = $3; 7862306a36Sopenharmony_ci } 7962306a36Sopenharmony_ci if ($line =~ /R13: ([0-9a-f]+) R14: ([0-9a-f]+) R15: ([0-9a-f]+)/) { 8062306a36Sopenharmony_ci $regs{"%r13"} = $1; 8162306a36Sopenharmony_ci $regs{"%r14"} = $2; 8262306a36Sopenharmony_ci $regs{"%r15"} = $3; 8362306a36Sopenharmony_ci } 8462306a36Sopenharmony_ci} 8562306a36Sopenharmony_ci 8662306a36Sopenharmony_cisub reg_name 8762306a36Sopenharmony_ci{ 8862306a36Sopenharmony_ci my ($reg) = @_; 8962306a36Sopenharmony_ci $reg =~ s/r(.)x/e\1x/; 9062306a36Sopenharmony_ci $reg =~ s/r(.)i/e\1i/; 9162306a36Sopenharmony_ci $reg =~ s/r(.)p/e\1p/; 9262306a36Sopenharmony_ci return $reg; 9362306a36Sopenharmony_ci} 9462306a36Sopenharmony_ci 9562306a36Sopenharmony_cisub process_x86_regs 9662306a36Sopenharmony_ci{ 9762306a36Sopenharmony_ci my ($line, $cntr) = @_; 9862306a36Sopenharmony_ci my $str = ""; 9962306a36Sopenharmony_ci if (length($line) < 40) { 10062306a36Sopenharmony_ci return ""; # not an asm istruction 10162306a36Sopenharmony_ci } 10262306a36Sopenharmony_ci 10362306a36Sopenharmony_ci # find the arguments to the instruction 10462306a36Sopenharmony_ci if ($line =~ /([0-9a-zA-Z\,\%\(\)\-\+]+)$/) { 10562306a36Sopenharmony_ci $lastword = $1; 10662306a36Sopenharmony_ci } else { 10762306a36Sopenharmony_ci return ""; 10862306a36Sopenharmony_ci } 10962306a36Sopenharmony_ci 11062306a36Sopenharmony_ci # we need to find the registers that get clobbered, 11162306a36Sopenharmony_ci # since their value is no longer relevant for previous 11262306a36Sopenharmony_ci # instructions in the stream. 11362306a36Sopenharmony_ci 11462306a36Sopenharmony_ci $clobber = $lastword; 11562306a36Sopenharmony_ci # first, remove all memory operands, they're read only 11662306a36Sopenharmony_ci $clobber =~ s/\([a-z0-9\%\,]+\)//g; 11762306a36Sopenharmony_ci # then, remove everything before the comma, thats the read part 11862306a36Sopenharmony_ci $clobber =~ s/.*\,//g; 11962306a36Sopenharmony_ci 12062306a36Sopenharmony_ci # if this is the instruction that faulted, we haven't actually done 12162306a36Sopenharmony_ci # the write yet... nothing is clobbered. 12262306a36Sopenharmony_ci if ($cntr == 0) { 12362306a36Sopenharmony_ci $clobber = ""; 12462306a36Sopenharmony_ci } 12562306a36Sopenharmony_ci 12662306a36Sopenharmony_ci foreach $reg (keys(%regs)) { 12762306a36Sopenharmony_ci my $clobberprime = reg_name($clobber); 12862306a36Sopenharmony_ci my $lastwordprime = reg_name($lastword); 12962306a36Sopenharmony_ci my $val = $regs{$reg}; 13062306a36Sopenharmony_ci if ($val =~ /^[0]+$/) { 13162306a36Sopenharmony_ci $val = "0"; 13262306a36Sopenharmony_ci } else { 13362306a36Sopenharmony_ci $val =~ s/^0*//; 13462306a36Sopenharmony_ci } 13562306a36Sopenharmony_ci 13662306a36Sopenharmony_ci # first check if we're clobbering this register; if we do 13762306a36Sopenharmony_ci # we print it with a =>, and then delete its value 13862306a36Sopenharmony_ci if ($clobber =~ /$reg/ || $clobberprime =~ /$reg/) { 13962306a36Sopenharmony_ci if (length($val) > 0) { 14062306a36Sopenharmony_ci $str = $str . " $reg => $val "; 14162306a36Sopenharmony_ci } 14262306a36Sopenharmony_ci $regs{$reg} = ""; 14362306a36Sopenharmony_ci $val = ""; 14462306a36Sopenharmony_ci } 14562306a36Sopenharmony_ci # now check if we're reading this register 14662306a36Sopenharmony_ci if ($lastword =~ /$reg/ || $lastwordprime =~ /$reg/) { 14762306a36Sopenharmony_ci if (length($val) > 0) { 14862306a36Sopenharmony_ci $str = $str . " $reg = $val "; 14962306a36Sopenharmony_ci } 15062306a36Sopenharmony_ci } 15162306a36Sopenharmony_ci } 15262306a36Sopenharmony_ci return $str; 15362306a36Sopenharmony_ci} 15462306a36Sopenharmony_ci 15562306a36Sopenharmony_ci# parse the oops 15662306a36Sopenharmony_ciwhile (<STDIN>) { 15762306a36Sopenharmony_ci my $line = $_; 15862306a36Sopenharmony_ci if ($line =~ /EIP: 0060:\[\<([a-z0-9]+)\>\]/) { 15962306a36Sopenharmony_ci $target = $1; 16062306a36Sopenharmony_ci } 16162306a36Sopenharmony_ci if ($line =~ /RIP: 0010:\[\<([a-z0-9]+)\>\]/) { 16262306a36Sopenharmony_ci $target = $1; 16362306a36Sopenharmony_ci } 16462306a36Sopenharmony_ci if ($line =~ /EIP is at ([a-zA-Z0-9\_]+)\+0x([0-9a-f]+)\/0x[a-f0-9]/) { 16562306a36Sopenharmony_ci $function = $1; 16662306a36Sopenharmony_ci $func_offset = $2; 16762306a36Sopenharmony_ci } 16862306a36Sopenharmony_ci if ($line =~ /RIP: 0010:\[\<[0-9a-f]+\>\] \[\<[0-9a-f]+\>\] ([a-zA-Z0-9\_]+)\+0x([0-9a-f]+)\/0x[a-f0-9]/) { 16962306a36Sopenharmony_ci $function = $1; 17062306a36Sopenharmony_ci $func_offset = $2; 17162306a36Sopenharmony_ci } 17262306a36Sopenharmony_ci 17362306a36Sopenharmony_ci # check if it's a module 17462306a36Sopenharmony_ci if ($line =~ /EIP is at ([a-zA-Z0-9\_]+)\+(0x[0-9a-f]+)\/0x[a-f0-9]+\W\[([a-zA-Z0-9\_\-]+)\]/) { 17562306a36Sopenharmony_ci $module = $3; 17662306a36Sopenharmony_ci } 17762306a36Sopenharmony_ci 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\_\-]+)\]/) { 17862306a36Sopenharmony_ci $module = $3; 17962306a36Sopenharmony_ci } 18062306a36Sopenharmony_ci parse_x86_regs($line); 18162306a36Sopenharmony_ci} 18262306a36Sopenharmony_ci 18362306a36Sopenharmony_cimy $decodestart = Math::BigInt->from_hex("0x$target") - Math::BigInt->from_hex("0x$func_offset"); 18462306a36Sopenharmony_cimy $decodestop = Math::BigInt->from_hex("0x$target") + 8192; 18562306a36Sopenharmony_ciif ($target eq "0") { 18662306a36Sopenharmony_ci print "No oops found!\n"; 18762306a36Sopenharmony_ci usage(); 18862306a36Sopenharmony_ci} 18962306a36Sopenharmony_ci 19062306a36Sopenharmony_ci# if it's a module, we need to find the .ko file and calculate a load offset 19162306a36Sopenharmony_ciif ($module ne "") { 19262306a36Sopenharmony_ci if ($modulefile eq "") { 19362306a36Sopenharmony_ci $modulefile = `modinfo -F filename $module`; 19462306a36Sopenharmony_ci chomp($modulefile); 19562306a36Sopenharmony_ci } 19662306a36Sopenharmony_ci $filename = $modulefile; 19762306a36Sopenharmony_ci if ($filename eq "") { 19862306a36Sopenharmony_ci print "Module .ko file for $module not found. Aborting\n"; 19962306a36Sopenharmony_ci exit; 20062306a36Sopenharmony_ci } 20162306a36Sopenharmony_ci # ok so we found the module, now we need to calculate the vma offset 20262306a36Sopenharmony_ci open(FILE, $cross_compile."objdump -dS $filename |") || die "Cannot start objdump"; 20362306a36Sopenharmony_ci while (<FILE>) { 20462306a36Sopenharmony_ci if ($_ =~ /^([0-9a-f]+) \<$function\>\:/) { 20562306a36Sopenharmony_ci my $fu = $1; 20662306a36Sopenharmony_ci $vmaoffset = Math::BigInt->from_hex("0x$target") - Math::BigInt->from_hex("0x$fu") - Math::BigInt->from_hex("0x$func_offset"); 20762306a36Sopenharmony_ci } 20862306a36Sopenharmony_ci } 20962306a36Sopenharmony_ci close(FILE); 21062306a36Sopenharmony_ci} 21162306a36Sopenharmony_ci 21262306a36Sopenharmony_cimy $counter = 0; 21362306a36Sopenharmony_cimy $state = 0; 21462306a36Sopenharmony_cimy $center = -1; 21562306a36Sopenharmony_cimy @lines; 21662306a36Sopenharmony_cimy @reglines; 21762306a36Sopenharmony_ci 21862306a36Sopenharmony_cisub InRange { 21962306a36Sopenharmony_ci my ($address, $target) = @_; 22062306a36Sopenharmony_ci my $ad = "0x".$address; 22162306a36Sopenharmony_ci my $ta = "0x".$target; 22262306a36Sopenharmony_ci my $delta = Math::BigInt->from_hex($ad) - Math::BigInt->from_hex($ta); 22362306a36Sopenharmony_ci 22462306a36Sopenharmony_ci if (($delta > -4096) && ($delta < 4096)) { 22562306a36Sopenharmony_ci return 1; 22662306a36Sopenharmony_ci } 22762306a36Sopenharmony_ci return 0; 22862306a36Sopenharmony_ci} 22962306a36Sopenharmony_ci 23062306a36Sopenharmony_ci 23162306a36Sopenharmony_ci 23262306a36Sopenharmony_ci# first, parse the input into the lines array, but to keep size down, 23362306a36Sopenharmony_ci# we only do this for 4Kb around the sweet spot 23462306a36Sopenharmony_ci 23562306a36Sopenharmony_ciopen(FILE, $cross_compile."objdump -dS --adjust-vma=$vmaoffset --start-address=$decodestart --stop-address=$decodestop $filename |") || die "Cannot start objdump"; 23662306a36Sopenharmony_ci 23762306a36Sopenharmony_ciwhile (<FILE>) { 23862306a36Sopenharmony_ci my $line = $_; 23962306a36Sopenharmony_ci chomp($line); 24062306a36Sopenharmony_ci if ($state == 0) { 24162306a36Sopenharmony_ci if ($line =~ /^([a-f0-9]+)\:/) { 24262306a36Sopenharmony_ci if (InRange($1, $target)) { 24362306a36Sopenharmony_ci $state = 1; 24462306a36Sopenharmony_ci } 24562306a36Sopenharmony_ci } 24662306a36Sopenharmony_ci } 24762306a36Sopenharmony_ci if ($state == 1) { 24862306a36Sopenharmony_ci if ($line =~ /^([a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9]+)\:/) { 24962306a36Sopenharmony_ci my $val = $1; 25062306a36Sopenharmony_ci if (!InRange($val, $target)) { 25162306a36Sopenharmony_ci last; 25262306a36Sopenharmony_ci } 25362306a36Sopenharmony_ci if ($val eq $target) { 25462306a36Sopenharmony_ci $center = $counter; 25562306a36Sopenharmony_ci } 25662306a36Sopenharmony_ci } 25762306a36Sopenharmony_ci $lines[$counter] = $line; 25862306a36Sopenharmony_ci 25962306a36Sopenharmony_ci $counter = $counter + 1; 26062306a36Sopenharmony_ci } 26162306a36Sopenharmony_ci} 26262306a36Sopenharmony_ci 26362306a36Sopenharmony_ciclose(FILE); 26462306a36Sopenharmony_ci 26562306a36Sopenharmony_ciif ($counter == 0) { 26662306a36Sopenharmony_ci print "No matching code found \n"; 26762306a36Sopenharmony_ci exit; 26862306a36Sopenharmony_ci} 26962306a36Sopenharmony_ci 27062306a36Sopenharmony_ciif ($center == -1) { 27162306a36Sopenharmony_ci print "No matching code found \n"; 27262306a36Sopenharmony_ci exit; 27362306a36Sopenharmony_ci} 27462306a36Sopenharmony_ci 27562306a36Sopenharmony_cimy $start; 27662306a36Sopenharmony_cimy $finish; 27762306a36Sopenharmony_cimy $codelines = 0; 27862306a36Sopenharmony_cimy $binarylines = 0; 27962306a36Sopenharmony_ci# now we go up and down in the array to find how much we want to print 28062306a36Sopenharmony_ci 28162306a36Sopenharmony_ci$start = $center; 28262306a36Sopenharmony_ci 28362306a36Sopenharmony_ciwhile ($start > 1) { 28462306a36Sopenharmony_ci $start = $start - 1; 28562306a36Sopenharmony_ci my $line = $lines[$start]; 28662306a36Sopenharmony_ci if ($line =~ /^([a-f0-9]+)\:/) { 28762306a36Sopenharmony_ci $binarylines = $binarylines + 1; 28862306a36Sopenharmony_ci } else { 28962306a36Sopenharmony_ci $codelines = $codelines + 1; 29062306a36Sopenharmony_ci } 29162306a36Sopenharmony_ci if ($codelines > 10) { 29262306a36Sopenharmony_ci last; 29362306a36Sopenharmony_ci } 29462306a36Sopenharmony_ci if ($binarylines > 20) { 29562306a36Sopenharmony_ci last; 29662306a36Sopenharmony_ci } 29762306a36Sopenharmony_ci} 29862306a36Sopenharmony_ci 29962306a36Sopenharmony_ci 30062306a36Sopenharmony_ci$finish = $center; 30162306a36Sopenharmony_ci$codelines = 0; 30262306a36Sopenharmony_ci$binarylines = 0; 30362306a36Sopenharmony_ciwhile ($finish < $counter) { 30462306a36Sopenharmony_ci $finish = $finish + 1; 30562306a36Sopenharmony_ci my $line = $lines[$finish]; 30662306a36Sopenharmony_ci if ($line =~ /^([a-f0-9]+)\:/) { 30762306a36Sopenharmony_ci $binarylines = $binarylines + 1; 30862306a36Sopenharmony_ci } else { 30962306a36Sopenharmony_ci $codelines = $codelines + 1; 31062306a36Sopenharmony_ci } 31162306a36Sopenharmony_ci if ($codelines > 10) { 31262306a36Sopenharmony_ci last; 31362306a36Sopenharmony_ci } 31462306a36Sopenharmony_ci if ($binarylines > 20) { 31562306a36Sopenharmony_ci last; 31662306a36Sopenharmony_ci } 31762306a36Sopenharmony_ci} 31862306a36Sopenharmony_ci 31962306a36Sopenharmony_ci 32062306a36Sopenharmony_cimy $i; 32162306a36Sopenharmony_ci 32262306a36Sopenharmony_ci 32362306a36Sopenharmony_ci# start annotating the registers in the asm. 32462306a36Sopenharmony_ci# this goes from the oopsing point back, so that the annotator 32562306a36Sopenharmony_ci# can track (opportunistically) which registers got written and 32662306a36Sopenharmony_ci# whos value no longer is relevant. 32762306a36Sopenharmony_ci 32862306a36Sopenharmony_ci$i = $center; 32962306a36Sopenharmony_ciwhile ($i >= $start) { 33062306a36Sopenharmony_ci $reglines[$i] = process_x86_regs($lines[$i], $center - $i); 33162306a36Sopenharmony_ci $i = $i - 1; 33262306a36Sopenharmony_ci} 33362306a36Sopenharmony_ci 33462306a36Sopenharmony_ci$i = $start; 33562306a36Sopenharmony_ciwhile ($i < $finish) { 33662306a36Sopenharmony_ci my $line; 33762306a36Sopenharmony_ci if ($i == $center) { 33862306a36Sopenharmony_ci $line = "*$lines[$i] "; 33962306a36Sopenharmony_ci } else { 34062306a36Sopenharmony_ci $line = " $lines[$i] "; 34162306a36Sopenharmony_ci } 34262306a36Sopenharmony_ci print $line; 34362306a36Sopenharmony_ci if (defined($reglines[$i]) && length($reglines[$i]) > 0) { 34462306a36Sopenharmony_ci my $c = 60 - length($line); 34562306a36Sopenharmony_ci while ($c > 0) { print " "; $c = $c - 1; }; 34662306a36Sopenharmony_ci print "| $reglines[$i]"; 34762306a36Sopenharmony_ci } 34862306a36Sopenharmony_ci if ($i == $center) { 34962306a36Sopenharmony_ci print "<--- faulting instruction"; 35062306a36Sopenharmony_ci } 35162306a36Sopenharmony_ci print "\n"; 35262306a36Sopenharmony_ci $i = $i +1; 35362306a36Sopenharmony_ci} 35462306a36Sopenharmony_ci 35562306a36Sopenharmony_cisub usage { 35662306a36Sopenharmony_ci print <<EOT; 35762306a36Sopenharmony_ciUsage: 35862306a36Sopenharmony_ci dmesg | perl $0 [OPTION] [VMLINUX] 35962306a36Sopenharmony_ci 36062306a36Sopenharmony_ciOPTION: 36162306a36Sopenharmony_ci -c, --cross-compile CROSS_COMPILE Specify the prefix used for toolchain. 36262306a36Sopenharmony_ci -m, --module MODULE_DIRNAME Specify the module filename. 36362306a36Sopenharmony_ci -h, --help Help. 36462306a36Sopenharmony_ciEOT 36562306a36Sopenharmony_ci exit; 36662306a36Sopenharmony_ci} 367