#!/usr/bin/perl -w #!/usr/local/bin/perl -w # Copyright (c) 2005-2016 Don North # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # # o Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # # o Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # o Neither the name of the copyright holder nor the names of its # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED # TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. require 5.008; =head1 NAME obj2bin.pl - Convert a Macro-11 program image to PROM/load format =head1 SYNOPSIS obj2bin.pl S<[--help]> S<[--debug]> S<[--verbose]> S<[--boot]> S<[--console]> S<[--binary]> S<[--ascii]> S<[--rt11]> S<[--rsx11]> S<[--bytes=N]> S<[--nocrc]> S<[--logfile=LOGFILE]> S<--outfile=BINFILE> S =head1 DESCRIPTION Converts a Macro-11 object files to various output formats, including M9312 boot and console PROM, straight binary records, ASCII format for M9312 console load commands, and loadable absolute binary program images (.BIN) files. Multiple .psect/.asect ops are supported, as well as all local (non-global) relocation directory entries. Supports either RT-11 or RSX-11 format object files. =head1 OPTIONS The following options are available: =over =item B<--help> Output this manpage and exit the program. =item B<--debug> Enable debug mode; print input file records as parsed. =item B<--verbose> Verbose status; output status messages during processing. =item B<--boot> Generate a hex PROM file image suitable for programming into an M9312 boot prom (512x4 geometry, only low half used). =item B<--console> Generate a hex PROM file image suitable for programming into an M9312 console/diagnostic prom (1024x4 geometry). =item B<--ascii> Generate a a sequence of 'L addr' / 'D data' commands for downloading a program via a terminal emulator thru the M9312 user command interface. Suitable only for really small test programs. =item B<--binary> Generate binary format load records of the program image (paper tape format) for loading into SIMH or compatible simulators. These files can also be copied onto XXDP filesystems to generate runnable program images (used to write custom diaqnostics). Binary format is the default if no other option is specified. If more than one option is specified the last one takes effect. =item B<--rt11> Read input object files in RT-11 format. =item B<--rsx11> Read input object files in RSX-11 format. RSX-11 object file format is the default if no other option is specified. If more than one option is specified the last one takes effect. =item B<--bytes=N> For hex format output files, output N bytes per line (default 16). =item B<--nocrc> For hex format output files, don't automatically stuff the computed CRC-16 as the last word in the ROM. =item B<--logfile=FILENAME> Generate debug output into this file. =item B<--outfile=FILENAME> Output binary file in format selected by user option. =item B Input object file(s) in .obj format. =back =head1 ERRORS The following diagnostic error messages can be produced on STDERR. The meaning should be fairly self explanatory. C -- bad option or missing file(s) C -- bad filename or unreadable file C -- valid RT-11 record must start with 0x01 C -- second RT-11 byte must be 0x00 C -- third byte is low byte of record length C -- fourth byte is high byte of record length C -- bytes five thru end-1 are data bytes C -- last RT-11 byte is checksum C -- compare rcv'ed checksum vs exp'ed checksum =head1 EXAMPLES Some examples of common usage: obj2bin.pl --help obj2bin.pl --verbose --boot --out 23-751A9.hex 23-751A9.obj obj2bin.pl --verbose --binary --rt11 --out memtest.bin memtest.obj obj2bin.pl --verbose --binary --rsx11 --out prftst.bin prftst.obj mac/printf.obj =head1 AUTHOR Don North - donorth =head1 HISTORY Modification history: 2005-05-05 v1.0 donorth - Initial version. 2016-01-15 v1.1 donorth - Added RLD(IR) processing, moved sub's to end. 2016-01-18 v1.2 donorth - Added GSD processing, improved debug output. 2016-01-20 v1.3 donorth - Initial support for linking multiple PSECTs. 2016-01-22 v1.4 donorth - Added objfile/outfile/logfile switches vs stdio. 2016-01-28 v1.5 donorth - Added RLD processing, especially complex. 2017-04-01 v1.9 donorth - Renamed from obj2hex.pl to obj2bin.pl 2017-05-04 v1.95 donorth - Updated capability to read multiple input .obj files. 2020-03-06 v2.0 donorth - Updated help documentation and README.md file. 2020-03-10 v2.1 donorth - Broke down and added RSX-11 input format option. =cut # options use strict; # external standard modules use Getopt::Long; use Pod::Text; use FindBin; use FileHandle; # external local modules search path BEGIN { unshift(@INC, $FindBin::Bin); unshift(@INC, $ENV{PERL5LIB}) if defined($ENV{PERL5LIB}); # cygwin bugfix unshift(@INC, '.'); } # external local modules # generic defaults my $VERSION = 'v2.1'; # version of code my $HELP = 0; # set to 1 for man page output my $DEBUG = 0; # set to 1 for debug messages my $VERBOSE = 0; # set to 1 for verbose messages # specific defaults my $crctype = 'CRC-16'; # type of crc calc to do my $memsize; # number of instruction bytes allowed my $memfill; # memory fill pattern my %excaddr; # words to be skipped in rom crc calc my $rombase; # base address of rom image my $romsize; # number of rom addresses my $romfill; # rom fill pattern my $romtype = 'BINA'; # default rom type is BINARY my $objtype = 'RSX11'; # default object file format is RSX11 my $bytesper = -1; # bytes per block in output file my $nocrc = 0; # output CRC16 as last word unless set my $outfile = undef; # output filename my $logfile = undef; # log filename # process command line arguments my $NOERROR = GetOptions( "help" => \$HELP, "debug" => \$DEBUG, "verbose" => \$VERBOSE, "boot" => sub { $romtype = 'BOOT'; }, "console" => sub { $romtype = 'DIAG'; }, "binary" => sub { $romtype = 'BINA'; }, "ascii" => sub { $romtype = 'ASC9'; }, "rt11" => sub { $objtype = 'RT11'; }, "rsx11" => sub { $objtype = 'RSX11'; }, "bytes=i" => \$bytesper, "nocrc" => \$nocrc, "outfile=s" => \$outfile, "logfile=s" => \$logfile, ); # init $VERBOSE = 1 if $DEBUG; # debug implies verbose messages # output the documentation if ($HELP) { # output a man page if we can if (ref(Pod::Text->can('new')) eq 'CODE') { # try the new way if appears to exist my $parser = Pod::Text->new(sentence=>0, width=>78); printf STDOUT "\n"; $parser->parse_from_file($0); } else { # else must use the old way printf STDOUT "\n"; Pod::Text::pod2text(-78, $0); }; exit(1); } # check for correct arguments present, print usage if errors unless ($NOERROR && scalar(@ARGV) >= 1 && defined($outfile) && $romtype ne 'NONE' ) { printf STDERR "obj2bin.pl %s by Don North (perl %g)\n", $VERSION, $]; print STDERR "Usage: $0 [options...] arguments\n"; print STDERR <<"EOF"; --help output manpage and exit --debug enable debug mode --verbose verbose status reporting --boot M9312 boot prom .hex --console M9312 console/diagnostic prom .hex --binary binary program load image .bin [default] --ascii ascii m9312 program load image .txt --rt11 read .obj files in RT11 format --rsx11 read .obj files in RSX11 format [default] --bytes=N bytes per block on output hex format --nocrc inhibit output of CRC-16 in hex format --logfile=LOGFILE logging message file --outfile=OUTFILE output .hex/.txt/.bin file OBJFILE... macro11 object .obj file(s) EOF # exit if errors... die "Aborted due to command line errors.\n"; } # setup log file as a file, defaults to STDERR if not supplied my $LOG = defined($logfile) ? FileHandle->new("> ".$logfile) : FileHandle->new_from_fd(fileno(STDERR),"w"); #---------------------------------------------------------------------------------------------------- # subroutine prototypes sub trim ($); sub chksum (@); sub rad2asc (@); sub crc (%); sub sym2psect ($$); sub read_rec ($); sub get_global ($); sub parse_rec ($$$); #---------------------------------------------------------------------------------------------------- # fill in the parameters of the device if ($romtype eq 'BOOT') { # M9312 512x4 boot prom %excaddr = ( 024=>1, 025=>1 ); # bytes to be skipped in rom crc calc $memsize = 128; # number of instruction bytes allowed $memfill = 0x00; # memory fill pattern $romsize = 512; # number of rom addresses (must be a power of two) $romfill = 0x00; # rom fill pattern $rombase = 0173000; # base address of rom } elsif ($romtype eq 'DIAG') { # M9312 1024x4 diagnostic/console prom %excaddr = ( ); # bytes to be skipped in rom crc calc $memsize = 512; # number of instruction bytes allowed $memfill = 0x00; # memory fill pattern $romsize = 1024; # number of rom addresses (must be a power of two) $romfill = 0x00; # rom fill pattern $rombase = 0165000; # base address of rom } elsif ($romtype eq 'BINA' || $romtype eq 'ASC9') { # program load image ... 56KB address space maximum %excaddr = ( ); # bytes to be skipped in rom crc calc $memsize = 7*8192; # number of instruction bytes allowed $memfill = 0x00; # memory fill pattern $romsize = 8*8192; # number of rom addresses (must be a power of two) $romfill = 0x00; # image fill pattern $rombase = 0; # base address of binary image } else { # unknown ROM type code die "ROM type '$romtype' is not supported!\n"; } if ($VERBOSE) { printf $LOG "ROM type is '%s'\n", $romtype; printf $LOG "ROM space is %d. bytes\n", $memsize; printf $LOG "ROM length is %d. addresses\n", $romsize; printf $LOG "ROM base address is 0%06o\n", $rombase; } #---------------------------------------------------------------------------------------------------- # read/process the input object file records # real pdp11 memory data words in boot prom my @mem = ((0) x $memsize); # min/max address limits in object file my ($adrmin,$adrmax) = ('',''); # state variables in processing object records my $rommsk = ($romsize-1)>>1; # address bit mask my $adrmsk = 0xFFFF; # 16b addr mask my $datmsk = 0xFFFF; # 16b data mask my $memmsk = 0xFF; # 8b memory data mask # databases my %gblsym = (); my %psect = (); my @psect = (); my %program = (); my $psectname = sprintf("%02d:%s",1,'. ABS.'); my $psectaddr = 0; my $psectnumb = -1; my $textaddr = 0; # program defaults $program{START}{ADDRESS} = 1; $program{START}{VALUE} = 1; $program{START}{PSECT} = $psectname; # two passes, first is headers, second is data records foreach my $pass (1..2) { foreach my $numb (0..$#ARGV) { my $objfile = $ARGV[$numb]; my $OBJ = FileHandle->new("< ".$objfile); die "Error: can't open input object file '$objfile'\n" unless defined $OBJ; printf $LOG "\n\nPROCESS PASS %d FILE %d '%s'\n\n", $pass, $numb+1, $objfile if $DEBUG; while (my @rec = &read_rec($OBJ)) { &parse_rec($numb+1, $pass, \@rec); } $OBJ->close; } } #---------------------------------------------------------------------------------------------------- # compute CRC if required, copy memory image to output buffer my @buf = ($romfill) x $romsize; # physical PROM data bytes, filled background pattern # only compute CRC on M9312 ROMs if ($romtype eq 'BOOT' || $romtype eq 'DIAG') { # compute CRC-16 of the prom contents (except exception words) and store at last location my $crctab = &crc(-name=>$crctype, -new=>1); my $crc = &crc(-name=>$crctype, -init=>1); for (my $adr = 0; $adr < $memsize-2; $adr += 1) { next if exists($excaddr{$adr}); # skip these addresses $mem[$rombase+$adr] = $memfill unless defined($mem[$rombase+$adr]); $crc = &crc(-name=>$crctype, -table=>$crctab, -crc=>$crc, -byte=>$mem[$rombase+$adr]); } $crc = &crc(-name=>$crctype, -crc=>$crc, -last=>1); unless ($nocrc) { # output computed CRC-16 as last word in the ROM file $mem[$rombase+$memsize-2] = ($crc>>0)&0xFF; $mem[$rombase+$memsize-1] = ($crc>>8)&0xFF; } printf $LOG "ROM %s is %06o (0x%04X)\n", $crctype, ($crc) x 2 if $VERBOSE; # process data words to actual PROM byte data # put 4bit nibble in low 4b of each 8b data byte, zero the upper 4b # only copy the above instruction portion over for (my $idx = 0; $idx < $memsize<<1; $idx += 4) { my $dat = ($mem[$rombase+($idx>>1)+1]<<8) | ($mem[$rombase+($idx>>1)+0]<<0); $buf[$idx+0] = ($dat&0xE)|(($dat>>8)&0x1); # bits 3 2 1 8 $buf[$idx+1] = ($dat>>4)&0xF; # bits 7 6 5 4 $buf[$idx+2] = ((($dat>>8)&0xE)|($dat&0x1))^0xC; # bits ~11 ~10 9 0 $buf[$idx+3] = (($dat>>12)&0xF)^0x1; # bits 15 14 13 ~12 } } elsif ($romtype eq 'BINA' || $romtype eq 'ASC9') { # only copy the above instruction portion over for (my $adr = 0; $adr < $memsize; $adr += 1) { $mem[$rombase+$adr] = $memfill unless defined($mem[$rombase+$adr]); $buf[$adr] = $mem[$rombase+$adr]; } } if ($VERBOSE) { # print checksum of entire device my $chksum = 0; map($chksum += $_, @buf); printf $LOG "ROM checksum is %06o (0x%04X)\n", $chksum, $chksum; } #---------------------------------------------------------------------------------------------------- # output the linked/processed binary file image in the desired format my $OUT = FileHandle->new("> ".$outfile); die "Error: can't open output file '$outfile'\n" unless defined $OUT; if ($romtype eq 'BOOT' || $romtype eq 'DIAG') { # output the entire PROM buffer as an intel hex file $bytesper = 16 if $bytesper <= 0; for (my $idx = 0; $idx < $romsize; $idx += $bytesper) { my $cnt = $idx+$bytesper <= $romsize ? $bytesper : $romsize-$idx; # N bytes or whatever is left my @dat = @buf[$idx..($idx+$cnt-1)]; # get the data my $dat = join('', map(sprintf("%02X",$_),@dat)); # map to ascii text printf $OUT ":%02X%04X%02X%s%02X\n", $cnt, $idx, 0x00, $dat, &chksum($cnt, $idx>>0, $idx>>8, 0x00, @dat); } printf $OUT ":%02X%04X%02X%s%02X\n", 0x00, 0x0000, 0x01, '', &chksum(0x0, 0x0000>>0, 0x0000>>8, 0x01); } elsif ($romtype eq 'BINA') { # Loader format consists of blocks, optionally preceded, separated, and # followed by zeroes. Each block consists of: # # 001 --- # 000 | # lo(length) | # hi(length) | # lo(address) > 'length' bytes # hi(address) | # databyte1 | # : | # databyteN --- # checksum # # If the byte length is exactly six, the block is the last on the tape, and # there is no checksum. If the origin is not 000001, then the origin is # the PC at which to start the program. $bytesper = 128 if $bytesper <= 0; my $start = $program{START}{ADDRESS}; sub m ($) { $_[0] & 0xFF; } # output the entire PROM buffer as a binary loader file for (my $idx = $adrmin; $idx < $adrmax+1; $idx += $bytesper) { my $cnt = $idx+$bytesper <= $adrmax+1 ? $bytesper : $adrmax+1-$idx; # N bytes or whatever is left my @dat = @buf[$idx..($idx+$cnt-1)]; # get the data my $len = $cnt+6; my @rec = (0x01, 0x00, &m($len>>0), &m($len>>8), &m($idx>>0), &m($idx>>8), @dat); print $OUT pack("C*", @rec, &chksum(@rec)); } my @end = (0x01, 0x00, 0x06, 0x00, &m($start>>0), &m($start>>8)); print $OUT pack("C*", @end, &chksum(@end)); } elsif ($romtype eq 'ASC9') { # ascii interface to M9312 console emulator sub n ($) { $_[0] & 0xFF; } # start program load here printf $OUT "L %o\r\n", $adrmin; # output the PROM buffer as an ascii load file for (my $idx = $adrmin; $idx < $adrmax+1; $idx += 2) { printf $OUT "D %06o\r\n", (&n($buf[$idx+1])<<8) | &n($buf[$idx+0]); } # start program exec here printf $OUT "L %o\r\nS\r\n", $adrmin; } # all done $OUT->close; #---------------------------------------------------------------------------------------------------- # really done $LOG->close; exit; #---------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------- # trim leading/trailing spaces on a string sub trim ($) { my ($str) = @_; $str =~ s/\s+$//; $str =~ s/^\s+//; return $str; } #---------------------------------------------------------------------------------------------------- # compute checksum (twos complement of the sum of bytes) sub chksum (@) { my $sum = 0; map($sum += $_, @_); return (-$sum) & 0xFF; } #---------------------------------------------------------------------------------------------------- # RAD50 to ASCII decode sub rad2asc (@) { my @str = split(//, ' ABCDEFGHIJKLMNOPQRSTUVWXYZ$.%0123456789'); # RAD50 character subset my $ascii = ""; foreach my $rad50 (@_) { $ascii .= $str[int($rad50/1600)%40] . $str[int($rad50/40)%40] . $str[$rad50%40]; } return $ascii; } #---------------------------------------------------------------------------------------------------- # symbol to psect name converter sub sym2psect ($$) { return sprintf("%02d:%-6s", @_); } #---------------------------------------------------------------------------------------------------- # crc computation routine sub crc (%) { # pass all args by name my %args = @_; # all the crcs we know how to compute my %crcdat = ( 'CRC-16' => [ 0xA001, 2, 0x0000, 0x0000 ], 'CRC-32' => [ 0xEDB88320, 4, 0xFFFFFFFF, 0xFFFFFFFF ] ); # run next byte thru crc computation, return updated crc return $args{-table}[($args{-crc}^$args{-byte}) & 0xFF]^($args{-crc}>>8) if exists($args{-byte}); # return initial crc value return $crcdat{$args{-name}}->[2] if exists($args{-init}); # return final crc value xored with xorout return $args{-crc} ^ $crcdat{$args{-name}}->[3] if exists($args{-last}); # compute the crc lookup table, return a pointer to it if (exists($args{-new})) { my $crctab = []; my $poly = $crcdat{$args{-name}}->[0]; foreach my $byte (0..255) { my $data = $byte; foreach (1..8) { $data = ($data>>1) ^ ($data&1 ? $poly : 0); } $$crctab[$byte] = $data; } return $crctab; } } #---------------------------------------------------------------------------------------------------- # read a record from the object file sub read_rec ($) { my ($fh) = @_; my ($buf, $cnt, $len, $err) = (0,0,0,0); my @pre = (); my @dat = (); my @suf = (); if ($objtype eq 'RT11') { # RT-11 object file format consists of blocks, optionally preceded, separated, and # followed by zeroes. Each block consists of: # # 001 --- # 000 | # lo(length) | # hi(length) > 'length' bytes # databyte1 | # : | # databyteN --- # checksum # # skip over strings of 0x00; exit OK if hit EOF do { return () unless $cnt = read($fh, $buf, 1); } while (ord($buf) == 0); # valid record starts with (1) $err = 1 unless $cnt == 1 && ord($buf) == 1; push(@pre, ord($buf)); # second byte must be (0) $cnt = read($fh, $buf, 1); $err = 2 unless $cnt == 1 && ord($buf) == 0; push(@pre, ord($buf)); # third byte is low byte of record length $cnt = read($fh, $buf, 1); $err = 3 unless $cnt == 1; $len = ord($buf); push(@pre, ord($buf)); # fourth byte is high byte of record length $cnt = read($fh, $buf, 1); $err = 4 unless $cnt == 1; $len += ord($buf)<<8; push(@pre, ord($buf)); # bytes five thru end-1 are data bytes $cnt = read($fh, $buf, $len-4); $err = 5 unless $cnt == $len-4 && $len >= 4; @dat = unpack("C*", $buf); # last byte is checksum $cnt = read($fh, $buf, 1); $err = 6 unless $cnt == 1; my $rcv = ord($buf); push(@suf, ord($buf)); # compare rcv'ed checksum vs exp'ed checksum my $exp = &chksum(0x01, $len>>0, $len>>8, @dat); warn sprintf("Warning: Bad checksum exp=0x%02X rcv=0x%02X", $exp, $rcv) unless $exp == $rcv; } elsif ($objtype eq 'RSX11') { # RSX-11 object file format consists of blocks of data in the following format. # Each block consists of: # # lo(length) # hi(length) # databyte1 --- # : | # : > 'length' bytes # : | # databyteN --- # zeroFill present if length is ODD; else not present # # first byte is low byte of record length $cnt = read($fh, $buf, 1); # but exit OK if hit EOF return () if $cnt == 0; $err = 10 unless $cnt == 1; $len = ord($buf); push(@pre, ord($buf)); # second byte is high byte of record length $cnt = read($fh, $buf, 1); $err = 11 unless $cnt == 1; $len += ord($buf)<<8; push(@pre, ord($buf)); # bytes three thru end are data bytes $cnt = read($fh, $buf, $len); $err = 12 unless $cnt == $len && $len >= 0; @dat = unpack("C*", $buf); # optional pad byte if length is odd $cnt = ($len & 1) ? read($fh, $buf, 1) : 2; $err = 13 unless $cnt == 1 && ord($buf) == 0 || $cnt == 2; } # output the record if debugging if ($DEBUG >= 2) { my $fmt = "%03o"; my $n = 16; my $pre = sprintf("RECORD: [%s] ",join(" ",map(sprintf($fmt,$_),@pre))); printf $LOG "\n\n%s", $pre; my $k = length($pre); my @tmp = @dat; while (@tmp > $n) { printf $LOG "%s\n%*s", join(" ",map(sprintf($fmt,$_),splice(@tmp,0,$n))), $k, ''; } printf $LOG "%s", join(" ",map(sprintf($fmt,$_),@tmp)) if @tmp; printf $LOG " [%s]\n\n", join(" ",map(sprintf($fmt,$_),@suf)); } # check we have a well formatted record warn sprintf("Warning: invalid %s object file record format (%d)", $objtype, $err) if $err; # all is well, return the record return @dat; } #---------------------------------------------------------------------------------------------------- # get a global symbol target value sub get_global ($) { my ($sym) = @_; # return target value if exists return $gblsym{$sym}{DEF}{ADDRESS} if exists $gblsym{$sym}{DEF}{ADDRESS}; # issue a warning for multiple definition with a different address warn sprintf("Warning: global symbol undefined: symbol=%s, assuming value of 000000\n", $sym); # and return nil return 0; } #---------------------------------------------------------------------------------------------------- # parse an input object file record, update data structures sub parse_rec ($$$) { my ($file,$pass,$rec) = (@_); # type is first byte of record my $key = $rec->[0]; if ($key == 001 && $pass == 1) { # GSD # iterate over GSD subrecords for (my $i = 2; $i < scalar(@$rec); ) { # GSD records are fixed 8B length all in the same format my $sym = &rad2asc(($rec->[$i+1]<<8)|($rec->[$i+0]<<0),($rec->[$i+3]<<8)|($rec->[$i+2]<<0)); my $flg = $rec->[$i+4]; my $ent = $rec->[$i+5]; my $val = ($rec->[$i+7]<<8)|($rec->[$i+6]<<0); my @ent = ('MODULE','CSECT','INTSYM','XFRADR','GBLSYM','PSECT','IDENT','VSECT'); my $def = undef; if ($ent == 3) { # XFRADR $program{START}{PSECT} = &sym2psect($file,$sym); $program{START}{VALUE} = $val; if ($DEBUG) { printf $LOG "..GSD: type='%-6s'(%03o) name='%s' value=%06o\n", $ent[$ent], $ent, $program{START}{PSECT}, $program{START}{VALUE}; } } elsif ($ent == 4) { # GBLSYM flags my $adr = $val + $psect{$psectname}{START}; $def = $flg&(1<<3) ? "DEF" : "REF"; if ($def eq "DEF" && exists $gblsym{$sym}{$def} && $adr != $gblsym{$sym}{$def}{ADDRESS}) { # issue a warning for multiple definition with a different address warn sprintf("Warning: global symbol redefinition: symbol=%s (address/psect) old=%06o/%s new=%06o/%s -- IGNORING\n", &trim($sym), $gblsym{$sym}{$def}{ADDRESS}, &trim($gblsym{$sym}{$def}{PSECT}), $adr, &trim($psectname)); } else { # define first time only ... ignore any redefinition attempt $gblsym{$sym}{$def}{FLG}{$flg&(1<<0) ? "WEA" : "STR"}++; $gblsym{$sym}{$def}{FLG}{$flg&(1<<3) ? "DEF" : "REF"}++; $gblsym{$sym}{$def}{FLG}{$flg&(1<<5) ? "REL" : "ABS"}++; $gblsym{$sym}{$def}{PSECT} = $psectname; $gblsym{$sym}{$def}{VALUE} = $val; $gblsym{$sym}{$def}{ADDRESS} = $adr; } if ($DEBUG) { printf $LOG "..GSD: type='%-6s'(%03o) name='%s' value=%06o", $ent[$ent], $ent, $sym, $val; printf $LOG " psect='%s' value=%06o", $gblsym{$sym}{$def}{PSECT}, $gblsym{$sym}{$def}{VALUE}; printf $LOG " flags=%s\n", join(",", sort(keys(%{$gblsym{$sym}{$def}{FLG}}))); } } elsif ($ent == 5) { # PSECT flags my $nam = &sym2psect($file,$sym); $psect[++$psectnumb] = $nam; $psect{$nam}{FILE} = $file; $psect{$nam}{NUMBER} = $psectnumb; $psect{$nam}{FLG}{$flg&(1<<0) ? "GBL" : $flg&(1<<6) ? "GBL" : "LCL"}++; $psect{$nam}{FLG}{$flg&(1<<2) ? "OVR" : "CON"}++; $psect{$nam}{FLG}{$flg&(1<<4) ? "R/O" : "R/W"}++; $psect{$nam}{FLG}{$flg&(1<<5) ? "REL" : "ABS"}++; $psect{$nam}{FLG}{$flg&(1<<7) ? "D" : "I/D"}++; $psectname = $nam; if ($psect{$nam}{FLG}{ABS}) { # absolute if ($psect{$nam}{FLG}{CON}) { # concatenated warn sprintf("Warning: psect ABS,CON is not supported, psect='%s'\n", $psectname); } elsif ($psect{$nam}{FLG}{OVR}) { # overlaid $psect{$nam}{LENGTH} = $val; $psect{$nam}{START} = 0; } } elsif ($psect{$nam}{FLG}{REL}) { # relative if ($psect{$nam}{FLG}{CON}) { # concatenated $psect{$nam}{LENGTH} = $val; $psect{$nam}{START} = $psectaddr & 1 ? ++$psectaddr : $psectaddr; $psectaddr += $val; } elsif ($psect{$nam}{FLG}{OVR}) { # overlaid warn sprintf("Warning: psect REL,OVR is not supported, psect='%s'\n", $psectname); } } if ($DEBUG) { printf $LOG "..GSD: type='%-6s'(%03o) name='%s' value=%06o", $ent[$ent], $ent, $nam, $val; printf $LOG " length=%06o start=%06o", $psect{$nam}{LENGTH}, $psect{$nam}{START}; printf $LOG " flags=%s\n", join(",", sort(keys(%{$psect{$nam}{FLG}}))); } } $i += 8; } } elsif ($key == 002 && $pass == 1) { # ENDGSD # just say we saw it printf $LOG "..ENDGSD\n\n" if $DEBUG; $program{END}{ADDRESS} = 0; foreach my $nam (sort({$psect{$a}{START} == $psect{$b}{START} ? $psect{$a}{NUMBER} <=> $psect{$b}{NUMBER} : $psect{$a}{START} <=> $psect{$b}{START}} keys(%psect))) { my $start = $psect{$nam}{START}; my $length = $psect{$nam}{LENGTH}; my $end = $length ? $start + $length - 1 : $start; $program{END}{ADDRESS} = $end if $end > $program{END}{ADDRESS}; printf $LOG "....PSECT[%02d](%s) START=%06o END=%06o LENGTH=%06o\n", $psect{$nam}{NUMBER}, $nam, $start, $end, $length if $length && $DEBUG; } printf $LOG "\n" if $DEBUG; foreach my $nam (sort(keys(%gblsym))) { if (exists $gblsym{$nam}{DEF}) { printf $LOG "....GBLSYM(%s) PSECT='%s' VALUE=%06o : ADDRESS=%06o\n", $nam, $gblsym{$nam}{DEF}{PSECT}, $gblsym{$nam}{DEF}{VALUE}, $gblsym{$nam}{DEF}{ADDRESS} if $DEBUG; } } if ($program{START}{ADDRESS} == 1) { $program{START}{ADDRESS} = $program{START}{VALUE} + $psect{$program{START}{PSECT}}{START}; } printf $LOG "\n....PROG(ADDRESS) START=%06o END=%06o\n", $program{START}{ADDRESS}, $program{END}{ADDRESS} if $DEBUG; } elsif ($key == 003 && $pass == 2) { # TXT # process text record my $off = ($rec->[3]<<8)|($rec->[2]<<0); my $len = scalar(@$rec)-4; my $base = $psect{$psectname}{START}; my $adr = ($base + $off) & $adrmsk; foreach my $i (1..$len) { $mem[$adr+$i-1] = $rec->[4+$i-1]; } if ($DEBUG) { printf $LOG "..TXT OFFSET=%06o LENGTH=%o BASE=%06o PSECTNAME='%s'\n", $off, $len, $base, $psectname; for (my $i = 0; $i < $len; $i += 2) { printf $LOG " %06o: ", ($adr+$i)&~1 if $i%8 == 0; printf $LOG " %03o...", $mem[$adr+$i++] if ($adr+$i)&1; printf $LOG " %06o", ($mem[$adr+$i+1]<<8)|($mem[$adr+$i+0]<<0) if $i < $len-1; printf $LOG " ...%03o", $mem[$adr+$i] if $i == $len-1; printf $LOG "\n" if $i%8 >= 6 && $i < $len-2; } printf $LOG "\n"; } $adrmin = $adr if $adrmin eq '' || $adr < $adrmin; $adrmax = $adr+$len-1 if $adrmax eq '' || $adr+$len-1 > $adrmax; $textaddr = $adr; } elsif ($key == 004 && $pass == 2) { # RLD # iterate over RLD subrecords for (my $i = 2; $i < scalar(@$rec); ) { # first byte is entry type and flags my $ent = $rec->[$i+0] & 0x7F; # entry type my $flg = $rec->[$i+0] & 0x80; # modification flag (0=word, 1=byte) # process an entry if ($ent == 001) { # internal relocation ... OK my $dis = $rec->[$i+1]; my $con = ($rec->[$i+3]<<8)|($rec->[$i+2]<<0); # process my $adr = $adrmsk & ($textaddr + $dis - 4); my $val = $datmsk & ($psect{$psectname}{START} + $con); # store $mem[($adr+0)&$adrmsk] = $memmsk & ($val>>0); $mem[($adr+1)&$adrmsk] = $memmsk & ($val>>8); # print printf $LOG "..RLD(IR): adr=%06o val=%06o ; dis=%06o con=%06o\n", $adr, $val, $dis, $con if $DEBUG; $i += 4; } elsif ($ent == 003) { # internal displaced relocation ... OK my $dis = $rec->[$i+1]; my $con = ($rec->[$i+3]<<8)|($rec->[$i+2]<<0); # process my $adr = $adrmsk & ($textaddr + $dis - 4); my $val = $datmsk & ($con - ($adr+2)); # store $mem[($adr+0)&$adrmsk] = $memmsk & ($val>>0); $mem[($adr+1)&$adrmsk] = $memmsk & ($val>>8); # print printf $LOG "..RLD(IDR): adr=%06o val=%06o ; dis=%06o con=%06o\n", $adr, $val, $dis, $con if $DEBUG; $i += 4; } elsif ($ent == 012) { # psect relocation ... OK my $dis = $rec->[$i+1]; my $nam = &sym2psect($file,&rad2asc(($rec->[$i+3]<<8)|($rec->[$i+2]<<0),($rec->[$i+5]<<8)|($rec->[$i+4]<<0))); # process my $adr = $adrmsk & ($textaddr + $dis - 4); my $val = $datmsk & ($psect{$nam}{START}); # store $mem[($adr+0)&$adrmsk] = $memmsk & ($val>>0); $mem[($adr+1)&$adrmsk] = $memmsk & ($val>>8); # print printf $LOG "..RLD(PR): adr=%06o val=%06o ; dis=%06o nam='%s'\n", $adr, $val, $dis, $nam if $DEBUG; $i += 6; } elsif ($ent == 014) { # psect displaced relocation ... OK my $dis = $rec->[$i+1]; my $nam = &sym2psect($file,&rad2asc(($rec->[$i+3]<<8)|($rec->[$i+2]<<0),($rec->[$i+5]<<8)|($rec->[$i+4]<<0))); # process my $adr = $adrmsk & ($textaddr + $dis - 4); my $val = $datmsk & ($psect{$nam}{START} - ($adr+2)); # store $mem[($adr+0)&$adrmsk] = $memmsk & ($val>>0); $mem[($adr+1)&$adrmsk] = $memmsk & ($val>>8); # print printf $LOG "..RLD(PDR): adr=%06o val=%06o ; dis=%06o nam='%s'\n", $adr, $val, $dis, $nam if $DEBUG; $i += 6; } elsif ($ent == 015) { # psect additive relocation ... OK my $dis = $rec->[$i+1]; my $nam = &sym2psect($file,&rad2asc(($rec->[$i+3]<<8)|($rec->[$i+2]<<0),($rec->[$i+5]<<8)|($rec->[$i+4]<<0))); my $con = ($rec->[$i+7]<<8)|($rec->[$i+6]<<0); # process my $adr = $adrmsk & ($textaddr + $dis - 4); my $val = $datmsk & ($psect{$nam}{START} + $con); # store $mem[($adr+0)&$adrmsk] = $memmsk & ($val>>0); $mem[($adr+1)&$adrmsk] = $memmsk & ($val>>8); # print printf $LOG "..RLD(PAR): adr=%06o val=%06o ; dis=%06o con=%06o nam='%s'\n", $adr, $val, $dis, $con, $nam if $DEBUG; $i += 8; } elsif ($ent == 016) { # psect additive displaced relocation ... OK my $dis = $rec->[$i+1]; my $nam = &sym2psect($file,&rad2asc(($rec->[$i+3]<<8)|($rec->[$i+2]<<0),($rec->[$i+5]<<8)|($rec->[$i+4]<<0))); my $con = ($rec->[$i+7]<<8)|($rec->[$i+6]<<0); # process my $adr = $adrmsk & ($textaddr + $dis - 4); my $val = $datmsk & ($psect{$nam}{START} + $con - ($adr+2)); # store $mem[($adr+0)&$adrmsk] = $memmsk & ($val>>0); $mem[($adr+1)&$adrmsk] = $memmsk & ($val>>8); # print printf $LOG "..RLD(PADR): adr=%06o val=%06o ; dis=%06o con=%06o nam='%s'\n", $adr, $val, $dis, $con, $nam if $DEBUG; $i += 8; } elsif ($ent == 002) { # global relocation ... OK my $dis = $rec->[$i+1]; my $sym = &rad2asc(($rec->[$i+3]<<8)|($rec->[$i+2]<<0),($rec->[$i+5]<<8)|($rec->[$i+4]<<0)); # process my $adr = $adrmsk & ($textaddr + $dis - 4); my $val = $datmsk & (&get_global($sym)); # store $mem[($adr+0)&$adrmsk] = $memmsk & ($val>>0); $mem[($adr+1)&$adrmsk] = $memmsk & ($val>>8); # print printf $LOG "..RLD(GR): adr=%06o val=%06o ; dis=%06o sym='%s'\n", $adr, $val, $dis, $sym if $DEBUG; $i += 6; } elsif ($ent == 004) { # global displaced relocation ... OK my $dis = $rec->[$i+1]; my $sym = &rad2asc(($rec->[$i+3]<<8)|($rec->[$i+2]<<0),($rec->[$i+5]<<8)|($rec->[$i+4]<<0)); # process my $adr = $adrmsk & ($textaddr + $dis - 4); my $val = $datmsk & (&get_global($sym) - ($adr+2)); # store $mem[($adr+0)&$adrmsk] = $memmsk & ($val>>0); $mem[($adr+1)&$adrmsk] = $memmsk & ($val>>8); # print printf $LOG "..RLD(GDR): adr=%06o val=%06o ; dis=%06o sym='%s'\n", $adr, $val, $dis, $sym if $DEBUG; $i += 6; } elsif ($ent == 005) { # global additive relocation ... OK my $dis = $rec->[$i+1]; my $sym = &rad2asc(($rec->[$i+3]<<8)|($rec->[$i+2]<<0),($rec->[$i+5]<<8)|($rec->[$i+4]<<0)); my $con = ($rec->[$i+7]<<8)|($rec->[$i+6]<<0); # process my $adr = $adrmsk & ($textaddr + $dis - 4); my $val = $datmsk & (&get_global($sym) + $con); # store $mem[($adr+0)&$adrmsk] = $memmsk & ($val>>0); $mem[($adr+1)&$adrmsk] = $memmsk & ($val>>8); # print printf $LOG "..RLD(GAR): adr=%06o val=%06o ; dis=%06o con=%06o sym='%s'\n", $adr, $val, $dis, $con, $sym if $DEBUG; $i += 8; } elsif ($ent == 006) { # global additive displaced relocation ... OK my $dis = $rec->[$i+1]; my $sym = &rad2asc(($rec->[$i+3]<<8)|($rec->[$i+2]<<0),($rec->[$i+5]<<8)|($rec->[$i+4]<<0)); my $con = ($rec->[$i+7]<<8)|($rec->[$i+6]<<0); # process my $adr = $adrmsk & ($textaddr + $dis - 4); my $val = $datmsk & (&get_global($sym) + $con - ($adr+2)); # store $mem[($adr+0)&$adrmsk] = $memmsk & ($val>>0); $mem[($adr+1)&$adrmsk] = $memmsk & ($val>>8); # print printf $LOG "..RLD(GADR): adr=%06o val=%06o ; dis=%06o con=%06o sym='%s'\n", $adr, $val, $dis, $con, $sym if $DEBUG; $i += 8; } elsif ($ent == 007) { # location counter definition ... OK my $dis = $rec->[$i+1]; my $nam = &sym2psect($file,&rad2asc(($rec->[$i+3]<<8)|($rec->[$i+2]<<0),($rec->[$i+5]<<8)|($rec->[$i+4]<<0))); my $con = ($rec->[$i+7]<<8)|($rec->[$i+6]<<0); # process $psectname = $nam; $textaddr = $datmsk & ($con); # print printf $LOG "..RLD(LCD): adr=%06o ; dis=%06o con=%06o nam='%s'\n", $textaddr, $dis, $con, $nam if $DEBUG; $i += 8; } elsif ($ent == 010) { # location counter modification ... OK my $dis = $rec->[$i+1]; my $con = ($rec->[$i+3]<<8)|($rec->[$i+2]<<0); # process $textaddr = $datmsk & ($con); # print printf $LOG "..RLD(LCM): adr=%06o ; dis=%06o con=%06o\n", $textaddr, $dis, $con if $DEBUG; $i += 4; } elsif ($ent == 011) { # program limits ... OK, mostly my $dis = $rec->[$i+1]; # process my $adr = $adrmsk & ($textaddr + $dis - 4); my $val = $datmsk & ( 01000 ); # make this up, no easy way to compute it # store $mem[($adr+0)&$adrmsk] = $memmsk & ($val>>0); $mem[($adr+1)&$adrmsk] = $memmsk & ($val>>8); # print printf $LOG "..RLD(LIM1): adr=%06o val=%06o ; dis=%06o\n", $adr, $val, $dis if $DEBUG; # process $dis += 2; $adr += 2; $val = $datmsk & ($program{END}{ADDRESS}); # store $mem[($adr+0)&$adrmsk] = $memmsk & ($val>>0); $mem[($adr+1)&$adrmsk] = $memmsk & ($val>>8); # print printf $LOG "..RLD(LIM2): adr=%06o val=%06o ; dis=%06o\n", $adr, $val, $dis if $DEBUG; $i += 2; } elsif ($ent == 017) { # complex relocation ... OK my $dis = $rec->[$i+1]; my $nam = &sym2psect($file,'. ABS.'); my $con = 0; # process my $adr = 0; my $loc = 0; my $val = 0; my $opc = ""; my @stk = (); my $dun = 0; for ($i += 2; !$dun; $i += 1) { if ($rec->[$i] == 000) { # NOP do nothing $opc = "NOP"; } elsif ($rec->[$i] == 001) { # ADD : pop + pop => push my @arg = splice(@stk,-2,2); push(@stk, $arg[0] + $arg[1]); $opc = "ADD"; } elsif ($rec->[$i] == 002) { # SUB : pop - pop => push my @arg = splice(@stk,-2,2); push(@stk, $arg[0] - $arg[1]); $opc = "SUB"; } elsif ($rec->[$i] == 003) { # MUL : pop * pop => push my @arg = splice(@stk,-2,2); push(@stk, $arg[0] * $arg[1]); $opc = "MUL"; } elsif ($rec->[$i] == 004) { # DIV : pop / pop => push my @arg = splice(@stk,-2,2); push(@stk, $arg[1] == 0 ? 0 : int($arg[0] / $arg[1])); $opc = "DIV"; } elsif ($rec->[$i] == 005) { # AND : pop & pop => push my @arg = splice(@stk,-2,2); push(@stk, $arg[0] & $arg[1]); $opc = "AND"; } elsif ($rec->[$i] == 006) { # IOR : pop | pop => push my @arg = splice(@stk,-2,2); push(@stk, $arg[0] | $arg[1]); $opc = "IOR"; } elsif ($rec->[$i] == 007) { # XOR : pop ^ pop => push my @arg = splice(@stk,-2,2); push(@stk, $arg[0] ^ $arg[1]); $opc = "XOR"; } elsif ($rec->[$i] == 010) { # NEG : pop - => push my @arg = splice(@stk,-1,1); push(@stk, -$arg[0]); $opc = "NEG"; } elsif ($rec->[$i] == 011) { # COM : pop ~ => push my @arg = splice(@stk,-1,1); push(@stk, ~$arg[0]); $opc = "COM"; } elsif ($rec->[$i] == 012) { # STO : pop => store @ address my @arg = splice(@stk,-1,1); $adr = $adrmsk & ($textaddr + $dis - 4); $val = $datmsk & ($arg[0]); $opc = "STO"; $dun = 1; } elsif ($rec->[$i] == 013) { # STO : pop => store @ address + disp my @arg = splice(@stk,-1,1); $adr = $adrmsk & ($textaddr + $dis - 4); $val = $datmsk & ($arg[0] - ($adr+2)); $opc = "STO+DIS"; $dun = 1; } elsif ($rec->[$i] == 016) { # FET : global => push $nam = &rad2asc(($rec->[$i+2]<<8)|($rec->[$i+1]<<0),($rec->[$i+4]<<8)|($rec->[$i+3]<<0)); $con = &get_global($nam); push(@stk, $con); $opc = sprintf("GLB[%s]=(%o)", &trim($nam), $con); $i += 4; } elsif ($rec->[$i] == 017) { # FET : local => push $nam = $psect[$rec->[$i+1]]; $con = ($rec->[$i+3]<<8) | ($rec->[$i+2]<<0); $loc = $psect{$nam}{START} + $con; push(@stk, $loc); $opc = sprintf("FET[%s+%o]=(%o)", &trim($nam), $con, $loc); $i += 3; } elsif ($rec->[$i] == 020) { # CONstant : value => push $con = ($rec->[$i+2]<<8) | ($rec->[$i+1]<<0); push(@stk, $con); $opc = "CON"; $i += 2; } $stk[-1] = $datmsk & $stk[-1] if @stk; printf $LOG "....OPC=%-20s STK=(%s)\n", $opc, join(",",map(sprintf("%o",$_),@stk)) if $DEBUG; } # print printf $LOG "..RLD(CPXR): adr=%06o val=%06o ; dis=%06o\n", $adr, $val, $dis if $DEBUG; # store $mem[($adr+0)&$adrmsk] = $memmsk & ($val>>0); $mem[($adr+1)&$adrmsk] = $memmsk & ($val>>8); } else { warn sprintf("Warning: Unknown RLD entry 0%o (%d)", $ent, $ent); } } } elsif ($key == 005) { # ISD # ignore printf $LOG "..ISD: ignored\n" if $DEBUG; } elsif ($key == 006) { # ENDMOD # just say we saw it printf $LOG "..ENDMOD\n\n\n" if $DEBUG; } elsif ($key == 007) { # LIBHDR # ignore printf $LOG "..LIBHDR: ignored\n" if $DEBUG; } elsif ($key == 010) { # LIBEND # ignore printf $LOG "..LIBEND: ignored\n" if $DEBUG; } elsif ($key == 000 || $key >= 011) { # unknown # invalid record type in the object file warn sprintf("Warning: unknown record type 0%o (%d)", $key, $key); } return; } #---------------------------------------------------------------------------------------------------- # the end