[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.