#!perl.exe use strict; use warnings; use IO::Socket; ##################################################################### # Copyright (c) 2005 IBM Corporation and others. # All rights reserved. This program and the accompanying materials # are made available under the terms of the Eclipse Public License v1.0 # which accompanies this distribution, and is available at # http://www.eclipse.org/legal/epl-v10.html # # Contributors: # Bjorn Freeman-Benson - initial API and implementation ##################################################################### ##################################################################### # # # I N I T I A L I Z A T I O N A N D V A R I A B L E S # # # ##################################################################### # # The push down automata stack (the data stack) # my @stack; # # Load all the code into memory # The code is stored as an array of strings, each line of # the source file being one entry in the array. # my $filename = shift; open INFILE, $filename or die $!; my @code = ; close INFILE; my %labels; sub map_labels { # # A mapping of labels to indicies in the code array # %labels = ( ); my $idx = 0; while( $idx <= $#code ) { if( length $code[$idx] > 0 ) { $code[$idx] =~ /^\s*(.+?)\s*$/; $code[$idx] = $1; $labels{$1} = $idx if( $code[$idx] =~ /^:(\S+)/ ); } else { $code[$idx] = "\n"; } $idx ++; } } map_labels(); # # The stack of stack frames (the control stack) # Each stack frame is a mapping of variable names to values. # There are a number of special variable names: # _pc_ is the current program counter in the frame # the pc points to the next instruction to be executed # _func_ is the name of the function in this frame # my @frames; my $currentframe; $currentframe = { _pc_ => 0, _func_ => 'main' }; # # The command line argument to start a debug session. # my $debugflag = shift; # # The port to listen for debug commands on # and the port to send debug events to # my $debugport; my $debugport2; # # The socket to listen for debug commands on # and the socket to send debug events on # my $debugsock; my $debugsock2; # # An input buffer # my $debugbuf; # # Breakpoint array # breakpoints are stored as a boolean for each line of code # if the boolean is true, there is a breakpoint on that line # my @breakpoints; # # Mapping of debugger commands to functions that evaluate them # my %debug_commands = ( clear => \&debug_clear_breakpoint, data => \&debug_data, drop => \&debug_drop_frame, eval => \&debug_eval, eventstop => \&debug_event_stop, exit => \&debug_exit, popdata => \&debug_pop, pushdata => \&debug_push, resume => \&debug_resume, set => \&debug_set_breakpoint, setdata => \&debug_set_data, setvar => \&debug_set_variable, stack => \&debug_stack, step => \&debug_step, stepreturn => \&debug_step_return, suspend => \&debug_suspend, var => \&debug_var, watch => \&debug_watch ); # # The run flag is true if the VM is running. # If the run flag is false, the VM exits the # next time the main instruction loop runs. # my $run = 1; # # The suspend flag is true if the VM should suspend # running the program and just listen for debug commands. # my $suspend = 0; my $started = 1; $suspend = "client" if( $debugflag ); # # The step flag is used to control single-stepping. # See the implementation of the "step" debug command. # The stepreturn flag is used to control step-return. # The eventstops table holds which events cause suspends and which do not. # The watchpoints table holds watchpoint information. # variablename_stackframedepth => N # N = 0 is no watch # N = 1 is read watch # N = 2 is write watch # N = 3 is both, etc. # my $step = 0; my $stepreturn = 0; my %eventstops = ( "unimpinstr" => 0, "nosuchlabel" => 0, ); my %watchpoints = ( ); # # Mapping of the names of the instructions to the functions that evaluate them # my %instructions = ( add => \&add, branch_not_zero => \&branch_not_zero, call => \&call, dec => \&dec, dup => \&dup, halt => \&halt, output => \&output, pop => \&ipop, push => \&ipush, return => \&ireturn, var => \&var, xyzzy => \&internal_end_eval, ); ##################################################################### # # # M A I N I N T E R P R E T E R # # # ##################################################################### # # Open a debug session if the command line argument is given. # start_debugger(); send_debug_event( "started", 0 ); debug_ui() if( $suspend ); # # The main run loop # while( $run ) { check_for_breakpoint(); debug_ui() if( $suspend ); yield_to_debug(); my $instruction = fetch_instruction(); increment_pc(); do_one_instruction($instruction); if( $$currentframe{_pc_} > $#code ) { $run = 0; } elsif( $stepreturn ) { $instruction = fetch_instruction(); $suspend = "step" if( is_return_instruction($instruction) ); } } send_debug_event( "terminated", 0 ); sub fetch_instruction { my $pc = $$currentframe{_pc_}; my $theinstruction = $code[$pc]; return $theinstruction; } sub is_return_instruction { my $theinstruction = shift; if( $theinstruction =~ /^:/ ) { return 0; } elsif( $theinstruction =~ /^#/ ) { return 0; } else { $theinstruction =~ /^(\S+)\s*(.*)/; return $1 eq "return"; } } sub increment_pc { my $pc = $$currentframe{_pc_}; $pc++; $$currentframe{_pc_} = $pc; } sub decrement_pc { my $pc = $$currentframe{_pc_}; $pc--; $$currentframe{_pc_} = $pc; } sub do_one_instruction { my $theinstruction = shift; if( $theinstruction =~ /^:/ ) { # label $suspend = "step" if( $step ); } elsif( $theinstruction =~ /^#/ ) { # comment } else { $theinstruction =~ /^(\S+)\s*(.*)/; my $op = $1; my $instr = $instructions{$op}; if( $instr ) { &$instr( $theinstruction, $2 ); $suspend = "step" if( $step ); } else { send_debug_event( "unimplemented instruction $op", 1 ); if( $eventstops{"unimpinstr"} ) { $suspend = "event unimpinstr"; decrement_pc(); } } } } ##################################################################### # # # I N S T R U C T I O N S # # # ##################################################################### sub add { my $val1 = pop @stack; my $val2 = pop @stack; my $val = $val1 + $val2; push @stack, $val; } sub branch_not_zero { my $val = pop @stack; if( $val ) { shift; my $label = shift; my $dest = $labels{$label}; if( !defined $dest ) { send_debug_event( "no such label $label", 1 ); if( $eventstops{"nosuchlabel"} ) { $suspend = "event nosuchlabel"; push @stack, $val; decrement_pc(); } } else { $$currentframe{_pc_} = $dest; } } } sub call { shift; my $label = shift; my $dest = $labels{$label}; if( !defined $dest ) { send_debug_event( "no such label $label", 1 ); if( $eventstops{"nosuchlabel"} ) { $suspend = "event nosuchlabel"; decrement_pc(); } } else { push @frames, $currentframe; $currentframe = { _pc_ => $dest, _func_ => $label }; } } sub dec { my $val = pop @stack; $val--; push @stack, $val; } sub dup { my $val = pop @stack; push @stack, $val; push @stack, $val; } sub halt { $run = 0; } sub output { my $val = pop @stack; print "$val\n"; } sub ipop { shift; my $arg = shift; if( $arg =~ /^\$(.*)/ ) { $$currentframe{$1} = pop @stack; my $key = "$$currentframe{_func_}\:\:$1"; if( defined $watchpoints{$key} ) { if( $watchpoints{$key} & 2 ) { $suspend = "watch write $key"; } } } else { pop @stack; } } sub ipush { shift; my $arg = shift; if( $arg =~ /^\$(.*)/ ) { my $val = $$currentframe{$1}; push @stack, $val; my $key = "$$currentframe{_func_}\:\:$1"; if( defined $watchpoints{$key} ) { if( $watchpoints{$key} & 1 ) { $suspend = "watch read $key"; } } } else { push @stack, $arg; } } sub ireturn { $currentframe = pop @frames; } sub var { shift; my $name = shift; $$currentframe{$name} = 0; } ##################################################################### # # # D E B U G G E R I N T E R F A C E # # # ##################################################################### sub check_for_breakpoint { if( $debugflag ) { my $pc = $$currentframe{_pc_}; if( $breakpoints[$pc] ) { $suspend = "breakpoint $pc" unless $suspend eq "eval"; } } } # # For each instruction, we check the debug co-routine for # control input. If there is input, we process it. # sub yield_to_debug { if( $debugflag ) { my $bytes_to_read = 1024; my $bytes_read = sysread($debugsock, $debugbuf, $bytes_to_read); if( defined($bytes_read) ) { #print "read $bytes_to_read\n"; my $rin = ''; my $win = ''; my $ein = ''; vec($rin,fileno($debugsock),1) = 1; $ein = $rin | $win; my $debugline = $debugbuf; while( !($debugline =~ /\n/) ) { select($rin, undef, undef, undef); my $bytes_to_read = 1024; my $bytes_read = sysread($debugsock, $debugbuf, $bytes_to_read); $debugline .= $debugbuf; } #print "read: $debugline"; process_debug_command($debugline); $debugline = ''; } else { # no bytes read } } } # # If the execution is suspended, then we go into the debug # ui loop, reading and processing instructions. # sub debug_ui { return unless( $suspend ); my $pc = $$currentframe{_pc_}; if (!$started) { send_debug_event( "suspended $suspend", 0 ); } else { $started = 0; } $step = 0; $stepreturn = 0; my $rin = ''; my $win = ''; my $ein = ''; vec($rin,fileno($debugsock),1) = 1; $ein = $rin | $win; my $debugline = ''; while( $suspend ) { select($rin, undef, undef, undef); my $bytes_to_read = 1024; my $bytes_read = sysread($debugsock, $debugbuf, $bytes_to_read); $debugline .= $debugbuf; if( $debugline =~ /\n/ ) { #print "read: $debugline"; process_debug_command($debugline); $debugline = ''; } } send_debug_event( "resumed step", 0 ) if( $step ); send_debug_event( "resumed client", 0 ) unless( $step ); } sub process_debug_command { my $line = shift; return if( length $line < 2 ); my @words = split /\s/, $line; my $command = lc($words[0]); my $dfunc = $debug_commands{$words[0]}; if( $dfunc ) { &$dfunc( @words ); } } sub debug_clear_breakpoint { shift; my $line = shift; $breakpoints[$line] = 0; print $debugsock "ok\n"; } my @saved_code; my %saved_labels; my $saved_pc; sub debug_eval { shift; my $code = shift; my @lines = split /\|/, $code; my $newpc = scalar @code; @saved_code = @code; %saved_labels = %labels; foreach my $line ( @lines ) { $line =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg; push @code, $line; } push @code, "xyzzy"; map_labels(); $saved_pc = $$currentframe{_pc_}; $$currentframe{_pc_} = $newpc; print $debugsock "ok\n"; $suspend = 0; } sub internal_end_eval { my $result = pop @stack; @code = @saved_code; %labels = %saved_labels; $$currentframe{_pc_} = $saved_pc; send_debug_event( "evalresult $result", 0 ); $suspend = "eval"; } sub debug_data { my $result = ''; foreach my $d ( @stack ) { $result .= $d . '|'; } print $debugsock "$result\n"; } sub debug_drop_frame { ireturn(); decrement_pc(); print $debugsock "ok\n"; send_debug_event( "resumed drop", 0 ); send_debug_event( "suspended drop", 0 ); } sub debug_event_stop { shift; my $event = shift; my $bool = shift; $eventstops{$event} = $bool; print $debugsock "ok\n"; } sub debug_exit { print $debugsock "ok\n"; send_debug_event( "terminated", 0 ); exit 0; } sub debug_pop { pop @stack; print $debugsock "ok\n"; } sub debug_push { shift; my $value = shift; push @stack, $value; print $debugsock "ok\n"; } sub debug_resume { $suspend = 0; print $debugsock "ok\n"; } sub debug_set_breakpoint { shift; my $line = shift; $breakpoints[$line] = 1; print $debugsock "ok\n"; } sub debug_set_data { shift; my $offset = shift; my $value = shift; $stack[$offset] = $value; print $debugsock "ok\n"; } sub debug_set_variable { shift; my $sfnumber = shift; my $var = shift; my $value = shift; if( $sfnumber > $#frames ) { $$currentframe{$var} = $value; } else { my $theframe = $frames[$sfnumber]; $$theframe{$var} = $value; } print $debugsock "ok\n"; } sub debug_stack { my $result = ''; foreach my $frame ( @frames ) { $result .= print_frame($frame); $result .= '#'; } $result .= print_frame($currentframe); print $debugsock "$result\n"; } sub debug_step { # set suspend to 0 to allow the debug loop to exit back to # the instruction loop and thus run an instruction. However, # we want to come back to the debug loop right away, so the # step flag is set to true which will cause the suspend flag # to get set to true when we get to the next instruction. $step = 1; $suspend = 0; print $debugsock "ok\n"; } sub debug_step_return { $stepreturn = 1; $suspend = 0; print $debugsock "ok\n"; } sub debug_suspend { $suspend = "client"; print $debugsock "ok\n"; } sub debug_var { shift; my $sfnumber = shift; my $var = shift; if( $sfnumber > $#frames ) { print $debugsock "$$currentframe{$var}\n"; } else { my $theframe = $frames[$sfnumber]; print $debugsock "$$theframe{$var}\n"; } } sub debug_watch { shift; my $key = shift; my $value = shift; $watchpoints{$key} = $value; print $debugsock "ok\n"; } # # Some event has happened so notify the debugger. # If there is no debugger, we may still want to report the # event (such as if it is an error). # sub send_debug_event { my $event = shift; if( $debugflag ) { print $debugsock2 "$event\n"; } else { my $use_stderr = shift; print "Error: $event\n" if $use_stderr; } } # # The stack frame output is: # frame # frame # frame ... # where each frame is: # filename | line number | function name | var | var | var | var ... # sub print_frame { my $frame = shift; my $result = $filename; $result .= '|' . $$frame{_pc_}; $result .= '|' . $$frame{_func_}; for my $var ( keys %$frame ) { $result .= '|' . $var unless( substr($var,0,1) eq '_'); } return $result; } sub start_debugger { if( defined($debugflag) ) { if( $debugflag eq "-debug" ) { { # make STDOUT unbuffered my $ofh = select STDOUT; $| = 1; select $ofh; } $debugflag = 1; $debugport = shift @ARGV; $debugport2 = shift @ARGV; print "-debug $debugport $debugport2\n"; my $mainsock = new IO::Socket::INET (LocalHost => '127.0.0.1', LocalPort => $debugport, Listen => 1, Proto => 'tcp', Reuse => 1, ); $debugsock = $mainsock->accept(); my $set_it = "1"; my $ioctl_val = 0x80000000 | (4 << 16) | (ord('f') << 8) | 126; ioctl($debugsock, $ioctl_val, $set_it); #or die "couldn't set nonblocking: $^E"; $debugsock->blocking(0); my $mainsock2 = new IO::Socket::INET (LocalHost => '127.0.0.1', LocalPort => $debugport2, Listen => 1, Proto => 'tcp', Reuse => 1, ); $debugsock2 = $mainsock2->accept(); print "debug connection accepted\n"; } else { $debugflag = 0; } } }