#!/usr/bin/perl # # a7out: user-mode simulator for PDP-7 Unix applications # # (c) 2016 Warren Toomey, GPL3 # use strict; use warnings; use Data::Dumper; ### Global variables ### my $debug = 0; # Debug flag my $singlestep = 0; # Are we running in single-step mode? my %Breakpoint; # Hash of defined breakpoints my @Mem; # 8K 18-bit words of main memory my @FD; # Array of open filehandles # Registers my $PC = 0; # Program counter my $AC = 0; # Accumulator my $LINK = 0; # Link register, either 0 or LINKMASK my $MQ = 0; # MQ register # Constants use constant MAXINT => 0777777; # Biggest unsigned integer use constant MAXPOSINT => 0377777; # Biggest signed integer use constant MAXADDR => 017777; # Largest memory address use constant LINKMASK => 01000000; # Mask for LINK register use constant EAESTEP => 077; # EAE step count mask use constant EAEIMASK => 0777700; # EAE instruction mask use constant SIGN => 0400000; # Sign bit ### Main program ### # Get any optional arguments while ( defined( $ARGV[0] ) && ( $ARGV[0] =~ m{^-} ) ) { # -d: debug mode if ( $ARGV[0] eq "-d" ) { $debug = 1; shift(@ARGV); } # -b: set a breakpoint if ( $ARGV[0] eq "-b" ) { shift(@ARGV); $Breakpoint{ oct( shift(@ARGV) ) } = 1; } } # Check the arguments die("Usage: $0 [-d] [-b breakpoint] a.outfile [arg1 arg2 ...]\n") if ( @ARGV < 1 ); # Load the a.out file into memory # and simulate it load_code( $ARGV[0] ); set_arguments(); #dump_memory(0, MAXADDR, 0); #exit(0); simulate(); exit(0); ### Load the a.out file into memory sub load_code { my $filename = shift; # Fill all the 8K words in memory with zeroes foreach my $i ( 0 .. MAXADDR ) { $Mem[$i] = 0; } # Set up some open filehandles $FD[0] = \*STDIN; $FD[1] = \*STDOUT; $FD[8] = \*STDERR; # According to cat.s (uses d8 == 8) # Open up the PDP-7 executable file open( my $IN, "<", $filename ) || die("Unable to open $filename: $!\n"); while (<$IN>) { chomp; # Lose any textual stuff after a tab character $_ =~ s{\t.*}{}; # Split into location and value, both in octal my ( $loc, $val ) = split( /:\s+/, $_ ); # Convert from octal and save $loc = oct($loc); $val = oct($val); $Mem[$loc] = $val; } close($IN); } ### Copy the arguments into the PDP-7 memory space, and build ### an array of pointers to these arguments. Build a pointer ### at MAXADDR that points at the array. ### ### Each argument string is four words long and space padded if the ### string is not eight characters long. These are stored below ### address MAXADDR. Below this is the count of words in the strings. ### Address MAXADDR points at the word count. Graphically (for two arguments): ### ### +------------+ ### +--| | Location 017777 (MAXADDR) ### | +------------+ ### | |............| ### | |............| argv[2] ### | |............| ### | +------------+ ### | |............| ### | |............| argv[1] ### | |............| ### | +------------+ ### | |............| ### | |............| argv[0] ### | |............| ### | +------------+ ### +->| argc=12 | ### +------------+ ### sub set_arguments { # Get the number of arguments including the command name my $argc = scalar(@ARGV); # We now know that argc will appear in memory # 4*argc +1 below location MAXADDR # Set argc to the number of words my $addr = MAXADDR - ( 4 * $argc + 1 ); $Mem[MAXADDR] = $addr; $Mem[ $addr++ ] = $argc * 4; # Now start saving the arguments foreach (@ARGV) { # Truncate and/or space pad the argument my $str = sprintf( "%-8s", substr( $_, 0, 8 ) ); # Store pairs of characters into memory for ( my $i = 0 ; $i < length($str) ; $i += 2 ) { my $c1 = substr( $str, $i, 1 ) || ""; my $c2 = substr( $str, $i + 1, 1 ) || ""; #printf("Saving %06o to %06o\n", (ord($c1) << 9 ) | ord($c2), $addr); $Mem[ $addr++ ] = ( ord($c1) << 9 ) | ord($c2); } } } ### Simulate the machine code loaded into memory sub simulate { # List of opcodes that we can simulate my %Oplist = ( oct("000") => \&cal, oct("004") => \&dac, oct("010") => \&jms, oct("014") => \&dzm, oct("020") => \&lac, oct("030") => \&add, oct("024") => \&xor, oct("034") => \&tad, oct("044") => \&isz, oct("050") => \&and, oct("054") => \&sad, oct("060") => \&jmp, oct("064") => \&eae, oct("074") => \&opr, ); # Loop indefinitely while (1) { # Get the instruction pointed to by PC and decode it my $instruction = $Mem[$PC]; my $opcode = ( $instruction >> 12 ) & 074; my $indirect = ( $instruction >> 13 ) & 1; my $addr = $instruction & MAXADDR; # Work out what any indirect address would be my $indaddr = ($indirect) ? $Mem[$addr] & MAXADDR : $addr; # If this is a breakpoint, stop now and get a user command if ( defined( $Breakpoint{$PC} ) ) { $singlestep = 1; dprintf( "break at PC %06o\n", $PC ); } get_user_command() if ($singlestep); dprintf( "PC %06o: ", $PC ); #dprintf("PC %06o: instr %06o, op %03o, in %o, addr %06o indaddr %06o\n", # $PC, $instruction, $opcode, $indirect, $addr, $indaddr ); # Simulate the instruction. Each subroutine updates the $PC if ( defined( $Oplist{$opcode} ) ) { $Oplist{$opcode}->( $instruction, $addr, $indaddr ); } else { printf( STDERR "Unknown instruction 0%06o at location 0%06o\n", $instruction, $PC ); exit(1); } } } # Debug code: dump memory contents # Print from $start to $end. # Print empty locations if $yeszero sub dump_memory { my ( $start, $end, $yeszero ) = @_; foreach my $i ( $start .. $end ) { printf( STDERR "%06o: %06o\n", $i, $Mem[$i] ) if ( $yeszero || $Mem[$i] != 0 ); } } # Load AC sub lac { my ( $instruction, $addr, $indaddr ) = @_; dprintf( "lac %06o (value %06o) into AC\n", $indaddr, $Mem[$indaddr] ); $AC = $Mem[$indaddr]; $PC++; } # Deposit AC sub dac { my ( $instruction, $addr, $indaddr ) = @_; dprintf( "dac AC (value %06o) into %06o\n", $AC, $indaddr ); $Mem[$indaddr] = $AC; $PC++; } # Add to AC, twos complement sub tad { my ( $instruction, $addr, $indaddr ) = @_; dprintf( "tad AC (value %06o) with addr %06o (%06o)\n", $AC, $indaddr, $Mem[$indaddr] ); $AC = $AC + $Mem[$indaddr]; $AC = $AC & MAXINT; $LINK = $AC & LINKMASK; $PC++; } # Add to AC, ones complement sub add { my ( $instruction, $addr, $indaddr ) = @_; dprintf( "add AC (value %06o) with addr %06o (%06o)\n", $AC, $indaddr, $Mem[$indaddr] ); # This logic shamelessly borrowed from SimH # https://github.com/simh/simh/blob/master/PDP18B/pdp18b_cpu.c my $sum = $AC + $Mem[$indaddr]; if ( $sum > MAXINT ) { # end around carry $sum = ( $sum + 1 ) & MAXINT; } if ( ( ( ~$AC ^ $sum ) & ( $AC ^ $sum ) ) & SIGN ) { # overflow? $LINK = LINKMASK; # set link } $AC = $sum; $PC++; } # And AC and Y sub and { my ( $instruction, $addr, $indaddr ) = @_; dprintf( "and AC (value %06o) with addr %06o (%06o)\n", $AC, $indaddr, $Mem[$indaddr] ); $AC &= $Mem[$indaddr]; $PC++; } # Xor AC and Y sub xor { my ( $instruction, $addr, $indaddr ) = @_; dprintf( "xor AC (value %06o) with addr %06o (%06o)\n", $AC, $indaddr, $Mem[$indaddr] ); $AC ^= $Mem[$indaddr]; $PC++; } # Skip if AC different to Y sub sad { my ( $instruction, $addr, $indaddr ) = @_; dprintf( "sad AC %06o cf. %06o\n", $AC, $Mem[$indaddr] ); $PC += ( $AC != $Mem[$indaddr] ) ? 2 : 1; } # Deposit zero in memory sub dzm { my ( $instruction, $addr, $indaddr ) = @_; dprintf( "dzm into %06o\n", $indaddr ); $Mem[$indaddr] = 0; $PC++; } # Index and skip if zero sub isz { my ( $instruction, $addr, $indaddr ) = @_; dprintf( "isz %06o (value %06o)\n", $indaddr, $Mem[$indaddr] ); $Mem[$indaddr]++; $Mem[$indaddr] &= MAXINT; $PC += ( $Mem[$indaddr] == 0 ) ? 2 : 1; } # Jump sub jmp { my ( $instruction, $addr, $indaddr ) = @_; dprintf( "jmp %06o\n", $indaddr ); $PC = $indaddr; } # Jump to subroutine sub jms { my ( $instruction, $addr, $indaddr ) = @_; dprintf( "jms %06o\n", $indaddr ); # Save the LINK and current PC into the $indaddr location $Mem[ $indaddr++ ] = $PC + 1 | ( ($LINK) ? 0400000 : 0 ); $PC = $indaddr; } # OPR instructions sub opr { my ( $instruction, $addr, $indaddr ) = @_; # hlt: halt simulation if ( $instruction == 0740040 ) { printf( STDERR "PC %06o: program halted\n", $PC ); dump_memory( 0, MAXADDR, 0 ) if ($debug); exit(1); } # law: load word into AC my $indirect = ( $instruction >> 13 ) & 1; if ($indirect) { dprintf( "law %06o into AC\n", $instruction ); $AC = $instruction; $PC++; return; } # List of skip opcode names for the next section my @skipop = ( '', 'sma', 'sza', 'sza sma', 'snl', 'snl sma', 'snl sza', 'snl sza sma', 'skp', 'spa', 'sna', 'sna spa', 'szl', 'szl spa', 'szl sna', 'szl sna spa' ); # This logic shamelessly borrowed from SimH # https://github.com/simh/simh/blob/master/PDP18B/pdp18b_cpu.c my $skip = 0; my $i = ( $instruction >> 6 ) & 017; # decode IR<8:11> dprintf( "L.AC %d.%06o %s", ($LINK) ? 1 : 0, $AC, $skipop[$i] ); $skip = 1 if ( ( $i == 1 ) && ( $AC & SIGN ) != 0 ); # sma $skip = 1 if ( ( $i == 2 ) && ( $AC & MAXINT ) == 0 ); # sza $skip = 1 if ( ( $i == 3 ) && ( ( ( $AC & MAXINT ) == 0 ) || ( ( $AC & SIGN ) != 0 ) ) ) ; # sza | sma $skip = 1 if ( ( $i == 4 ) && ($LINK) ); # snl $skip = 1 if ( ( $i == 5 ) && ( $LINK || ( $AC >= SIGN ) ) ); # snl | sma $skip = 1 if ( ( $i == 6 ) && ( $LINK || ( $AC == 0 ) ) ); # snl | sza $skip = 1 if ( ( $i == 7 ) && ( $LINK || ( $AC >= SIGN ) || ( $AC == 0 ) ) ); # snl | sza | sma $skip = 1 if ( $i == 010 ); # skp $skip = 1 if ( ( $i == 011 ) && ( ( $AC & SIGN ) == 0 ) ); # spa $skip = 1 if ( ( $i == 012 ) && ( ( $AC & MAXINT ) != 0 ) ); # sna $skip = 1 if ( ( $i == 013 ) && ( ( $AC & MAXINT ) != 0 ) && ( ( $AC & SIGN ) == 0 ) ); # sna & spa $skip = 1 if ( ( $i == 014 ) && ( $LINK == 0 ) ); # szl $skip = 1 if ( ( $i == 015 ) && ( $LINK == 0 ) && ( $AC < SIGN ) ); # szl & spa $skip = 1 if ( ( $i == 016 ) && ( $LINK == 0 ) && ( $AC != 0 ) ); # szl & sna $skip = 1 if ( ( $i == 017 ) && ( $LINK == 0 ) && ( $AC != 0 ) && ( $AC != 0 ) ); # szl & sna & spa # Clear operations if ( $instruction & 010000 ) { # cla dprintf(" cla"); $AC = 0; } if ( $instruction & 004000 ) { # cli dprintf(" cli"); $LINK = 0; } if ( $instruction & 000002 ) { # cmi dprintf(" cmi"); $LINK = ($LINK) ? 0 : LINKMASK; } if ( $instruction & 000001 ) { # cma dprintf(" cma"); $AC = ( $AC ^ MAXINT ) & MAXINT; } # Rotate instructions $i = $instruction & 02030; # Single rotate right if ( $i == 020 ) { dprintf(" rar"); my $newlink = ( $AC & 1 ) ? LINKMASK : 0; $AC = ( $LINK | $AC ) >> 1; $LINK = $newlink; } # Double rotate right if ( $i == 02020 ) { dprintf(" rtr"); my $msb = ( $AC & 1 ) << 17; my $newlink = ( $AC & 2 ) ? LINKMASK : 0; $AC = ( ( $LINK | $AC ) >> 2 ) | $msb; $LINK = $newlink; } # Single rotate left if ( $i == 010 ) { dprintf(" ral"); my $newlink = ( $AC & SIGN ) ? LINKMASK : 0; my $lsb = $LINK ? 1 : 0; $AC = ( ( $AC << 1 ) | $lsb ) & MAXINT; $LINK = $newlink; } # Double rotate left if ( $i == 02010 ) { dprintf(" rtl"); my $newlink = ( $AC & 0200000 ) ? LINKMASK : 0; my $lsb = ( $AC & SIGN ) ? 1 : 0; my $twolsb = $LINK ? 2 : 0; $AC = ( ( $AC << 2 ) | $twolsb | $lsb ) & MAXINT; $LINK = $newlink; } # Impossible left and right rotates: 02030 or 00030. Do nothing! # Note: We didn't do the oas instruction above. $PC += 1 + $skip; dprintf("\n"); return; } # cal: used for system calls sub cal { my ( $instruction, $addr, $indaddr ) = @_; # Syscalls that we can simulate my %Syscallist = ( 3 => \&sys_open, 4 => \&sys_read, 5 => \&sys_write, 6 => \&sys_creat, 9 => \&sys_close, 14 => \&sys_exit, ); # Simulate the syscall. Each syscall updates the $PC if ( defined( $Syscallist{$addr} ) ) { $Syscallist{$addr}->(); } else { printf( STDERR "PC %06o: Unknown syscall %d\n", $PC, $addr ); exit(1); } } # Extended arithmetic element instructions sub eae { my ( $instruction, $addr, $indaddr ) = @_; my $step = $instruction & EAESTEP; $instruction &= EAEIMASK; if ( $instruction == 0660500 ) { # lrss: long right shift, signed # We ignore the MQ as it's not # used by any user-mode programs dprintf( "lrss %06o AC step %d\n", $AC, $step ); # Save the AC's sign into LINK my $newlink = ( $AC << 1 ) & LINKMASK; $AC = ( ( $LINK | $AC ) >> $step ) & MAXINT; $LINK = $newlink; $PC++; return; } if ( $instruction == 0660700 ) { # alss: long left shift, signed # We don't fill the lsb with LINK yet dprintf( "alss %06o AC step %d\n", $AC, $step ); $AC = ( $AC << $step ) & MAXINT; $PC++; return; } printf( STDERR "PC %06o: Unknown eae instruction %06o\n", $PC, $instruction ); exit(1); } # Exit system call sub sys_exit { dprintf("exit system call\n"); exit(0); } # Close system call sub sys_close { # AC is the file descriptor my $fd = $AC; dprintf( "close: closing fd %d\n", $fd ); # Bump up the PC $PC += 1; # That filehandle is not open, set an error -1 in octal if ( !defined( $FD[$fd] ) ) { dprint("close: fd $fd is not open\n"); $AC = MAXINT; return; } close( $FD[$fd] ); $FD[$fd] = undef; $AC = 0; return; } # Common code for creat and open sub creatopen { my ($filename, $readorwrite)= @_; # Open the file if ( open( my $FH, $readorwrite, $filename ) ) { # Find a place in the @FD array to store this filehandle. # 99 is arbitrary foreach my $fd ( 0 .. 99 ) { if ( !defined( $FD[$fd] ) ) { $FD[$fd] = $FH; $AC = $fd; last; } } } else { # No filehandle, so it's an error dprintf("open failed: $!\n"); $AC = MAXINT; } } # Open system call sub sys_open { # Open seems to have 2 arguments: PC+1 is a pointer to the filename. # PC+2 seems to be 1 for write, 0 for read. # Some programs seem to have a third argument always set to 0. # AC is the opened fd on success, or -1 on error # Get the start address of the string my $start = $Mem[ $PC + 1 ]; # Convert this to a sensible ASCII filename my $filename = mem2arg($start); # Choose to open read-only or write-only my $readorwrite = ( $Mem[ $PC + 2 ] ) ? ">" : "<"; dprintf( "open: base %06o, %s file %s\n", $start, $readorwrite, $filename ); # Bump up the PC $PC += 3; # Now open the file and return creatopen($filename, $readorwrite); } # Creat system call sub sys_creat { # Open seems to have 1 arguments: PC+1 is a pointer to the filename. # Some programs seem to have a second argument always set to 0. # AC is the opened fd on success, or -1 on error # Get the start address of the string my $start = $Mem[ $PC + 1 ]; # Convert this to a sensible ASCII filename my $filename = mem2arg($start); # Choose to open write-only my $readorwrite = ">"; dprintf( "creat: base %06o, file %s\n", $start, $filename ); # Bump up the PC $PC += 2; # Now open the file and return creatopen($filename, $readorwrite); } # Read system call sub sys_read { # Read seems to have arguments: AC is the file descriptor, PC+1 is # the pointer to the buffer and PC+2 is the number of words to read. # Return the number of words read in AC on success, or -1 on error. # Get the file descriptor, start address and end address my $fd = $AC; my $start = $Mem[ $PC + 1 ]; my $count = $Mem[ $PC + 2 ]; my $end = ( $start + $count - 1 ) & MAXADDR; die("sys_read: bad start/end addresses $start $end\n") if ( $end < $start ); dprintf( "read: %d words into %06o from fd %d\n", $count, $start, $fd ); # Bump up the PC $PC += 3; # That filehandle is not open, set an error -1 in octal if ( !defined( $FD[$fd] ) ) { dprint("read: fd $fd is not open\n"); $AC = MAXINT; return; } # Read each word in my $FH = $FD[$fd]; $count = 0; foreach my $addr ( $start .. $end ) { my $c1 = getc($FH); last if ( !defined($c1) ); # No character, leave the loop my $c2 = getc($FH) || ""; # No character, make it a NUL $Mem[$addr] = ( ord($c1) << 9 ) | ord($c2); # Pack both into one word $count++; } # No error $AC = $count; return; } # Write system call sub sys_write { # Write seems to have arguments: AC is the file descriptor, PC+1 is # the pointer to the buffer and PC+2 is the number of words to write # Get the file descriptor, start address and end address my $fd = $AC; my $start = $Mem[ $PC + 1 ]; my $count = $Mem[ $PC + 2 ]; my $end = ( $start + $count - 1 ) & MAXADDR; die("sys_write: bad start/end addresses $start $end\n") if ( $end < $start ); dprintf( "write: %d words from %06o to fd %d\n", $count, $start, $fd ); # Bump up the PC $PC += 3; # That filehandle is not open, set an error -1 in octal if ( !defined( $FD[$fd] ) ) { dprint("write: fd $fd is not open\n"); $AC = MAXINT; return; } # Write each word out my $FH = $FD[$fd]; foreach my $addr ( $start .. $end ) { print( $FH word2ascii( $Mem[$addr] ) ); } # No error $AC = 0; return; } # Convert an 18-bit word into two ASCII characters and return them. # Don't return NUL characters sub word2ascii { my $word = shift; my $c1 = ( $word >> 9 ) & 0177; my $c2 = $word & 0177; my $result = ""; $result .= chr($c1) if ($c1); $result .= chr($c2) if ($c2); return ($result); } # Given the address of a four word argument string in # memory, return a copy of the string in ASCII format. # Lose any trailing spaces as well. sub mem2arg { my $addr = shift; my $result = ""; foreach ( 1 .. 4 ) { # Stop if the address leave the 8K word address space last if ( $addr > MAXADDR ); my $word = $Mem[ $addr++ ]; my $c1 = ( $word >> 9 ) & 0177; my $c2 = $word & 0177; $result .= chr($c1) . chr($c2); } $result =~ s{ *$}{}; return ($result); } # Given the address of a word in memory, interpret that location # and those following as a NUL-terminated ASCII string and return # a copy of this string # XXX: not sure if I still need this. sub mem2string { my $addr = shift; my $result = ""; while (1) { # Stop when the address leave the 8K word address space return ($result) if ( $addr > MAXADDR ); # Stop when the value there is zero my $word = $Mem[$addr]; return ($result) if ( $word == 0 ); # Get the top ASCII character, return if NUL my $c1 = ( $word >> 9 ) & 0177; return ($result) if ( $c1 == 0 ); $result .= chr($c1); # Get the bottom ASCII character, return if NUL my $c2 = $word & 0177; return ($result) if ( $c2 == 0 ); $result .= chr($c2); # Move up to the next address $addr++; } } # Print out debug messages sub dprintf { printf( STDERR @_ ) if ( ($debug) || ($singlestep) ); } # Get one or more commands from the user and execute them sub get_user_command { my %Cmdlist = ( 'b' => \&cmd_setbreak, 'break' => \&cmd_setbreak, 'd' => \&cmd_dump, 'dump' => \&cmd_dump, 'db' => \&cmd_delbreak, 'del' => \&cmd_delbreak, 'delete' => \&cmd_delbreak, '?' => \&cmd_help, 'h' => \&cmd_help, 'help' => \&cmd_help, 's' => \&cmd_step, 'l' => \&cmd_listbreak, 'list' => \&cmd_listbreak, 'step' => \&cmd_step, 'q' => \&cmd_exit, 'x' => \&cmd_exit, 'quit' => \&cmd_exit, 'exit' => \&cmd_exit, 'c' => \&cmd_continue, 'continue' => \&cmd_continue, 'r' => \&cmd_showregs, 'regs' => \&cmd_showregs, ); # Loop until we get a leave result while (1) { # Get a command from the user # and split into command, start and end addresses. # Convert addresses from octal print("a7out> "); chomp( my $line = ); my ( $cmd, $addr, $endaddr ) = split( /\s+/, $line ); $addr = oct($addr) if ( defined($addr) ); $endaddr = oct($endaddr) if ( defined($endaddr) ); # Run the command my $leave; if ( defined($cmd) && defined( $Cmdlist{$cmd} ) ) { $leave = $Cmdlist{$cmd}->( $addr, $endaddr ); } else { printf( "%s: unknown command\n", $cmd || "" ); cmd_help(); } return if ($leave); } } # Exit the program sub cmd_exit { exit(0); } # Continue by disabling single-step # and break out of the command loop sub cmd_continue { $singlestep = 0; return (1); } # Step by staying in single-step # but break out of the command loop sub cmd_step { return (1); } # Set a breakpoint sub cmd_setbreak { my $addr = shift; $Breakpoint{$addr} = 1; return (0); } # Delete a breakpoint sub cmd_delbreak { my $addr = shift; delete( $Breakpoint{$addr} ); printf( "Delete breakpoint %06o\n", $addr ); return (0); } sub cmd_help { print(" [b]reak set a breakpoint\n"); print(" [c]ontinue leave single-step and continue\n"); print(" [d]ump [] [] dump addresses in range\n"); print(" db delete a breakpoint\n"); print(" [del]ete delete a breakpoint\n"); print(" [l]ist list breakpoints\n"); print(" [r]egs print PC, LINK, AC and MQ regs\n"); print(" [s]tep single-step next instruction\n"); print(" ?, h, help print this help list\n"); print(" e[x]it, [q]uit exit the program\n"); return (0); } sub cmd_showregs { my $link = ($LINK) ? 1 : 0; printf( "PC: %06o, L.AC %d.%06o, MQ: %06o\n", $PC, $link, $AC, $MQ ); return (0); } sub cmd_dump { my ( $start, $end ) = @_; # No arguments, so dump everything but not empty locations if ( !defined($start) ) { dump_memory( 0, MAXADDR, 0 ); return (0); } # Dump a limited range $end = $start if ( !defined($end) ); dump_memory( $start, $end, 1 ); return (0); } sub cmd_listbreak { print("Breakpoints:\n"); foreach my $addr ( sort( keys(%Breakpoint) ) ) { printf( " %06o\n", $addr ); } return (0); }