MacLarry
Volume Number: 16 (2000)
Issue Number: 2
Column Tag: Programming
MacLarry
By F.C. Kuechmann, Vancouver WA
An M68HC11 S-record dissecting kit in CodeWarrior Pascal
Introduction
S-record files are used to transfer code from development systems to PROM and EPROM programmers. This article discusses the implementation and use of MacLarry, an S-record dissecting program. MacLarry allows you to view the contents of S-records in several formats.
What is an S-Record File?
Programmers who are confined to the desktop world experience code movement in terms of disk file to memory and back to disk. They are often unfamiliar with hex-format files that are used to transfer program code from a desktop development system to an EPROM (Erasable Programmable Read-Only Memory) or PROM programmer for use in an embedded controller. Common examples of embedded controllers include the M68HC16 in a Jaz drive, and the M683xx-series inhabiting Palm Pilot PDAs and anti-lock brake systems (ABSes) in General Motors cars. In each of these cases the program code probably spent some time in S-records.
How do we use one?
In a typical instance you might be writing a BASIC11 program for a Micromint RTC-HC11 stackable module to be used to control a gate and barrier system at a fish hatchery. After the usual multiple-pass write-debug-modify-debug cycle you send an "ESAVE" command to the RTC-HC11 via the RS-232C serial port connection (usually the modem port on a Mac). The RTC-HC11, after a brief delay, churns out the program in its memory in the form of S-records. Using a terminal program you capture the records to a text file, clean up any garbage with an editor, and transfer the file to an EPROM programmer via a serial port. When the file has been written to a 27256 (32k-by-8) EPROM by the programmer, you type an "AUTOST" command to the RTC-HC11, shut off its power, and replace the resident EPROM with the one you've just programmed. Restore the power and the program autostarts and runs.
At another time you might submit the MC68HC05b6 code for your smart toaster to Motorola in the form of an S-record file so that the processors for your next production run can be masked with the code during manufacture.
How is MacLarry Useful?
In the normal course of embedded development MacLarry is probably unnecessary. You already have the assembler sourcecode. But what if old friend Murphy (of Murphy's Law fame) visits and destroys all copies of your source, leaving you with only an S-record file of your code? MacLarry could be very useful in helping recover your source.
Perhaps, after getting so involved in programming and testing that you neglected to clearly label the files, you're merely trying to determine which S-record file holds the most recent version, or a particular version, of your code.
Or what if you're just curious about some S-record files you found and downloaded from the net? MacLarry again comes to the rescue.
What does an S-record look like?
The S-record format originated at Motorola and is commonly used with code for processors made by that company, including the extremely popular MC68HC05 used in cellular phones and pagers, and the M68HC11 series. S-records are similar in concept to the more common Intel hex format with a few important differences in detail. An S-record line consists of a two-character header, a two-character hex bytecount that gives the number of bytes in the record, a two-byte hex address, bytecount minus three hex bytes of code, and a one's complement hex checksum. The S-record files that MacLarry deals with have "S1" headers on all lines but the last, which has an "S9" header and no significant content. The components of a line with an "S1" header might look like Figure 1.
Figure 1. S-record format.
"S1" is the header. The next two digits (13) indicate in hex the number of bytes to follow in the line, including a four-digit address and two-digit checksum. The "0000" is the address and is followed by 10 (hex, 16 decimal) bytes of data and the "BF" checksum.
MacLarry the Dissector
The CodeWarrior Pascal program MacLarry can be used to dissect M68HC11 S-records in order to recreate assembler sourcecode. MacLarry dissects 68HC11 S-records three ways:
- Asciification - isolates printable ASCII characters (range 32-126).
- Disassembly into mnemonics with address and data fields.
- Disassembly without address and data fields for ready conversion to assembler sourcecode.
Asciification or dumpster?
What is "asciification"? This method of dissecting S-record files prints characters in the ASCII range 32-126 beside the address and hex data, replacing characters outside that range with dots. This sort of output is commonly called a "hex dump" when derived from a binary file. But what is it if you start with a hex file to begin with? Can you hex dump a hex file? "Dumpster" might be a more descriptive name than "asciifier". Whatever it's called, it simplifies locating embedded text strings and command tables in the target file.
The output files
A MacLarry dissection creates three files, one each with the extensions "asc", "dis" and "src" appended to the original filename minus the usual "S19" extension. Thus if the S-record filename is JudithWhatley.S19, the output files will be JudithWhatley.asc, JudithWhatley.dis and JudithWhatley.src.
Sample output
Listing 1a shows the asciification of parts of the file BASIC113.S19, containing version 1.13 of BASIC11. BASIC11 is a freeware integer BASIC interpreter for the M68HC11. Versions of this BASIC are distributed by companies such as New Micros Inc, and MicroMint Inc with some of their M68HC11-based stackable control computers. Generic versions that aren't modified specifically for a given manufacturer's boards are also freely available via the internet. Some URLs can be found at the end of this article. The listing shows the identity text and copyright notice.
Listing 1a.
Ascifying bas113.s19
Start 19:38:41
Finish 19:39:08
E000 0000 7EED30BDECD6B6600881552603BDF116 ~.0....'..U&....
E010 0010 BDE0608EDFFF0E4F5FDD3ADD3FDD41BD ..'....O_.:.?.A.
E020 0020 E0A37F00197F00187F001DBDE0F4BDE0 ................
E030 0030 B3BDE053BDEE0F1A83000026D6DE0EDF ...S.......&....
E040 0040 2FBDE1167D001926CA20D7DE2F6F00DF /...}..&. ../o..
E050 0050 0E20C0BDE38C81202605BDE39520F439 . ..... &.... .9
E060 0060 CEE0667EE10B0D0A0D0A424153494331 ..f~......BASIC1
E070 0070 312076312E31330D0A436F7079726967 1 v1.13..Copyrig
E080 0080 687420313938352C313938362062790D ht 1985,1986 by.
E090 0090 0A476F72646F6E20446F7567686D616E .Gordon Doughman
E0A0 00A0 0D0A00CEE0A97EE10B0D0A5245414459 ......~....READY
Listing 1b reveals a table of BASIC11 commands, which are presumably used in tokenizing input.
Listing 1b.
E200 0200 813A2607867ABDE3A020B3810D270586 .:&..z... ...'..
E210 0210 027EEA4E867DBDE3A0DC0283DE00CEDE .~.N.}..........
E220 0220 00E7023944415441000CE3BC4C455400 ...9DATA....LET.
E230 0230 01E3DC52454144000DEA0E524553544F ...READ....RESTO
E240 0240 5245000EE3BB474F535542000FE3B247 RE....GOSUB....G
E250 0250 4F544F0012E3B24F4E54494D450025E8 OTO....ONTIME.%.
E260 0260 894F4E4952510026E8894F4E50414343 .ONIRQ.&..ONPACC
E270 0270 0028E8A94F4E0013E83B52455455524E .(..ON...;RETURN
E280 0280 0014E3BB49460015E8BD494E50555400 ....IF....INPUT.
E290 0290 16E9F65052494E540003E98E3F0003E9 ...PRINT....?...
E2A0 02A0 8E464F520004E9264E4558540005E981 .FOR...&NEXT....
E2B0 02B0 53544F500017E3BB454E445748001AE3 STOP....ENDWH...
E2C0 02C0 BB454E440018E3BB52454D000AE3BC54 .END....REM....T
E2D0 02D0 524F4E0006E3BB54524F46460007E3BB RON....TROFF....
E2E0 02E0 5748494C450019EA41504F4B450008E7 WHILE...APOKE...
E2F0 02F0 B344494D0009EA0E454550001BE7CC50 .DIM....EEP....P
E300 0300 4F525441001CE3D8504F525442001DE3 ORTA....PORTB...
E310 0310 D8504F525443001EE3D8504F52544400 .PORTC....PORTD.
E320 0320 1FE3D8494E425954450023EA3954494D ...INBYTE.#.9TIM
E330 0330 450024EA49524554490027E3BB504143 E.$.IRETI.'..PAC
The BUFFALO monitor is a freeware M68HC11 monitor. BUFFALO is an acronym for Bit User Fast Friendly Aid to Logical Operation.
The partial asciification of the Buffalo monitor, is shown in Listing 2. It looks like a command table starting about $E543, followed by the Buffalo sign-on string at $E61F, followed by error messages.
Listing 2.
E540 0540 BE3905415353454DF2F905425245414B .9.ASSEM...BREAK
E550 0550 E6DD0442554C4BE7980742554C4B414C ...BULK...BULKAL
E560 0560 4CE79D0443414C4CE8A30444554D50E7 L...CALL...DUMP.
E570 0570 A60446494C4CE83B02474FE8FB044845 ..FILL.;.GO...HE
E580 0580 4C50EA7604484F5354EEE4044C4F4144 LP.v.HOST...LOAD
E590 0590 EF82064D454D4F5259F081044D4F5645 ...MEMORY...MOVE
E5A0 05A0 F1340750524F43454544E8E908524547 .4.PROCEED...REG
E5B0 05B0 4953544552F1C50653544F504154E9D1 ISTER...STOPAT..
E5C0 05C0 055452414345E98C06564552494659EF .TRACE...VERIFY.
E5D0 05D0 7A013FEA760558424F4F54FDB1034153 z.?.v.XBOOT...AS
E5E0 05E0 4DF2F9024246E83B04434F5059F13405 M...BF.;.COPY.4.
E5F0 05F0 4552415345E798024D44E7A6024D4DF0 ERASE...MD...MM.
E600 0600 81025244F1C502524DF1C50452454144 ..RD...RM...READ
E610 0610 F13402544DEEE40454455354FE29FF42 .4.TM...TEST.).B
E620 0620 554646414C4F20332E322028696E7429 UFFALO 3.2 (int)
E630 0630 202D2042697420557365722046617374 - Bit User Fast
E640 0640 20467269656E646C792041696420746F Friendly Aid to
E650 0650 204C6F676963616C204F706572617469 Logical Operati
E660 0660 6F6E04576861743F04546F6F204C6F6E on.What?.Too Lon
E670 0670 670446756C6C044F702D2004726F6D2D g.Full.Op- .rom-
E680 0680 04436F6D6D616E643F04426164206172 .Command?.Bad ar
E690 0690 67756D656E74044E6F20686F73742070 gument.No host p
E6A0 06A0 6F727420617661696C61626C6504646F ort available.do
E6B0 06B0 6E6504636865636B73756D206572726F ne.checksum erro
E6C0 06C0 72046572726F72206164647220047265 r.error addr .re
E6D0 06D0 636569766572206572726F7204BDE2FA ceiver error....
The disassembler
Disassembly is the conversion of machine instructions into their assembly language equivalents. An output line produced by the MacLarry disassembler consists of an unmodified S-record address, then the sum of the number of bytes fetched and the EPROM base address (which defaults to zero). One (or two if the byte is $18, $1A or $CD) byte(s) of opcode precedes one or more bytes of operand if present (the inherent addressing mode has no operand). The mnemonic then is followed by the hex operand if present.. A typical disassembled line looks something like Figure 2.
Figure 2. MacLarry sample output.
Listings 3 and 4 provide examples.
Listing 3 shows the disassembly of the beginning of version 1.13 of BASIC11.S19. The added comments indicate the Reset entry point accessed via the address stored at $FFFE-$FFFF.
Listing 3.
Disass -- bas113.s19
EPROM Addr $0000
Offset -- 0 bytes
Start 19:39:09
Finish 19:39:20
E000 0000 7E ED 30 jmp $ED30
E003 0003 BD EC D6 jsr $ECD6
E006 0006 B6 60 08 ldaa $6008
E009 0009 81 55 cmpa #$55
E00B 000B 26 03 bne $03
E00D 000D BD F1 16 jsr $F116
E010 0010 BD E0 60 jsr $E060
E013 0013 8E DF FF lds #$DFFF
E016 0016 0E cli
E017 0017 4F clra
E018 0018 5F clrb
E019 0019 DD 3A std $3A
E01B 001B DD 3F std $3F
E01D 001D DD 41 std $41
E01F 001F BD E0 A3 jsr $E0A3
E022 0022 7F 00 19 clr $0019
E025 0025 7F 00 18 clr $0018
E028 0028 7F 00 1D clr $001D
E02B 002B BD E0 F4 jsr $E0F4
.
ED27 0D27 97 1C staa $1C
ED29 0D29 B7 A0 00 staa $A000
ED2C 0D2C 7F 00 37 clr $0037
ED2F 0D2F 39 rts
ED30 0D30 86 93 ldaa #$93 ; reset vector
; points here
ED32 0D32 B7 10 39 staa $1039
ED35 0D35 86 03 ldaa #$03
ED37 0D37 B7 10 24 staa $1024
ED3A 0D3A 8E DF FF lds #$DFFF
ED3D 0D3D CE 00 C4 ldx #$00C4
ED40 0D40 18 CE ED C2 ldy #$ED
ED44 0D44 C6 14 ldb #$14
ED46 0D46 86 7E ldaa #$7E
ED48 0D48 A7 00 staa $00,x
ED4A 0D4A 08 inx
ED4B 0D4B 1A EF 00 sty $00,x
ED4E 0D4E 08 inx
ED4F 0D4F 08 inx
ED50 0D50 5A decb
ED51 0D51 26 F5 bne $F5
ED53 0D53 97 9E staa $9E
ED55 0D55 97 A1 staa $A1
ED57 0D57 CC EC 79 ldd #$EC79
ED5A 0D5A DD 9F std $9F
ED5C 0D5C CC EC 6E ldd #$EC6E
ED5F 0D5F DD A2 std $A2
ED61 0D61 CE 00 F7 ldx #$00F7
ED64 0D64 CC ED 30 ldd #$ED30
ED67 0D67 ED 01 std $01,x
ED69 0D69 ED 04 std $04,x
ED6B 0D6B ED 07 std $07,x
ED6D 0D6D CE 00 A4 ldx #$00A4
ED70 0D70 C6 20 ldb #$20
ED72 0D72 6F 00 clr $00,x
ED74 0D74 08 inx
ED75 0D75 5A decb
In Listing 4, you can see the disassembly of the beginning of version 3.2 of the Buffalo monitor. The comments highlight the $E000 entry point.
Listing 4.
Disass -- buf32.s19
EPROM Addr $0000
Offset -- 0 bytes
Start 19:40:05
Finish 19:40:16
E000 0000 CE 10 0A ldx #$100A ;reset vector $FFFE-$FFFF
E003 0003 1F 00 01 03 brclr $00,x $01 $03 ; points to $E000
E007 0007 7E B6 00 jmp $B600 ;test pin 1 at addr $100A
E00A 000A 86 93 ldaa #$93 ;[port E]; branch to $E00A
E00C 000C B7 10 39 staa $1039 ; if pin 1 low, else jmp to
E00F 000F 86 00 ldaa #$00 ;512 byte reset routine at
E011 0011 B7 10 24 staa $1024 ;$B600 in internal EEPROM
E014 0014 86 00 ldaa #$00
E016 0016 B7 10 35 staa $1035
E019 0019 8E 00 65 lds #$0065
E01C 001C BD E3 4B jsr $E34B
E01F 001F CE 00 47 ldx #$0047
Offset and address options
The two user options - offset and EPROM base address - affect disassembly. Since most real-world code contains embedded data, tables, and other things that aren't opcodes and operands, it is useful to be able to vary the point at which disassembly begins, "throwing away" a user-specified number of data bytes from the beginning of the S-record file. It is frequently necessary to combine pieces from several disassemblies with different offsets to accurately reconstruct the skeleton of the source code.
In addition, S-record addresses may or may not correspond to the actual EPROM addresses where the code runs. For example, in the case of the BASIC11E9 distributed by New Micros, S-record addresses occupy the range $0000-$1FFF, yet the code is programmed into an EPROM that is addressed at $E000-$FFFF (2764 8k-by-8 size). Address references are easiest to assess when the corrected addresses are provided by the disassembler. The EPROM address also affects the asciification listing, showing the location of text in processor memory. MacLarry's menu allows the user to vary the offset in the range 0-9 bytes and the EPROM base address in the range $0000-$E000 in $1000 steps.
In listings 1-4 in this article, the S-record addresses run from $E000 through $FFFF and the EPROM address option is left at the zero default.
Program Structure
MacLarry makes three separate passes through the S-record file, creating first the "asc" file, followed by the "dis", and concluding with the "src" file. In all cases output is initially written to a temporary file, then copied to the final output file after dissection is complete. In pseudocode, minus the folderol and simplified, the main program loop looks like this:
for n:= 1 to 3 do
begin
open s-rec file
open tmp file
case n of
1:
asciify
2:
disassemble
3:
make sourcecode
end;
close s-rec file
copy tmp file to final file
end;
The asciifier simply reads each line from the input file, strips out the header and byte count, then formats an output line consisting of address, corrected address, hex data, and the printable ASCII characters in the data; characters outside the printable ASCII range #32-#126 are each represented by a dot (.).
The disassembler structure is more complex but logical. After opening the input and output files, the program fetches and counts but otherwise ignores the number of bytes specified in the offset parameter. In pseudocode, you might do it like this:
count = 0
while (not eof) and (count < offset)
fetch data byte
increment count
wend
The main loop follows:
while (not eof) and (not error)
fetch data byte
if not making assembler sourcecode
send S-record address to outfile
send EPROM address to outfile
endif
process data byte as opcode
wend
The Main File
Listings 5a and 5b show the contents of the main program file, MacLarry.p . After calling the Initialize procedure to init the toolbox, window, global variables and such, the program calls the DoMain procedure in Listing 5b, which serves as the program's manager. When it returns from DoMain it calls the procedure CleanUpMess, whose function should be obvious, before terminating.
Listing 5.a
MacLarry
(*
MacLarry
A Motorola M68HC11 S-record Dissecter
F.C. Kuechmann
1998-9
*)
Program MacLarry;
uses
Globals,Inits,Misc,FileStuff,EventStuff,TimeStuff,
Disassembler,Sourcer,DoAski;
begin
Initialize;
InitRegionHdl;
DoMain;
KillRegionHdl;
CleanUpMess;
end.
DoMain first initializes some local variables before entering two nested repeat loops. The inner loop executes, continually checking events and updating the time, offset and EPROM address displays, until the goFlag is set by pushing the "Go" button or the doneFlag is set by pushing the "Quit" button. If the "Quit" button was pushed, the Leave instruction exits the outer loop and the procedure is exited thereafter. If the "Go" button was pushed a file selection window appears displaying the 'TEXT' files in the current directory. If no selection is made, the outer loop is exited. If a selection is made, a for loop that serves as dispatcher executes three times, opening files and calling the asciifier, disassembler and sourcer code in succession. The proper output file is created during each iteration and the header, start and finish times written to the output file before the temporary file contents are added. In the case of the "dis" and "src" files, the EPROM address and offset values are also written to the output file. After completion of the for loop a repeat loop executes until either the "Go" button is pushed to dissect another file (or perhaps the same file with a different offset value) or the "Quit" button is pushed to exit.
Listing 5b.
DoMain
procedure DoMain;
var
selectFlag,eofFlag,errFlag,doneFlag,
goFlag,flag:boolean;
fileName,textLine,outLine,outLine1,
outLine2,S,S1,S2:Str255;
nowTime,startTime,startTime2,endTime:DateTimeRec;
index,offset,n:integer;
epromAddr:word;
nowSecs,startSecs:longint;
const
cDoneStr:string='Done!';
begin
index:=1;
n:=0;
startSecs:=0;
ClearWindow;
ShowWindow(ggMonkWindow);
errFlag:=FALSE;
doneFlag:=FALSE;
goFlag:=FALSE;
flag:=TRUE;
outLine:=ggcNullString;
outLine1:=ggcNullString;
outLine2:=ggcNullString;
repeat
epromAddr:=ggwEpromAddr;
offset:=ggThrowAway;
SetControlTitle(ggGoButtonHdl,'Run');
repeat
GetDateTime(nowSecs);
if nowSecs<>startSecs then
begin
GetTime(startTime);
Time2String(startTime,S2);
UpdateRect(ggcBeginTime,S2);
startSecs:=nowSecs;
end;
HandleEvent(doneFlag,goFlag);
if offset<>ggThrowAway then
begin
offset:=ggThrowAway;
UpdateRect(ggcOffsetRect,ggcNullString);
end;
if epromAddr<>ggwEpromAddr then
begin
epromAddr:=ggwEpromAddr;
UpdateRect(ggcEpromRect,ggcNullString);
end;
until goFlag or doneFlag;
if doneFlag then
leave;
SetControlTitle(ggGoButtonHdl,'Pause');
SelectFile(selectFlag,fileName);
ClearWindow;
if not selectFlag then
leave;
GetTime(startTime);
Time2String(startTime,S2);
UpdateRect(ggcBeginTime,S2);
DateToSeconds(startTime,startSecs);
outLine1:=Word2HexStr(ggwEpromAddr);
outLine1:=concat('EPROM Addr $',outLine1);
NumToString(ggThrowAway,outLine2);
outLine2:=concat('Offset -- ',outLine2,' bytes');
for index:=1 to 3 do
begin
InitDisDatLin;
errFlag:=FALSE;
OpenSrecFile(fileName,errFlag);
if errFlag then
leave;
{output goes first to a temporary file}
{that is copied to the final out file}
{with start and end times both at the}
{beginning}
OpenTmpFileWrite(errFlag);
if errFlag then
leave;
GetTime(startTime2);
Time2String(startTime2,S1);
case index of
ggcMakeAski:
begin
S:='Ascifying '+fileName;
AscifyFile(startSecs,doneFlag);
UpdateRect(ggcAsky,cDoneStr);
end;
ggcMakeDis:
begin
S:='Disass -- '+fileName;
DisDatFile(startSecs,doneFlag);
end;
ggcMakeDat:
begin
S:='Sourc --- '+fileName;
SourceDatFile(startSecs,doneFlag);
end;
end;
GetTime(endTime);
Time2String(endTime,S2);
CloseSrecFile(errFlag);
CloseTmpFile(errFlag);
if errFlag then
leave;
{open temp file for input}
OpenTmpFileRead(errFlag);
{create & open destination file}
OpenOutFile(fileName,index,errFlag,TRUE);
if errFlag then
leave;
{asc, dis or src line}
WriteToOutFile(S,errFlag);
{EPROM addr & offset to disass & src files}
if index>ggcMakeAski then
begin
WriteToOutFile(outLine1,errFlag);
WriteToOutFile(outLine2,errFlag);
end;
if not errFlag then
begin
{beg & fin times}
WriteToOutFile('Start '+S1,errFlag);
WriteToOutFile('Finish '+S2,errFlag);
{send a few blank lines}
for n:=1 to 3 do
WriteToOutFile(ggcNullString,errFlag);
eofFlag:=FALSE;
{read tmp, write final}
while not (eofFlag or errFlag) do
begin
ReadFromTmpFile(S,eofFlag,errFlag);
WriteToOutFile(S,errFlag);
end;
{shoot a few blanks}
for n:=1 to 3 do
WriteToOutFile(ggcNullString,errFlag);
CloseTmpFile(errFlag);
{erase tmp file for re-use}
ScratchTmpFile(errFlag);
CloseOutFile(index,errFlag,flag);
EraseErrMess;
end;
end; {for}
UpdateS19Rect(ggcNullString,2);
goFlag:=FALSE;
SetControlTitle(ggGoButtonHdl,'Run');
repeat
HandleEvent(doneFlag,goFlag);
until goFlag or doneFlag or errFlag;
EraseErrMess;
until doneFlag or errFlag;
Creating assembler sourcecode
The asciifier, disassembler and sourcer routines all fetch lines one-at-a-time from the input file and dissect them in the appropriate manner, blessing the output files with the results.
The following disassembly code listings are for creation of assembler source files ("src" filename extension) with a space in the label field left of the mnemonic. The code for "dis" files is similar, but the output lines contain address and data fields left of the mnemonic.
For disassemblies a pair of hex digits is chopped from the data segment of the s-record and converted to an unsigned byte integer value. It is then passed to a ProcessOpCode procedure shown in Listing 6. There, the opcode is tested against the values held in sets of byte constants whose membership is arranged according to addressing mode.
- cPostByteSet membership indicates we've got the first byte of a two-byte opcode and need to fetch another byte to find out what to do. Postbyte instructions operate on the Y-index register and D-accumulator.
- cRelSet instructions are relative branches in the range -128..+127 addresses from the current address and require fetching a single operand byte holding the offset.
- cInhSet opcodes consist of complete instructions requiring no operands.
- cIndxSet opcodes use the X-index register and need a variable number of operand bytes.
- cImmSet opcodes have data operands rather than addresses.
- cExtSet opcodes use extended addressing with two byte operands.
- cDirSet opcodes use direct addressing with single byte operands.
Listing 6.
ProcessOpCode
procedure ProcessOpCode(opcode:byte;
var errFlag,eofFlag:boolean);
const
cPostByteSet :ggtByteSet=[$18,$1a,$cd];
cRelSet :ggtByteSet=[$20..$2f,$8d];
cInhSet :ggtByteSet=[$00..$0f,$10,$11,$16,
$17,$19,$1b,$30..$3f,
$40,$43,$44,$46..$4a,
$4c,$4d,$4f,$50,$53,$54,
$56..$5a,$5c,$5d,$5f,
$8f,$cf];
cIndxSet :ggtByteSet=[$1c..$1f,$60,$63,$64,
$66..$6a,$6c..$6f,
$a0..$af,$e0..$ef];
cImmSet :ggtByteSet=[$80..$86,$88..$8c,
$8e,$c0..$c6,
$c8..$cc,$ce];
cExtSet :ggtByteSet=[$15,$70,$73,$74,
$76..$7a,$7c..$7f,
$ad,$b0..$bf,$f0..$ff];
cDirSet :ggtByteSet=[$12..$14,$90..$9f,$d0..$df];
begin
if opcode in cPostByteSet then
ProcessPostByte(opcode,errFlag,eofFlag)
else if opcode in cInhSet then
ProcessInherent(opcode,errFlag,eofFlag)
else if opcode in cIndxSet then
ProcessIndexX(opcode,errFlag,eofFlag)
else if opcode in cRelSet then
ProcessRelative(opcode,errFlag,eofFlag)
else if opcode in cImmSet then
ProcessImmediate(opcode,errFlag,eofFlag)
else if opcode in cExtSet then
ProcessExt(opcode,errFlag,eofFlag)
else if opcode in cDirSet then
ProcessDirect(opcode,errFlag,eofFlag)
else
ProcessNotInSets(opcode,errFlag);
end;
Processing inherently
Converting inherent addressing mode instructions is simplest in that the operand is inherent in the opcode and need not be fetched separately. The ProcessInherent procedure is shown in Listing 7. Using the opcode as the selector in a case statement, the mnemonic is assigned to the string variable s. Variable s is then concatenated with the string variable srcStr, which holds a space, and sent to the output file.
Listing 7.
ProcessInherent
procedure ProcessInherent(opcode:byte;
var errFlag,eofFlag:boolean);
var
s,srcStr:str255;
begin
srcStr:=ggcSpace;
case opcode of
$00 :
s:='test';
$01 :
s:='nop';
$02 :
s:='idiv';
$03 :
s:='fdiv';
.
.
.
$58 :
s:='aslb';
$59 :
s:='rolb';
$5a :
s:='decb';
$5c :
s:='incb';
$5d :
s:='tstb';
$5f :
s:='clrb';
$8f :
s:='xgdx';
$cf :
s:='stop';
end; {case}
srcStr:=concat(srcStr,s);
PutString(srcStr,errFlag);
end;
Other address modes
Addressing modes other than inherent require one, two or three byte operands to be fetched from the input text and attached to the output string. The direct address mode offers a simple illustration of how this can be done.
Direct mode
The procedure ProcessDirect, in Listing 8, defines two sets of constants that hold the values of the opcodes that require more than one operand byte.
Listing 8.
ProcessDirect
procedure ProcessDirect(opcode:byte;
var errFlag,eofFlag:boolean);
var
s,operandStr,srcStr:str255;
operand:byte;
badHexFlag:boolean;
const
cTwoByteOperSet :ggtByteSet=[$14];
cThreeByteOperSet :ggtByteSet=[$12,$13];
begin
badHexFlag:=FALSE;
srcStr:=ggcSpace;
case opcode of {inst xx}
$12: {brset dd mm xx}
s:='brset';
$13: {brclr dd mm xx}
s:='brclr';
$14: {bset dd mm}
s:='bset';
.
.
.
$de :
s:='ldx';
$df :
s:='stx';
end; {case}
srcStr:=concat(srcStr,s);
FetchByte(s,errFlag,eofFlag);
if not HexStr2Byte(s,operand) then
badHexFlag:=TRUE
else
begin
operandStr:=concat('$',s);
if (opcode in cTwoByteOperSet) or
(opcode in cThreeByteOperSet) then
begin
FetchByte(s,errFlag,eofFlag);
if not HexStr2Byte(s,operand) then
badHexFlag:=TRUE
else
begin
operandStr:=concat(operandStr,
ggcSpace,'$',s);
if opcode in cThreeByteOperSet then
begin
FetchByte(s,errFlag,eofFlag);
if not HexStr2Byte(s,operand) then
badHexFlag:=TRUE
else
operandStr:=concat(operandStr,
ggcSpace,'$',s);
end;
end;
end;
end;
if not badHexFlag then
srcStr:=concat(srcStr,ggcSpace,operandStr)
else
srcStr:=concat(srcStr,ggcSpace,operandStr,
ggcSpace,s,ggcSpace,' - invalid hex byte!');
PutString(srcStr,errFlag);
end;
The opcode is used as the selector in a case statement to assign the proper mnemonic to a string variable; most of the case statement is omitted from the listing to enhance clarity. Next the first operand byte is fetched from the input string and joined to the output string. Additional bytes are fetched for the members of the two and three byte sets and appended. The completed line is then sent to output.
Indexed instructions
Index register instructions are somewhat more complicated in formatting in that they often require inclusion of an "x" or "y" in the output line. Instructions for the "X" register are handled by the code in Listing 9.
Listing 9.
ProcessIndexX
procedure ProcessIndexX(opcode:byte;
var errFlag,eofFlag:boolean);
var
s,operandStr,srcStr:str255;
badHexFlag:boolean;
operand:byte;
const
cTwoByteSet :ggtByteSet=[$1c,$1d];
cThreeByteSet :ggtByteSet=[$1e,$1f];
begin
badHexFlag:=FALSE;
srcStr:=ggcSpace;
case opcode of
$1c: {bset (ind,x) dd}
s:='bset';
$1d: {bclr (ind,x) dd}
s:='bclr';
$1e: {brset (ind,x) xx dd}
s:='brset';
$1f: {brclr dd,x xx dd}
s:='brclr';
$60 :
s:='neg';
$63 :
s:='com';
$64 :
s:='lsr';
$67: {asr (ind,x)}
s:='asr';
$68: {asl (ind,x)}
s:='asl';
.
$ec :
s:='ldd';
$ed :
s:='std';
$ee :
s:='ldx';
$ef :
s:='stx';
end;
srcStr:=concat(srcStr,s);
FetchByte(s,errFlag,eofFlag);
if not HexStr2Byte(s,operand) then
badHexFlag:=TRUE
else
begin
operandStr:=concat('$',s,',x');
if (opcode in cTwoByteSet) or
(opcode in cThreeByteSet) then
begin
FetchByte(s,errFlag,eofFlag);
if not HexStr2Byte(s,operand) then
badHexFlag:=TRUE
else
begin
operandStr:=concat(operandStr,
ggc2Spaces,'$',s);
if opcode in cThreeByteSet then
begin
FetchByte(s,errFlag,eofFlag);
if not HexStr2Byte(s,operand) then
badHexFlag:=TRUE
else
operandStr:=concat(operandStr,
ggcSpace,'$',s);
end;
end;
end;
end;
if not badHexFlag then
srcStr:=concat(srcStr,ggcSpace,operandStr)
else
srcStr:=concat(srcStr,ggcSpace,operandStr,
ggcSpace,s,ggcSpace,' - invalid hex byte!');
PutString(srcStr,errFlag);
end;
The opcode bytes twice
The "post byte" instructions offer a somewhat different form of complexity in that the first thing to be done is fetch the "real" opcode. The two-byte opcodes are made neccessary by the fact that a one-byte opcode can hold only 256 possible values. When Motorola started designing 8-bit processors with more than one index register, they needed more than 256 opcodes. Their solution with processors like the 6809 and 68HC11 is a number of two byte opcodes. In the case of the 68HC11 opcodes whose first bytes are $18, $1A or $CD use a second byte for the actual instruction. With the 68HC11 most post byte instructions use the Y-index register and many directly mirror X-register instructions with the same opcodes, although some affect the D-accumulator.
Initial processing of $1A instructions is shown in Listing 10.
Listing 10
Process1A
procedure Process1A(var errFlag,eofFlag:boolean);
var
s,operandStr,srcStr:str255;
opcode,operand:byte;
badHexFlag:boolean;
begin
badHexFlag:=FALSE;
srcStr:=ggcSpace;
FetchByte(s,errFlag,eofFlag);
if not HexStr2Byte(s,opcode) then
begin
SendBadHexMess(errFlag);
exit;
end;
case opcode of
$83: {cpd #dddd}
s:='cpd';
$93: {cpd dd}
s:='cpd';
$a3: {cpd dd,x}
s:='cpd';
$ac: {cpy dd,x}
s:='cpy';
$b3: {cpd dddd}
s:='cpd';
$ee: {ldy dd,x}
s:='ldy';
$ef: {sty dd,x}
s:='sty';
else
begin
ProcessNotInSets(opcode,errFlag);
exit;
end;
end; {case}
srcStr:=concat(srcStr,s);
FetchByte(s,errFlag,eofFlag);
if not HexStr2Byte(s,operand) then
badHexFlag:=TRUE
else
begin
case opcode of
$83 :
begin
operandStr:=concat('#$',s);
FetchByte(s,errFlag,eofFlag);
if not HexStr2Byte(s,operand) then
badHexFlag:=TRUE
else
operandStr:=concat(operandStr,s);
end;
$b3,$93 :
begin
operandStr:=concat('$',s);
if opcode=$b3 then
begin
FetchByte(s,errFlag,eofFlag);
if not HexStr2Byte(s,operand) then
badHexFlag:=TRUE
else
operandStr:=concat(operandStr,s);
end;
end;
otherwise
operandStr:=concat('$',s,',x');
end;
end;
if not badHexFlag then
srcStr:=concat(srcStr,ggcSpace,operandStr)
else
srcStr:=concat(srcStr,ggcSpace,s,
ggcSpace,' - invalid hex byte!');
PutString(srcStr,errFlag);
end;
The second opcode byte is fetched and converted to numeric form, then used in a case statement to select the instruction mnemonic. Operand bytes are fetched as needed to compose the output string.
The modes not shown
The rest of the addressing modes are handled in a manner similar to these examples with the exception of the relative address mode whose code is shown in Listing 11.
Relatively coding
Except for the "bsr" (branch to subroutine) instruction, whose code is $8d, the opcodes occupy the range $20..$2F. The mnemonics for those sixteen codes are contained in an array of three-character strings indexed by the opcodes. Using a set [$20..$2F] for a qualifier, an array element or the string 'bsr' is copied to string variable s. The single operand byte is then fetched and the output line assembled.
Listing 11.
ProcessRelative
procedure ProcessRelative(opcode:byte;
var errFlag,eofFlag:boolean);
type
tOpStrRay = array [$20..$2f] of ggtString3;
var
s,operandStr,srcStr:str255;
badHexFlag:boolean;
index:integer;
operand:byte;
const
cRelSet :ggtByteSet=[$20..$2f];
cRelCodes :tOpStrRay=('bra','brn','bhi','bls',
'bcc','bcs','bne','beq',
'bvc','bvs','bpl','bmi',
'bge','blt','bgt','ble');
c8dCode :ggtString3='bsr';
begin
badHexFlag:=FALSE;
srcStr:=ggcSpace;
if opcode in cRelSet then
s:=cRelCodes[opcode]
else if opcode=$8d then
s:=c8dCode;
srcStr:=concat(srcStr,s);
FetchByte(s,errFlag,eofFlag);
if not HexStr2Byte(s,operand) then
badHexFlag:=TRUE
else
s:=concat('$',s);
if not badHexFlag then
srcStr:=concat(srcStr,ggcSpace,s)
else
srcStr:=concat(srcStr,ggcSpace,s,
ggcSpace,' - invalid hex byte!');
PutString(srcStr,errFlag);
end;
Jack and Jill went up the hill to fetch a pail of bytes
You may have noticed repeated calls to the FetchByte procedure in the previous code listings. FetchByte, shown in Listing 12, is MacLarry's operating engineer. It retrieves S-records from the input file and chops them up, delivering bytes of data as needed. Available data bytes are held in global string variable gS19Line, which is initialized to an empty string. If its length is zero when we enter the while loop, a new S-record is fetched from the input file. The header is stripped off and if it is "S9" we've reached the end of the file; if the header is neither "S1" nor "S9", the badLineFlag is TRUE - in either case we exit without further ado. If the header was "S1" we strip off the byte count, address and checksum and assign the remaining text, presumed to be bytes of useful data, to the global variable gS19Line. Finally, we chop off a byte of hex data to take home.
If the length of the string gS19Line is greater than zero when we enter the while loop, we simply chop off a byte of data and leave the loop on the next conditional check.
Listing 12.
FetchByte
procedure FetchByte(var s:str255;
var errFlag,eofFlag:boolean);
var
s2,txt :str255;
c :char;
I,numOfBytes :byte;
S9Flag,badLineFlag:boolean;
begin
s:=ggcNullString;
eofFlag:=FALSE;
errFlag:=FALSE;
while (not (eofFlag or errFlag)) and
(s=ggcNullString) do
begin
s:=ggcNullString;
if length(gS19Line)<1 then
begin
FetchLine(txt,eofFlag,errFlag);
if not errFlag then
begin
S9Flag:=FALSE;
StripHeader(txt,badLineFlag,S9Flag);
if S9Flag or badLineFlag then
begin
txt:='';
exit;
end
else
begin
StripByteCount(txt,numOfBytes);
StripAddr(txt);
StripChecksum(txt);
gS19Line:=txt;
end;
end;
end;
{chop off byte to return with}
s:=copy(gS19Line,1,2);
Delete(gS19Line,1,2);
Inc(gByteTotal);
end; {while}
end;
Running MacLarry
When you run MacLarry the window appears and nothing much happens other than the relentless advance of the clock at upper right. The offset and EPROM address values, which both default to zero, are displayed below the time. Use the Address, Offset, and Speed menus to make any needed changes from defaults, then punch the "Go" button. Select the S-record file to be dissected and click "Open". (A sample S-record file, buf32.s19, is provided in the archive.) That's it. You'll see the ASCII scroll by in the top window, followed by successive disassemblies in the big window below.
To continue with dissections, adjust the offset, address and speed values, then punch "Go" to repeat.
Conclusion
While MacLarry might not be an expensive microprocessor development tool, it is a useful utility for poking around inside M68HC11 S-record files. In conjunction with an assembler it can be a useful learning aid for embedded controller assembly language.
The files available for download include CodeWarrior Pro Pascal projects and sourcecode, as well as compiled 68k and PPC apps. Freeware 68HC11 Mac cross-assemblers, S-recordfiles for the BASIC11 interpreter and BUFFALO monitor for dissecting are available at the URLs noted in the references.
Happy dissecting.
References
- Motorola, M68HC11 Reference Manual; lit. ref. #M68HC11RM/AD; the "bible" of the M68HC11 family and a must-have for any serious M68HC11 programmer. In addition, there are many supplementary manuals to document the numerous versions of the microcontroller. Most of the manuals can be downloaded in PDF form from the Motorola M68HC11 documentation page. A CD containing several manuals is also available directly from Motorola at no cost.
- Skroder, John, Using the M68HC11 Microcontroller: A Guide to Interfacing and Programming,. Prentice Hall, 1996; ISBN 0-13-120676-1. Integrated lab/text format.
- F. Driscoll, R. Coughlin and R. Villanucci; Data Acquisition and Process Control with the M68HC11 Microcontroller , Macmillan Publishing Company, 1994, ISBN 0-02-330555-X
- G. H. Miller, Microcomputer Engineering; Prentice Hall, 1993, ISBN 0-13-584475-4. The basics. Assembly language programs. Written for use with the Motorola M68HC11-EVB trainer board.
- P. Spasov; Microcontroller Technology, The 68hc11 ; Prentice Hall, 1996, ISBN 0-13-362724-1; Hardware and software. A good book for M68HC11 novices.
- R. J. Tocci, L. P. Laskowski and F. J. Ambrosio, Microprocessors and Microcomputers: Hardware and Software; Prentice Hall, 1996; ISBN 0-13-235946-4
- J. D. Greenfield, The 68HC11 Microcontroller; Saunders College Publishing, 1992; ISBN 0-03-051588-2
Some of the best documentation for the M68HC11, as well as many versions of both BASIC11 and the BUFFALO monitor, can be downloaded at no charge from Motorola's web site. The following URLs will get you started on this documentation.
One of the best sources of information on embedded systems in general is a monthly magazine called Circuit Cellar <http://www.circuitcellar.com/>, founded by Steve Ciarcia, the popular former Byte contributor and one of the mainstays of that publication during its 1980s prime.
F.C. Kuechmann is a programmer, hardware designer and consultant with degrees from the University of Illinois at Chicago and Clark College. His favorite programming language is Pascal and his favorite programming utility is a large mug of kaffee mocha. He can be reached at fk@aone.com.