diff --git a/README.md b/README.md index ecd5e43..c2614b2 100644 --- a/README.md +++ b/README.md @@ -15,8 +15,8 @@ Usage: ./obj2hex.pl [options...] arguments --bytes=N bytes per block on output --nocrc inhibit output of CRC-16 in hex format --logfile=LOGFILE logging message file - --objfile=OBJFILE macro11 object .obj file --outfile=OUTFILE output .hex/.txt/.bin file + OBJFILE... macro11 object .obj file(s) Aborted due to command line errors. ``` @@ -28,8 +28,8 @@ NAME SYNOPSIS obj2hex.pl [--help] [--debug] [--verbose] [--boot] [--console] [--binary] - [--ascii] [--bytes=N] [--nocrc] [--logfile=LOGFILE] --objfile=OBJFILE - --outfile=BINFILE + [--ascii] [--bytes=N] [--nocrc] [--logfile=LOGFILE] --outfile=BINFILE + OBJFILE... DESCRIPTION Converts a Macro-11 object file to various output formats, including M9312 @@ -86,12 +86,12 @@ OPTIONS --logfile=FILENAME Generate debug output into this file. - --objfile=FILENAME - Input objject file in .obj format. - --outfile=FILENAME Output binary file in format selected by user option. + OBJFILE... + Input object file(s) in .obj format. + ERRORS The following diagnostic error messages can be produced on STDERR. The meaning should be fairly self explanatory. @@ -124,9 +124,9 @@ EXAMPLES obj2hex.pl --help - obj2hex.pl --verbose --boot --in 23-751A9.obj --out 23-751A9.hex + obj2hex.pl --verbose --boot --out 23-751A9.hex 23-751A9.obj - obj2hex.pl --verbose --binary --in memtest.obj --out memtest.bin + obj2hex.pl --verbose --binary --out memtest.bin memtest.obj AUTHOR Don North - donorth @@ -140,4 +140,6 @@ HISTORY 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 v2.0 donorth - Started to add capability to process multiple + input object files ... still a work in progress. ``` diff --git a/obj2hex.pl b/obj2hex.pl index 7ced521..b27a3fc 100644 --- a/obj2hex.pl +++ b/obj2hex.pl @@ -1,1059 +1,1075 @@ -#!/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 - -obj2hex.pl - Convert a Macro-11 program image to PROM/load format - -=head1 SYNOPSIS - -obj2hex.pl -S<[--help]> -S<[--debug]> -S<[--verbose]> -S<[--boot]> -S<[--console]> -S<[--binary]> -S<[--ascii]> -S<[--bytes=N]> -S<[--nocrc]> -S<[--logfile=LOGFILE]> -S<--objfile=OBJFILE> -S<--outfile=BINFILE> - -=head1 DESCRIPTION - -Converts a Macro-11 object file 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. - -Currently the program is limited to a single object input file that -can be output in the selected format. Multiple .psect/.asect ops are -supported, as well as all local (non-global) relocation directory -entries. Multiple object files are (not yet) supported. - -=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<--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). - -=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. - -Exactly ONE of B<--boot>, B<--console>, B<--binary>, or B<--ascii> -must be specified. - -=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<--objfile=FILENAME> - -Input objject file in .obj format. - -=item B<--outfile=FILENAME> - -Output binary file in format selected by user option. - -=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 record must start with 0x01 - -C -- second 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 byte is checksum - -C -- compare rcv'ed checksum vs exp'ed checksum - -=head1 EXAMPLES - -Some examples of common usage: - - obj2hex.pl --help - - obj2hex.pl --verbose --boot --in 23-751A9.obj --out 23-751A9.hex - - obj2hex.pl --verbose --binary --in memtest.obj --out memtest.bin - -=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. - -=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 = 'v1.5'; # 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 = 'NONE'; # default rom type -my $bytesper = -1; # bytes per block in output file -my $nocrc = 0; # output CRC16 as last word unless set -my $objfile = undef; # input filename -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'; }, - "bytes=i" => \$bytesper, - "nocrc" => \$nocrc, - "objfile=s" => \$objfile, - "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) == 0 - && defined($objfile) - && defined($outfile) - && $romtype ne 'NONE' - ) { - printf STDERR "obj2hex.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 - --console M9312 console/diagnostic prom - --binary binary program load image - --ascii ascii m9312 program load image - --bytes=N bytes per block on output - --nocrc inhibit output of CRC-16 in hex format - --logfile=LOGFILE logging message file - --objfile=OBJFILE macro11 object .obj file - --outfile=OUTFILE output .hex/.txt/.bin file -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 read_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 - -# open the input .obj file, die if error -my $OBJ = FileHandle->new("< ".$objfile); -die "Error: can't open input object file '$objfile'\n" unless defined $OBJ; - -# databases -my %gblsym = (); -my %psect = (); -my @psect = (); -my %program = (); -my $psectname = '. ABS.'; -my $psectaddr = 0; -my $psectnumb = -1; -my $textaddr = 0; - -# program defaults -$program{START}{VALUE} = 1; -$program{START}{PSECT} = '. ABS.'; - -# now parse all the records -while (my @rec = &read_rec($OBJ)) { - - # type is first byte of record - my $key = $rec[0]; - - if ($key == 001) { # GSD - - # iterate over GSD subrecords - for (my $i = 2; $i < @rec; ) { - # GSD records are fixed 8B length all in the same format - my $nam = &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'); - if ($ent == 3) { - # XFRADR - $program{START}{PSECT} = $nam; - $program{START}{VALUE} = $val; - } elsif ($ent == 4) { - # GBLSYM flags - $gblsym{$nam}{FLG}{$flg&(1<<0) ? "WEA" : "STR"}++; - $gblsym{$nam}{FLG}{$flg&(1<<3) ? "DEF" : "REF"}++; - $gblsym{$nam}{FLG}{$flg&(1<<5) ? "REL" : "ABS"}++; - $gblsym{$nam}{PSECT} = $psectname; - $gblsym{$nam}{VALUE} = $val; - } elsif ($ent == 5) { - # PSECT flags - $psect[++$psectnumb] = $nam; - $psect{$nam}{NUMBER} = $psectnumb; - $psect{$nam}{FLG}{$flg&(1<<0) ? "GBL" : $flg&(1<<6) ? "GBL" : "LCL"}++; - $psect{$nam}{FLG}{$flg&(1<<2) ? "OVR" : "CAT"}++; - $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"}++; - if ($psect{$nam}{FLG}{CAT}) { - $psect{$nam}{LENGTH} = $val; - $psect{$nam}{START} = $psectaddr; - $psectname = $nam; - $psectaddr += $val; - } elsif ($psect{$nam}{FLG}{ABS}) { - $psect{$nam}{LENGTH} = $val; - $psect{$nam}{START} = 0; - $psectname = $nam; - } - } - if ($DEBUG) { - printf $LOG "..GSD: type='%-6s'(%03o) name='%s' value=%06o", $ent[$ent], $ent, $nam, $val; - printf $LOG " flags=%s", join(",", sort(keys(%{$gblsym{$nam}{FLG}}))) if $ent == 4; - printf $LOG " flags=%s", join(",", sort(keys(%{$psect{$nam}{FLG}}))) if $ent == 5; - printf $LOG "\n"; - } - $i += 8; - } - - } elsif ($key == 002) { # ENDGSD - - # just say we saw it - printf $LOG "..ENDGSD\n" if $DEBUG; - - $program{END}{ADDRESS} = 0; - foreach my $nam (sort(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[%d](%s) START=%06o END=%06o LENGTH=%06o\n", - $psect{$nam}{NUMBER}, $nam, $start, $end, $length if $length && $DEBUG; - } - $program{START}{ADDRESS} = $program{START}{VALUE} + $psect{$program{START}{PSECT}}{START}; - printf $LOG "..PROG(ADDRESS) START=%06o END=%06o\n", - $program{START}{ADDRESS}, $program{END}{ADDRESS} if $DEBUG; - - } elsif ($key == 003) { # TXT - - # process text record - my $off = ($rec[3]<<8)|($rec[2]<<0); - my $len = @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) { # RLD - - # iterate over RLD subrecords - for (my $i = 2; $i < @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); - $mem[($adr+0)&$adrmsk] = $memmsk & ($val>>0); - $mem[($adr+1)&$adrmsk] = $memmsk & ($val>>8); - printf $LOG "..RLD(IR): adr=%06o val=%06o ; dis=%06o con=%06o\n", - $adr, $val, $dis, $con if $DEBUG; - $i += 4; - } elsif ($ent == 002) { - # global relocation ... TBD <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - my $dis = $rec[$i+1]; - my $nam = &rad2asc(($rec[$i+3]<<8)|($rec[$i+2]<<0), ($rec[$i+5]<<8)|($rec[$i+4]<<0)); - # process - printf $LOG "..RLD(GR): dis=%06o nam='%s'\n", - $dis, $nam if $DEBUG; - $i += 6; - } elsif ($ent == 003) { - # internal displaced ... 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)); - $mem[($adr+0)&$adrmsk] = $memmsk & ($val>>0); - $mem[($adr+1)&$adrmsk] = $memmsk & ($val>>8); - printf $LOG "..RLD(ID): adr=%06o val=%06o ; dis=%06o con=%06o\n", - $adr, $val, $dis, $con if $DEBUG; - $i += 4; - } elsif ($ent == 004) { - # global displaced relocation ... TBD <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - my $dis = $rec[$i+1]; - my $nam = &rad2asc(($rec[$i+3]<<8)|($rec[$i+2]<<0), ($rec[$i+5]<<8)|($rec[$i+4]<<0)); - # process - printf $LOG "..RLD(GDR): dis=%06o nam='%s'\n", - $dis, $nam if $DEBUG; - $i += 6; - } elsif ($ent == 005) { - # global additive relocation ... TBD <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - my $dis = $rec[$i+1]; - my $nam = &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 - printf $LOG "..RLD(GAR): dis=%06o con=%06o nam='%s'\n", - $dis, $con, $nam if $DEBUG; - $i += 8; - } elsif ($ent == 006) { - # global additive displaced relocation ... TBD <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - my $dis = $rec[$i+1]; - my $nam = &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 - printf $LOG "..RLD(GADR): dis=%06o con=%06o nam='%s'\n", - $dis, $con, $nam if $DEBUG; - $i += 8; - } elsif ($ent == 007) { - # location counter definition ... OK - my $dis = $rec[$i+1]; - my $nam = &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); - 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); - 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 - $mem[($adr+0)&$adrmsk] = $memmsk & ($val>>0); - $mem[($adr+1)&$adrmsk] = $memmsk & ($val>>8); - printf $LOG "..RLD(LIM1): adr=%06o val=%06o ; dis=%06o\n", - $adr, $val, $dis if $DEBUG; - $dis += 2; - $adr += 2; - $val = $datmsk & ($program{END}{ADDRESS}); - $mem[($adr+0)&$adrmsk] = $memmsk & ($val>>0); - $mem[($adr+1)&$adrmsk] = $memmsk & ($val>>8); - printf $LOG "..RLD(LIM2): adr=%06o val=%06o ; dis=%06o\n", - $adr, $val, $dis if $DEBUG; - $i += 2; - } elsif ($ent == 012) { - # psect relocation ... OK - my $dis = $rec[$i+1]; - my $nam = &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}); - $mem[($adr+0)&$adrmsk] = $memmsk & ($val>>0); - $mem[($adr+1)&$adrmsk] = $memmsk & ($val>>8); - 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 = &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)); - $mem[($adr+0)&$adrmsk] = $memmsk & ($val>>0); - $mem[($adr+1)&$adrmsk] = $memmsk & ($val>>8); - 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 = &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); - $mem[($adr+0)&$adrmsk] = $memmsk & ($val>>0); - $mem[($adr+1)&$adrmsk] = $memmsk & ($val>>8); - 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 = &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)); - $mem[($adr+0)&$adrmsk] = $memmsk & ($val>>0); - $mem[($adr+1)&$adrmsk] = $memmsk & ($val>>8); - 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 == 017) { - # complex relocation ... OK - my $dis = $rec[$i+1]; - my $nam = '. ABS.'; - my $con = 0; - # process - my $adr = $adrmsk & ($textaddr + $dis - 4); - my $loc = 0; - my $val = 0; - my $opc = ""; - my @stk = (); - my $dun = 0; - for ($i += 2; !$dun; $i += 1) { - if ($rec[$i] == 000) { - $opc = "NOP"; - } elsif ($rec[$i] == 001) { - my @arg = splice(@stk,-2,2); - push(@stk, $arg[0] + $arg[1]); - $opc = "ADD"; - } elsif ($rec[$i] == 002) { - my @arg = splice(@stk,-2,2); - push(@stk, $arg[0] - $arg[1]); - $opc = "SUB"; - } elsif ($rec[$i] == 003) { - my @arg = splice(@stk,-2,2); - push(@stk, $arg[0] * $arg[1]); - $opc = "MUL"; - } elsif ($rec[$i] == 004) { - my @arg = splice(@stk,-2,2); - push(@stk, $arg[1] == 0 ? 0 : int($arg[0] / $arg[1])); - $opc = "DIV"; - } elsif ($rec[$i] == 005) { - my @arg = splice(@stk,-2,2); - push(@stk, $arg[0] & $arg[1]); - $opc = "AND"; - } elsif ($rec[$i] == 006) { - my @arg = splice(@stk,-2,2); - push(@stk, $arg[0] | $arg[1]); - $opc = "IOR"; - } elsif ($rec[$i] == 007) { - my @arg = splice(@stk,-2,2); - push(@stk, $arg[0] ^ $arg[1]); - $opc = "XOR"; - } elsif ($rec[$i] == 010) { - my @arg = splice(@stk,-1,1); - push(@stk, -$arg[0]); - $opc = "NEG"; - } elsif ($rec[$i] == 011) { - my @arg = splice(@stk,-1,1); - push(@stk, ~$arg[0]); - $opc = "COM"; - } elsif ($rec[$i] == 012) { - my @arg = splice(@stk,-1,1); - $val = $arg[0]; - $opc = "STO"; - $dun = 1; - } elsif ($rec[$i] == 013) { - ############## may need tweaking ################ - my @arg = splice(@stk,-1,1); - $val = $arg[0]; - $opc = "STO+DIS"; - $dun = 1; - } elsif ($rec[$i] == 016) { - ############## may need tweaking ################ - $nam = &rad2asc(($rec[$i+2]<<8)|($rec[$i+1]<<0), - ($rec[$i+4]<<8)|($rec[$i+3]<<0)); - $con = $gblsym{$nam}{VALUE}; - push(@stk, $con); - $opc = sprintf("GLB[%s]=(%o)", &trim($nam), $con); - $i += 4; - } elsif ($rec[$i] == 017) { - $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) { - $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; - } - printf $LOG "..RLD(CMPX): adr=%06o val=%06o ; dis=%06o\n", $adr, $val, $dis if $DEBUG; - } else { - die sprintf("Error: 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; - - } else { # unknown - - # invalid record type in the object file - die sprintf("Error: unknown record type 0%o (%d)", $key, $key); - - } - -} - -# done with object file -$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; -} - -#---------------------------------------------------------------------------------------------------- - -# 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 = (); - - # 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)); - - # output the record if debugging - if ($DEBUG) { - 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 - die sprintf("Error: invalid object file record format (%d)", $err) if $err; - - # compare rcv'ed checksum vs exp'ed checksum - my $exp = &chksum(0x01, $len>>0, $len>>8, @dat); - die sprintf("Error: Bad checksum exp=0x%02X rcv=0x%02X", $exp, $rcv) unless $exp == $rcv; - - # all is well, return the record - return @dat; -} - -#---------------------------------------------------------------------------------------------------- - -# the end +#!/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 + +obj2hex.pl - Convert a Macro-11 program image to PROM/load format + +=head1 SYNOPSIS + +obj2hex.pl +S<[--help]> +S<[--debug]> +S<[--verbose]> +S<[--boot]> +S<[--console]> +S<[--binary]> +S<[--ascii]> +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. + +Multiple input object files are (not yet fully) supported - this +part is work in progress. In particular definition and resolution +of global symbols are not supported. + +=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<--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). + +=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. + +Exactly ONE of B<--boot>, B<--console>, B<--binary>, or B<--ascii> +must be specified. + +=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 record must start with 0x01 + +C -- second 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 byte is checksum + +C -- compare rcv'ed checksum vs exp'ed checksum + +=head1 EXAMPLES + +Some examples of common usage: + + obj2hex.pl --help + + obj2hex.pl --verbose --boot --out 23-751A9.hex 23-751A9.obj + + obj2hex.pl --verbose --binary --out memtest.bin memtest.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 v2.0 donorth - Started to add capability to process multiple + input object files ... still a work in progress. + +=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.0'; # 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 = 'NONE'; # default rom type +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'; }, + "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 "obj2hex.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 + --console M9312 console/diagnostic prom + --binary binary program load image + --ascii ascii m9312 program load image + --bytes=N bytes per block on output + --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 read_rec ($); +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 = '. ABS.'; +my $psectaddr = 0; +my $psectnumb = -1; +my $textaddr = 0; + +# program defaults +$program{START}{VALUE} = 1; +$program{START}{PSECT} = '. ABS.'; + +# process all object files +while (my $objfile = shift(@ARGV)) { + + # open the input .obj file, die if error + my $OBJ = FileHandle->new("< ".$objfile); + die "Error: can't open input object file '$objfile'\n" unless defined $OBJ; + + # now parse all the records + while (my @rec = &read_rec($OBJ)) { &parse_rec(\@rec); } + + # done with object file + $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; +} + +#---------------------------------------------------------------------------------------------------- + +# 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 = (); + + # 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)); + + # output the record if debugging + if ($DEBUG) { + 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 + die sprintf("Error: invalid object file record format (%d)", $err) if $err; + + # compare rcv'ed checksum vs exp'ed checksum + my $exp = &chksum(0x01, $len>>0, $len>>8, @dat); + die sprintf("Error: Bad checksum exp=0x%02X rcv=0x%02X", $exp, $rcv) unless $exp == $rcv; + + # all is well, return the record + return @dat; +} + +#---------------------------------------------------------------------------------------------------- + +# parse an input object file record, update data structures + +sub parse_rec ($) { + + my ($rec) = (@_); + + # type is first byte of record + my $key = $rec->[0]; + + if ($key == 001) { # GSD + + # iterate over GSD subrecords + for (my $i = 2; $i < scalar(@$rec); ) { + # GSD records are fixed 8B length all in the same format + my $nam = &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'); + if ($ent == 3) { + # XFRADR + $program{START}{PSECT} = $nam; + $program{START}{VALUE} = $val; + } elsif ($ent == 4) { + # GBLSYM flags + $gblsym{$nam}{FLG}{$flg&(1<<0) ? "WEA" : "STR"}++; + $gblsym{$nam}{FLG}{$flg&(1<<3) ? "DEF" : "REF"}++; + $gblsym{$nam}{FLG}{$flg&(1<<5) ? "REL" : "ABS"}++; + $gblsym{$nam}{PSECT} = $psectname; + $gblsym{$nam}{VALUE} = $val; + } elsif ($ent == 5) { + # PSECT flags + $psect[++$psectnumb] = $nam; + $psect{$nam}{NUMBER} = $psectnumb; + $psect{$nam}{FLG}{$flg&(1<<0) ? "GBL" : $flg&(1<<6) ? "GBL" : "LCL"}++; + $psect{$nam}{FLG}{$flg&(1<<2) ? "OVR" : "CAT"}++; + $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"}++; + if ($psect{$nam}{FLG}{CAT}) { + $psect{$nam}{LENGTH} = $val; + $psect{$nam}{START} = $psectaddr; + $psectname = $nam; + $psectaddr += $val; + } elsif ($psect{$nam}{FLG}{ABS}) { + $psect{$nam}{LENGTH} = $val; + $psect{$nam}{START} = 0; + $psectname = $nam; + } + } + if ($DEBUG) { + printf $LOG "..GSD: type='%-6s'(%03o) name='%s' value=%06o", $ent[$ent], $ent, $nam, $val; + printf $LOG " flags=%s", join(",", sort(keys(%{$gblsym{$nam}{FLG}}))) if $ent == 4; + printf $LOG " flags=%s", join(",", sort(keys(%{$psect{$nam}{FLG}}))) if $ent == 5; + printf $LOG "\n"; + } + $i += 8; + } + + } elsif ($key == 002) { # ENDGSD + + # just say we saw it + printf $LOG "..ENDGSD\n" if $DEBUG; + + $program{END}{ADDRESS} = 0; + foreach my $nam (sort(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[%d](%s) START=%06o END=%06o LENGTH=%06o\n", + $psect{$nam}{NUMBER}, $nam, $start, $end, $length if $length && $DEBUG; + } + $program{START}{ADDRESS} = $program{START}{VALUE} + $psect{$program{START}{PSECT}}{START}; + printf $LOG "..PROG(ADDRESS) START=%06o END=%06o\n", + $program{START}{ADDRESS}, $program{END}{ADDRESS} if $DEBUG; + + } elsif ($key == 003) { # 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) { # 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); + $mem[($adr+0)&$adrmsk] = $memmsk & ($val>>0); + $mem[($adr+1)&$adrmsk] = $memmsk & ($val>>8); + printf $LOG "..RLD(IR): adr=%06o val=%06o ; dis=%06o con=%06o\n", + $adr, $val, $dis, $con if $DEBUG; + $i += 4; + } elsif ($ent == 002) { + # global relocation ... TBD <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + my $dis = $rec->[$i+1]; + my $nam = &rad2asc(($rec->[$i+3]<<8)|($rec->[$i+2]<<0), ($rec->[$i+5]<<8)|($rec->[$i+4]<<0)); + # process + printf $LOG "..RLD(GR): dis=%06o nam='%s'\n", + $dis, $nam if $DEBUG; + $i += 6; + } elsif ($ent == 003) { + # internal displaced ... 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)); + $mem[($adr+0)&$adrmsk] = $memmsk & ($val>>0); + $mem[($adr+1)&$adrmsk] = $memmsk & ($val>>8); + printf $LOG "..RLD(ID): adr=%06o val=%06o ; dis=%06o con=%06o\n", + $adr, $val, $dis, $con if $DEBUG; + $i += 4; + } elsif ($ent == 004) { + # global displaced relocation ... TBD <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + my $dis = $rec->[$i+1]; + my $nam = &rad2asc(($rec->[$i+3]<<8)|($rec->[$i+2]<<0), ($rec->[$i+5]<<8)|($rec->[$i+4]<<0)); + # process + printf $LOG "..RLD(GDR): dis=%06o nam='%s'\n", + $dis, $nam if $DEBUG; + $i += 6; + } elsif ($ent == 005) { + # global additive relocation ... TBD <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + my $dis = $rec->[$i+1]; + my $nam = &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 + printf $LOG "..RLD(GAR): dis=%06o con=%06o nam='%s'\n", + $dis, $con, $nam if $DEBUG; + $i += 8; + } elsif ($ent == 006) { + # global additive displaced relocation ... TBD <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + my $dis = $rec->[$i+1]; + my $nam = &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 + printf $LOG "..RLD(GADR): dis=%06o con=%06o nam='%s'\n", + $dis, $con, $nam if $DEBUG; + $i += 8; + } elsif ($ent == 007) { + # location counter definition ... OK + my $dis = $rec->[$i+1]; + my $nam = &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); + 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); + 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 + $mem[($adr+0)&$adrmsk] = $memmsk & ($val>>0); + $mem[($adr+1)&$adrmsk] = $memmsk & ($val>>8); + printf $LOG "..RLD(LIM1): adr=%06o val=%06o ; dis=%06o\n", + $adr, $val, $dis if $DEBUG; + $dis += 2; + $adr += 2; + $val = $datmsk & ($program{END}{ADDRESS}); + $mem[($adr+0)&$adrmsk] = $memmsk & ($val>>0); + $mem[($adr+1)&$adrmsk] = $memmsk & ($val>>8); + printf $LOG "..RLD(LIM2): adr=%06o val=%06o ; dis=%06o\n", + $adr, $val, $dis if $DEBUG; + $i += 2; + } elsif ($ent == 012) { + # psect relocation ... OK + my $dis = $rec->[$i+1]; + my $nam = &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}); + $mem[($adr+0)&$adrmsk] = $memmsk & ($val>>0); + $mem[($adr+1)&$adrmsk] = $memmsk & ($val>>8); + 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 = &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)); + $mem[($adr+0)&$adrmsk] = $memmsk & ($val>>0); + $mem[($adr+1)&$adrmsk] = $memmsk & ($val>>8); + 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 = &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); + $mem[($adr+0)&$adrmsk] = $memmsk & ($val>>0); + $mem[($adr+1)&$adrmsk] = $memmsk & ($val>>8); + 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 = &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)); + $mem[($adr+0)&$adrmsk] = $memmsk & ($val>>0); + $mem[($adr+1)&$adrmsk] = $memmsk & ($val>>8); + 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 == 017) { + # complex relocation ... OK + my $dis = $rec->[$i+1]; + my $nam = '. ABS.'; + my $con = 0; + # process + my $adr = $adrmsk & ($textaddr + $dis - 4); + my $loc = 0; + my $val = 0; + my $opc = ""; + my @stk = (); + my $dun = 0; + for ($i += 2; !$dun; $i += 1) { + if ($rec->[$i] == 000) { + $opc = "NOP"; + } elsif ($rec->[$i] == 001) { + my @arg = splice(@stk,-2,2); + push(@stk, $arg[0] + $arg[1]); + $opc = "ADD"; + } elsif ($rec->[$i] == 002) { + my @arg = splice(@stk,-2,2); + push(@stk, $arg[0] - $arg[1]); + $opc = "SUB"; + } elsif ($rec->[$i] == 003) { + my @arg = splice(@stk,-2,2); + push(@stk, $arg[0] * $arg[1]); + $opc = "MUL"; + } elsif ($rec->[$i] == 004) { + my @arg = splice(@stk,-2,2); + push(@stk, $arg[1] == 0 ? 0 : int($arg[0] / $arg[1])); + $opc = "DIV"; + } elsif ($rec->[$i] == 005) { + my @arg = splice(@stk,-2,2); + push(@stk, $arg[0] & $arg[1]); + $opc = "AND"; + } elsif ($rec->[$i] == 006) { + my @arg = splice(@stk,-2,2); + push(@stk, $arg[0] | $arg[1]); + $opc = "IOR"; + } elsif ($rec->[$i] == 007) { + my @arg = splice(@stk,-2,2); + push(@stk, $arg[0] ^ $arg[1]); + $opc = "XOR"; + } elsif ($rec->[$i] == 010) { + my @arg = splice(@stk,-1,1); + push(@stk, -$arg[0]); + $opc = "NEG"; + } elsif ($rec->[$i] == 011) { + my @arg = splice(@stk,-1,1); + push(@stk, ~$arg[0]); + $opc = "COM"; + } elsif ($rec->[$i] == 012) { + my @arg = splice(@stk,-1,1); + $val = $arg[0]; + $opc = "STO"; + $dun = 1; + } elsif ($rec->[$i] == 013) { + ############## may need tweaking ################ + my @arg = splice(@stk,-1,1); + $val = $arg[0]; + $opc = "STO+DIS"; + $dun = 1; + } elsif ($rec->[$i] == 016) { + ############## may need tweaking ################ + $nam = &rad2asc(($rec->[$i+2]<<8)|($rec->[$i+1]<<0), + ($rec->[$i+4]<<8)|($rec->[$i+3]<<0)); + $con = $gblsym{$nam}{VALUE}; + push(@stk, $con); + $opc = sprintf("GLB[%s]=(%o)", &trim($nam), $con); + $i += 4; + } elsif ($rec->[$i] == 017) { + $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) { + $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; + } + printf $LOG "..RLD(CMPX): adr=%06o val=%06o ; dis=%06o\n", $adr, $val, $dis if $DEBUG; + } else { + die sprintf("Error: 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; + + } else { # unknown + + # invalid record type in the object file + die sprintf("Error: unknown record type 0%o (%d)", $key, $key); + + } + + return; +} + +#---------------------------------------------------------------------------------------------------- + +# the end