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

Reply via email to