[From the "I Just Thought I'd Share" file.] I was trying to trace down a bug in BASIC (which later turned out to be a bug in how I thought) and I got stuck at the point where I had a tracefile that led up to the crash. I got the trace with parrot's trace instruction. In BASIC I've got a built-in named "TRACE" to toggle it. So I said:
C:\projects\parrot\languages\basic\>basic.pl 2>tracefile Including stackops.pasm Including alpha.pasm Including dumpstack.pasm Including tokenize.pasm Including basicvar.pasm Including basic.pasm Including instructions.pasm Including expr.pasm 3727 lines Ready LOAD wumpus LOADING wumpus.bas...DONE Ready TRACE 1 Ready RUN So now I've got the parrot trace going out to tracefile. Once the program crashed (again my fault) I could tail the tracefile and it'd show me the last instructions evaluated. Of course, I have no idea what those really correlate to in the BASIC PASM source because all I get are the Program Counters. This is not a fun way to spend Friday night. And the disassembler is broken again, so I couldn't use that. So I filtered the tracefile through this bit o Perl and sent it to a file named "output": open(T, "tracefile") || die; $|++; while(<T>) { next unless /^PC=(\d+);\sOP=\d+\s\((\w+)\)/; $pc{$1}=$2; } foreach(sort { $a <=> $b } keys %pc) { print "PC=$_ $pc{$_}\n"; } So now I've got all of the instructions executed during the run sorted in PC order in "output"... And I've got the original PASM in a file called "test.pasm" (an intermediate file that basic.pl creates)... Most importantly I've got Perl. So I hacked together this little script: open(P, "test.pasm") || die "test.pasm: $!"; @program=<P>; chomp(@program); close(P); open(T, "outfile") || die "tracedump: $!"; @trace=<T>; chomp(@trace); close(T); sub neat { my($pc,$lab,$code)=@_; $pc=" " if $pc<0; $lab=" " unless $lab; $code=~s/^\s+//g; printf("%5s %8s %s\n", $pc, $lab, $code); } foreach(@program) { $orig=$_; if (/^\s*#/) { s/^\s+//; neat(-1, "", $orig); next; } if (/^\s*$/) { next; } if (m/^\s*(\w+):/) { # Label neat(-1, "${1}:", ""); s/^\s*\w+:\s*//; # Remove it redo; } s/^\s*//; if (! m/^([a-z]+)/) { die "Syntax error? $orig"; } $_=$1; if (@trace) { $instr=$trace[0]; $instr=~m/PC=(\d+)\s+([a-z]+)/; if ($_ eq $2) { neat($1, "", $orig); shift @trace; } else { neat(-1, "", $orig); } } } What it effectively does is merge the tracefile with the original PASM to give me 1. a mapping from PC to the original source code and 2. an execution coverage map for the program against the source code. The output looks something like: # Okay, found an ) went back to (, is the next thing a ~ ? TILDECK: 4477 bsr OPSTACKDEPTH 4479 restore I0 4481 eq I0, 0, GETTOP # Nope, apparently not. 4485 bsr POPOPSTACK 4487 set S1, "" 4490 restore S1 4492 eq S1, "~", GOTTILDE save S1 bsr PUSHOPSTACK # Oops, sorry. branch GETTOP GOTTILDE: 4502 concat S0, S1 # Mash that tilde on there. 4505 concat S0, "|" 4508 branch GETTOP CANPUSH: Which tells me a few things. First that the second bsr PUSHOPSTACK was never executed, and when I went looking for instruction at PC 4481 I went right to the correct place in the PASM and found my problem. (Which turned out to be a typo in the BASIC program I was working on! :) With a small modification, I can get output which shows me how many times each instruction (in the source) was executed. It's not perfect. There are places where it thinks it's found the instruction to match up with the PC and guesses wrong (it corrects shortly thereafter). Eventually I'd like to hack something like this into the assembler, but for now this suffices.