#!/usr/bin/perl # minixtrace.pl by Kanda.Motohiro@gmail.com May 1, 2008 # # Reads Bochs instruction trace file, looks up Minix kernel symbol map files, # and prints function call graph to stdout. use strict; use Carp; my $TAB_STOP = 4; my $debug=0; my ($line, @sortedSymbols, %symbols, $needPrint, $file, $indent); if (@ARGV == 1 && ($ARGV[0] eq '--help' || $ARGV[0] eq '-h' || $ARGV[0] eq '/?')) { usage(); } print "Generated by $0\n"; # On Minix, run nm /usr/src/kernel/kernel > kernel.map # and put the map files here on the host computer. if ( ! (-e 'kernel.map' && -e 'fs.map')) { fail("Please copy kernel.map and friends from Minix to current directory."); } foreach $file (<*.map>) { loadSymbols($file); } sortSymbols(); $needPrint = 1; $indent = 0; # Read and print Bochs trace records saved in debugger.out file. while (<>) { processOneRecord($_); } exit 0; sub loadSymbols { my ($file) = @_; my ($base, $prefix, $a, $b); #### You need to adjust module load addresses. if ($file =~ 'kernel.map') { $base = 0x1000; $prefix = ""; } elsif ($file =~ 'pm.map') { $base = 0x0100000; $prefix = "pm:"; } elsif ($file =~ 'fs.map') { $base = 0x011f000; $prefix = "fs:"; } elsif ($file =~ 'tty.map') { $base = 0x060a000; $prefix = "tty:"; } elsif ($file =~ 'mm.map') { $base = 0x061f000; $prefix = "mm:"; } elsif ($file =~ 'at.map') { $base = 0x067b400; $prefix = "at:"; # Add if you are interestead in ethernet drivers etc. } #### end need adjusting. open(F, $file) || fail($file); while () { my ($addr, $type, $name) = split; # Only global and local text symbols are used. if ($type ne 'T' && $type ne 't') { next;} # Ignore lines like 00001828 t exec.o if ($type eq 't' && $name =~ /\.o$/) { next;} if ($debug) { printf "%d: $addr $name\n", __LINE__; } # Remove leading underscore for readability. $name =~ s/^_//; $a = '0x' . $addr; $a = $base + hex($a);; $b = $prefix . $name; $symbols{$a} = $b; } # while close F; } sub processOneRecord { my ($line) = @_; my (@items, $a, $b, $cs, $phys, $offset, $inst, $sym); # These events are so important. Print. # CPU 0: Interrupt 0x50 occured (error_code=0x0000) if ($line =~ /Interrupt/) { print $line; return; } # If this is not an instruction trace record, print one also. if ( ! ($line =~ /^\(\d+\)/)) { #print $line; return; } # print irregular lines like (0) Breakpoint 1 ... if ( ! ($line =~ /^\(\d+\)\.\[\d+\]/)) { print $line; return; } # Parse one line of trace record. Here is a sample. # (0).[25007503] [0x001154ee] 0007:00000000000038ee (unk. ctxt): push eax ; 50 # (cpu).[ticks] [physical address] csreg:offset (symbol): nmemonic ; hex # See bx_dbg_disassemble_current in Bochs/cpu/cpu.cc. @items = split /\s+/, $line, 4; $a = $items[1]; $a =~ s/\[//; $a =~ s/\]//; $phys = hex($a); ($a, $b) = split /:/, $items[2]; $cs = hex('0x' . $a); $offset = hex('0x' . $b); (undef, $inst) = split /:/, $items[3]; chomp $inst; if ($debug) { printf "%d: phys=%x cs=%x offset=%x %s\n", __LINE__, $phys, $cs, $offset, $inst; } # Print if this is a call/return/interrupt return or # the next instruction of them. if ($inst =~ /call/ || $inst =~ /ret/) { $needPrint = 1; goto PRINT_LINE; } if ($needPrint) { $needPrint = 0; PRINT_LINE: $sym = phys2sym($phys); #printf "phys=%x cs=%x offset=%x %s %s\n", $phys, $cs, $offset, $sym, $inst; if ($indent < 0 || 80 <= $indent) { warn "indent=$indent"; $indent = 0; } print ' ' x ($TAB_STOP * $indent); if ($inst =~ /\s*ret\s+/) { print "$sym ret\n"; } else { print "$sym\n"; } # Adjust indentation for the next trace record print. # Ignore int/iretd. Only indent with call/return, for simplicity. if ($inst =~ /\s*call\s+/) { $indent ++; } elsif ($inst =~ /\s*ret\s+/) { $indent --; } } # if print } sub sortSymbols { my ($key, $i); foreach $key (sort { $a <=> $b } keys %symbols) { push @sortedSymbols, $key; push @sortedSymbols, $symbols{$key}; } if ($debug) { open S, ">System.map"; for ($i=0; $i<@sortedSymbols; $i+=2) { printf S "0x%x\t%s\n", $sortedSymbols[$i], $sortedSymbols[$i+1]; } close S; } } # Looks up map files and converts physical address to a symbol. sub phys2sym { my ($addr) = @_; my ($offset, $sym, $i, $start, $end); # -1 because the last symbol should not be used. for ($i=0; $i<(@sortedSymbols - 1); $i+=2) { # Find a symbol with start <= addr < end $start = $sortedSymbols[$i]; if ($addr < $start) { next; } $end = $sortedSymbols[$i+2]; if ($end <= $addr) { next; } # Found $sym = $sortedSymbols[$i+1]; # If addr falls between kernel module address ranges, it belongs to # userland proc, whose symbol map we do not know. if ($sym eq 'endtext' || $sym eq 'etext') { goto NOT_FOUND; } if ($addr != $start) { $offset = $addr - $start; $sym .= sprintf "+0x%x", $offset; } return $sym; } # for NOT_FOUND: # Symbol not found. Return hex addr. return sprintf "0x%x", $addr; } sub fail { confess "@_ $!"; } sub usage { print STDERR "usage: $0 [ debugger.out ]\n"; exit 1; } =begin stand alone test loadSymbols("kernel.map"); sortSymbols(); $line = "(0).[5282351013] [0x00000c00] 0030:0000000000000400 (unk. ctxt): cld ; fc\n"; processOneRecord($line); =cut __END__ Copyright (C) 2008 Kanda Motohiro This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Changes May 13 2008 Hand-adjusted kernel load address etc. for Minix 3.1.2a Ignore .o symbols which gnu nm generates. 00001828 T _do_exec 00001828 t exec.o # eof