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