Development
The preferred way to develop software for the 2650 microprocessor is to use the original tools, on the original hardware. This is not always possible, especially when the original media have been lost and the software has to be recreated. And sometimes a modern computer with modern editors and other tools is just more convenient.
One of the main tools is an assembler. I created asm2650 to offer the best features of available assemblers. For TWIN users the Signetics assemblers are of interest.
I also created several utilities, mostly scripts for MacOS but they should work with no or little modification on other Unix-like systems. And — who knows — perhaps on Windows as well.
Most systems offer some kind of debugging facility, ranging from simple but effective to complex and powerful.
While developing programs I encountered some common programming patterns. I collected them as 2650 assembler patterns.
Reverse-engineering
I often come across software that is available only in binary form: a sequence of bytes containing 2650 opcodes. Reverse engineering is the task of transforming that binary code into text that
- is understandable (for a person who has sufficient knowledge of the 2650 assembly language);
- can be fed through an assembler, and will generate the same output as the original binary file;
- with as little effort as possible.
I have invented a work flow that works for me. It may not be the best way, or the preferred way for others, but it has given me useful results.
Tools
I work on a Macbook and I am used to Unix-style tools and scripts, so that is what I use. Shell scripts and sed-files are important tools. sed
is a complex tool, but I basically only use one function (see below).
The core tool is actually a Windows program: DASMx. DASMx is a disassembler, which performs most of the work of translating binary code into assembly language. Since macOS cannot run Windows software directly, I use Crossover, which includes the Wine layer.
A good text editor is essential. I use BBEdit. BBEdit has several features that I cannot do without: grouping files into projects and collections, extremely powerful find & replace and syntax highlighting for the 2650 language.
One last useful utility is called entr
. It takes a list of files and executes a script when any one of this files changes.
Process
The first step is to make sure that the binary file is “clean”. Any header information should be removed and only the bytes that would be loaded into memory should remain. For example, TWIN modules have a 128-byte header that should be stripped; pgm-files start from memory address zero, and all bytes leading up to the actual base address should be removed.
This will provide you with a binary that can be handled by DASMx. Create a basic symbol file for DASMx to work with. Let’s assume that the binary is called test.bin
; create a file test.sym
like this:
org 0x1500
code 0x1500
Assuming that the code is supposed to load from address 1500 onwards, and that the first byte is also the entry point.
You then invoke DASMx with …
dasmx -t -c2650 test.bin
… to create the file test.lst
, which will be the basis on which you continue reverse engineering. For this there are no simple rules to follow. Just try to read the code, and try to understand its structure.
DASMx creates labels for data fields that start with the letter “X” followed by the hexadecimal address of the data field. Code labels start with the letter “L” followed by the code address.
DASMx does a decent job of discovering all executable code, but sometimes it needs a hint to keep going. For example, several routines in TWIN DOS are called with R2 and R3 containing the return/continuation address. DASMx will produce something like
lodi,r2 h'16'
lodi,r3 h'1a'
bcta,un L025E
Since DASMx cannot discover that address h’161a’ is actually a code entry point you need to add this information to the sym-file:
org 0x1500
code 0x1500
code 0x161a
and rerun DASMx again. Another common pattern is a loop like this
lodi,r1 h'06'
L0314 loda,r0 X0515,r1,-
stra,r0 X06BE,r1
brnr,r1 L0314
where address h’0515′ contains a recognisable six-letter string. This information is added to the sym-file as well:
org 0x1500
code 0x1500
code 0x161a
string 0x0515 MyString 6
Re-run the disassembler again. Step by step the resulting list-file will take shape. In addition to strings, you will encounter single-byte variables, two-byte variables and addresses, and more. See the manual for DASMx for information on how to specify these data structures in the sym-file.
The labels “L” and “X” followed by hexadecimal are not very readable. In the sym-file you can use symbolic names to make the output more understandable and readable. Sometimes that is not enough, and I use sed
to complete the job. Basically, a sed-file is a list of search-and-replace patterns that look like this:
s/L1972/DefaultDest/
s/L194F/DoAssignDest/
s/L195E/AssignSuccess/
s/X1A13/SRBGetParam+srbData/
s/X1A33/DebugTracePkg/
This is especially useful for 2-byte values. In the sym-file you write
word 0x1234 MyAddress
while in the sed-file (named test.sed
) you write
s/X1235/MyAddress+1/
DASMx knows not to use label X1234 but to use MyAddress instead. However, it is not smart enough to replace X1235 by MyAddress+1. The sed-file will take care of that.
Invoke the sed-file with the command
sed -i .bak -f test.sed test.lst
This will backup the file test.lst as test.lst.bak, and then replace all the labels by their readable equivalent.
I usually combine the DASMx and sed commands in a shell script called reverse_engineer.sh, and then invoke that using entr as follows.
ls -1 *.sed *.sym | entr ./reverse_engineer.sh
This you can keep running in a terminal window. Whenever you save one of the files in your text editor, the script is automatically run to update the list file.
Once the bulk of the structure has been cleared up, run DASMx one more time with the -a
option, to create an assembler source file. From there, using a good text editor, you can clean things up, add comments and documentation, etc.
Example
To see how this would work together, here is a partial reverse-engineering of the TWIN module “J
” that executes the commands DEVICE, KILL, SEARCH and TYPE.
sym-file
; DOS API
;include ../tos_inc.sym
code 0x2000 API_SupervisorCall
code 0x2012 API_QueueErrorR0R1
byte 0x2021 SystemDrive
byte 0x202f FlagSearch
word 0x2046 _Str_CurrSlaveCmd
org 0x1880
code 0x1880 StartType
code 0x1882 StartKill
code 0x1884 StartDevice
code 0x1886 StartSearch
word 0x18DE _SystemDrive
byte 0x18C0 moduleNum
byte 0x18C1 moduleOffset
code 0x18c2 BXAJmpType
code 0x18c5 BXAJmpKill
code 0x18c8 BXAJmpDevice
code 0x18cb BXAJmpSearch
word 0x18DC _FlagSearch
string 0x1903 SRBGetParam1 6
word 0x1909 GetParam1Buf
vector 0x190b GetParam1Jmp
code 0x1919 GetParam1Done
string 0x1987 SRBGetParam2 6
word 0x198d GetParam2Buf
vector 0x198f GetParam2Jmp
code 0x1991 GetParam2Done
string 0x190d Param 12
string 0x1963 SRBAbort 1
sed-file
# Common constants ################
#
s/H'0D'/H'0D'\t?=Carriage Return/
# SRBs
#
s/lodi,r2\tH'19'/lodi,r2\tH'19'\t?=<SRB.../
s/lodi,r3\tH'03'/lodi,r3\tH'03'\t?=>SRBGetParam1/
s/lodi,r3\tH'87'/lodi,r3\tH'87'\t?=>SRBGetParam2/
s/lodi,r3\tH'63'/lodi,r3\tH'63'\t?=>SRBAbort/
# Addresses of routines and subroutines
#
s/L188B/TypeInit/
s/L1891/KillInit/
s/L1897/DeviceInit/
s/L189D/SearchInit/
s/L18A1/DoInit/
s/L1954/ErrorInvParam/
s/L1956/ErrorExit/
s/L195C/AbortExit/
s/L1964/GoType/
s/L1969/GoKill/
s/L19C2/GoSearch/
s/L19CC/SearchParam2/
resulting lst-file
;
; Disassembled by:
; DASMx object code disassembler
; (c) Copyright 1996-2003 Conquest Consultants
; Version 1.40 (Oct 18 2003)
;
; File: devKillSearchType.bin
;
; Size: 386 bytes
; Checksum: 5EF3
; CRC-32: 4EA5CD76
;
; Date: Sun Oct 06 18:07:59 2024
;
; CPU: Signetics 2650 (2650 family)
;
;
;
org H'1880'
;
1880 StartType:
1880 : 1B 09 " " bctr,un TypeInit
1882 StartKill:
1882 : 1B 0D " " bctr,un KillInit
1884 StartDevice:
1884 : 1B 11 " " bctr,un DeviceInit
1886 StartSearch:
1886 : 1B 15 " " bctr,un SearchInit
;
1888 : 1F 19 5C " \" db H'1F', H'19', H'5C'
;
188B TypeInit:
188B : 07 00 " " lodi,r3 H'00'
188D : 04 48 " H" lodi,r0 H'48'
188F : 1B 10 " " bctr,un DoInit
;
1891 KillInit:
1891 : 07 03 " " lodi,r3 H'03' ?=>SRBGetParam1
1893 : 04 4B " K" lodi,r0 H'4B'
1895 : 1B 0A " " bctr,un DoInit
;
1897 DeviceInit:
1897 : 07 06 " " lodi,r3 H'06'
1899 : 04 27 " '" lodi,r0 H'27'
189B : 1B 04 " " bctr,un DoInit
;
189D SearchInit:
189D : 07 09 " " lodi,r3 H'09'
189F : 04 54 " T" lodi,r0 H'54'
18A1 DoInit:
18A1 : CB 1D " " strr,r3 moduleNum
18A3 : C8 1C " " strr,r0 moduleOffset
18A5 : 05 FF " " lodi,r1 H'FF'
18A7 L18A7:
18A7 : 0D B8 D6 " " loda,r0 *X18D6,r1,+
18AA : CD 78 E0 " x " stra,r0 X18E0,r1
18AD : E5 13 " " comi,r1 H'13'
18AF : 98 76 " v" bcfr,eq L18A7
18B1 : 05 FF " " lodi,r1 H'FF'
18B3 L18B3:
18B3 : 0D B8 D8 " " loda,r0 *X18D8,r1,+
18B6 : CD 78 F4 " x " stra,r0 X18F4,r1
18B9 : E5 07 " " comi,r1 H'07'
18BB : 98 76 " v" bcfr,eq L18B3
18BD : 1F 18 FC " " bcta,un L18FC
;
18C0 moduleNum:
18C0 : 00 " " db H'00'
18C1 moduleOffset:
18C1 : 00 " " db H'00'
;
18C2 BXAJmpType:
18C2 : 1F 19 64 " d" bcta,un GoType
18C5 BXAJmpKill:
18C5 : 1F 19 69 " i" bcta,un GoKill
18C8 BXAJmpDevice:
18C8 : 1F 19 5C " \" bcta,un AbortExit
18CB BXAJmpSearch:
18CB : 1F 19 C2 " " bcta,un GoSearch
;
18CE : 20 50 20 54 " P T" db H'20', H'50', H'20', H'54'
18D2 : 20 68 20 6C " h l" db H'20', H'68', H'20', H'6C'
18D6 X18D6:
18D6 : 10 60 " `" dw X1060
18D8 X18D8:
18D8 : 20 46 " F" dw _Str_CurrSlaveCmd
18DA : 20 24 " $" db H'20', H'24'
18DC _FlagSearch:
18DC : 20 2F " /" dw FlagSearch
18DE _SystemDrive:
18DE : 20 21 " !" dw SystemDrive
18E0 X18E0:
18E0 : 00 00 00 00 " " db H'00', H'00', H'00', H'00'
18E4 : 00 00 00 00 " " db H'00', H'00', H'00', H'00'
18E8 : 00 00 00 00 " " db H'00', H'00', H'00', H'00'
18EC X18EC:
18EC : 00 00 " " dw X0000
18EE X18EE:
18EE : 00 00 " " dw X0000
18F0 : 00 00 00 00 " " db H'00', H'00', H'00', H'00'
18F4 X18F4:
18F4 : 00 00 00 00 " " db H'00', H'00', H'00', H'00'
18F8 : 00 00 " " db H'00', H'00'
18FA X18FA:
18FA : 00 00 " " dw X0000
;
18FC L18FC:
18FC : 06 19 " " lodi,r2 H'19' ?=<SRB...
18FE : 07 03 " " lodi,r3 H'03' ?=>SRBGetParam1
1900 : 1F 20 00 " " bcta,un API_SupervisorCall
;
1903 SRBGetParam1:
1903 : 13 00 00 01 " " db "\x13\x00\x00\x01\x00\x12"
00 12 " "
1909 GetParam1Buf:
1909 : 19 0D " " dw Param
190B GetParam1Jmp:
190B : 19 19 " " dw GetParam1Done
190D Param:
190D : 00 00 00 00 " " db "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
00 00 00 00 " "
00 00 00 00 " "
;
1919 GetParam1Done:
1919 : 05 1F " " lodi,r1 H'1F'
191B : 0C 19 0D " " loda,r0 Param
191E : 1E 19 56 " V" bcta,lt ErrorExit
1921 : 0D 18 C0 " " loda,r1 moduleNum
1924 : E5 06 " " comi,r1 H'06'
1926 : 1C 19 6F " o" bcta,eq L196F
1929 : E4 4F " O" comi,r0 H'4F'
192B : 98 27 " '" bcfr,eq ErrorInvParam
192D : 05 FF " " lodi,r1 H'FF'
192F : 06 01 " " lodi,r2 H'01'
1931 : 0E 79 0D " y " loda,r0 Param,r2
1934 : E4 46 " F" comi,r0 H'46'
1936 : 18 13 " " bctr,eq L194B
1938 : 05 00 " " lodi,r1 H'00'
193A : E4 4E " N" comi,r0 H'4E'
193C : 98 16 " " bcfr,eq ErrorInvParam
193E L193E:
193E : 0E 39 0D " 9 " loda,r0 Param,r2,+
1941 L1941:
1941 : E4 0D " " comi,r0 H'0D' ?=Carriage Return
1943 : 98 0F " " bcfr,eq ErrorInvParam
1945 : 0F 18 C0 " " loda,r3 moduleNum
1948 : 9F 18 C2 " " bxa BXAJmpType
;
194B L194B:
194B : 0E 39 0D " 9 " loda,r0 Param,r2,+
194E : E4 46 " F" comi,r0 H'46'
1950 : 98 6F " o" bcfr,eq L1941
1952 : 1B 6A " j" bctr,un L193E
;
1954 ErrorInvParam:
1954 : 05 1E " " lodi,r1 H'1E'
1956 ErrorExit:
1956 : 0C 18 C1 " " loda,r0 moduleOffset
1959 : 3F 20 12 "? " bsta,un API_QueueErrorR0R1
195C AbortExit:
195C : 06 19 " " lodi,r2 H'19' ?=<SRB...
195E : 07 63 " c" lodi,r3 H'63' ?=>SRBAbort
1960 : 1F 20 00 " " bcta,un API_SupervisorCall
;
1963 SRBAbort:
1963 : 1F " " db "\x1F"
;
1964 GoType:
1964 : CD 98 FA " " stra,r1 *X18FA
1967 : 1B 03 " " bctr,un L196C
;
1969 GoKill:
1969 : CD 98 EE " " stra,r1 *X18EE
196C L196C:
196C : 1F 19 5C " \" bcta,un AbortExit
;
196F L196F:
196F : 06 19 " " lodi,r2 H'19' ?=<SRB...
1971 : 07 0D " " lodi,r3 H'0D' ?=Carriage Return
1973 : 3F 20 15 "? " bsta,un L2015
1976 : 05 34 " 4" lodi,r1 H'34'
1978 : E6 0E " " comi,r2 H'0E'
197A : 1D 19 56 " V" bcta,gt ErrorExit
197D : CE 19 C1 " " stra,r2 X19C1
1980 L1980:
1980 : 06 19 " " lodi,r2 H'19' ?=<SRB...
1982 : 07 87 " " lodi,r3 H'87' ?=>SRBGetParam2 ?=>SRBDelProcedure
1984 : 1F 20 00 " " bcta,un API_SupervisorCall
;
1987 SRBGetParam2:
1987 : 13 00 00 02 " " db "\x13\x00\x00\x02\x00\x06"
00 06 " "
198D GetParam2Buf:
198D : 19 0D " " dw Param
198F GetParam2Jmp:
198F : 19 91 " " dw GetParam2Done
;
1991 GetParam2Done:
1991 : 0C 18 C0 " " loda,r0 moduleNum
1994 : E4 09 " " comi,r0 H'09'
1996 : 1C 19 CC " " bcta,eq SearchParam2
1999 : 05 1E " " lodi,r1 H'1E'
199B : 06 00 " " lodi,r2 H'00'
199D : 0C 19 0D " " loda,r0 Param
19A0 : E4 55 " U" comi,r0 H'55'
19A2 : 18 07 " " bctr,eq L19AB
19A4 : 06 80 " " lodi,r2 H'80'
19A6 : E4 44 " D" comi,r0 H'44'
19A8 : 9C 19 56 " V" bcfa,eq ErrorExit
19AB L19AB:
19AB : 0C 19 0E " " loda,r0 X190E
19AE : E4 0D " " comi,r0 H'0D' ?=Carriage Return
19B0 : 9C 19 56 " V" bcfa,eq ErrorExit
19B3 : 0B 0C " " lodr,r3 X19C1
19B5 : 0F F8 EC " " loda,r0 *X18EC,r3
19B8 : 44 7F "D " andi,r0 H'7F'
19BA : 62 "b" iorz r2
19BB : CF F8 EC " " stra,r0 *X18EC,r3
19BE : 1F 19 5C " \" bcta,un AbortExit
;
19C1 X19C1:
19C1 : 00 " " db H'00'
;
19C2 GoSearch:
19C2 : 04 08 " " lodi,r0 H'08'
19C4 : 25 FF "% " eori,r1 H'FF'
19C6 : 1C 19 F9 " " bcta,eq L19F9
19C9 : 1F 19 80 " " bcta,un L1980
;
19CC SearchParam2:
19CC : 0E 98 DE " " loda,r2 *_SystemDrive
19CF : 0C 19 0D " " loda,r0 Param
19D2 : 9A 0B " " bcfr,lt L19DF
19D4 : 05 1F " " lodi,r1 H'1F'
19D6 : E6 01 " " comi,r2 H'01'
19D8 : 1D 19 56 " V" bcta,gt ErrorExit
19DB : 04 02 " " lodi,r0 H'02'
19DD : 1B 18 " " bctr,un L19F7
;
19DF L19DF:
19DF : 0D 19 0E " " loda,r1 X190E
19E2 : E5 0D " " comi,r1 H'0D' ?=Carriage Return
19E4 : 9C 19 54 " T" bcfa,eq ErrorInvParam
19E7 : A4 30 " 0" subi,r0 H'30'
19E9 : E4 08 " " comi,r0 H'08'
19EB : 1D 19 54 " T" bcta,gt ErrorInvParam
19EE : E4 02 " " comi,r0 H'02'
19F0 : 1E 19 54 " T" bcta,lt ErrorInvParam
19F3 : E2 " " comz r2
19F4 : 9D 19 54 " T" bcfa,gt ErrorInvParam
19F7 L19F7:
19F7 : 05 FF " " lodi,r1 H'FF'
19F9 L19F9:
19F9 : CC 90 C2 " " stra,r0 *X10C2
19FC : CD 98 DC " " stra,r1 *_FlagSearch
19FF : 1F 19 5C " \" bcta,un AbortExit
;--------------------------------------------------------------
Symbol table
============
Value Type Name
----- ---- ----
0000 Data X0000
1060 Data X1060
10C2 Data X10C2
1880 Code StartType
1882 Code StartKill
1884 Code StartDevice
1886 Code StartSearch
188B Code TypeInit
1891 Code KillInit
1897 Code DeviceInit
189D Code SearchInit
18A1 Code DoInit
18A7 Code L18A7
18B3 Code L18B3
18C0 Data moduleNum
18C1 Data moduleOffset
18C2 Code BXAJmpType
18C5 Code BXAJmpKill
18C8 Code BXAJmpDevice
18CB Code BXAJmpSearch
18D6 Data X18D6
18D8 Data X18D8
18DC Data _FlagSearch
18DE Data _SystemDrive
18E0 Data X18E0
18EC Data X18EC
18EE Data X18EE
18F4 Data X18F4
18FA Data X18FA
18FC Code L18FC
1903 Data SRBGetParam1
1909 Data GetParam1Buf
190B Data GetParam1Jmp
190D Data Param
190E Data X190E
1919 Code GetParam1Done
193E Code L193E
1941 Code L1941
194B Code L194B
1954 Code ErrorInvParam
1956 Code ErrorExit
195C Code AbortExit
1963 Data SRBAbort
1964 Code GoType
1969 Code GoKill
196C Code L196C
196F Code L196F
1980 Code L1980
1987 Data SRBGetParam2
198D Data GetParam2Buf
198F Data GetParam2Jmp
1991 Code GetParam2Done
19AB Code L19AB
19C1 Data X19C1
19C2 Code GoSearch
19CC Code SearchParam2
19DF Code L19DF
19F7 Code L19F7
19F9 Code L19F9
2000 Code API_SupervisorCall
2012 Code API_QueueErrorR0R1
2015 Code L2015
2021 Data SystemDrive
202F Data FlagSearch
2046 Data _Str_CurrSlaveCmd
Number of symbols: 65
;--------------------------------------------------------------
Useful tools:
asm2650: my assembler, with many features to make development easier.
DASMx: an incredibly useful tool when reverse-engineering binary software for which sources are missing.
VACS: for those who use Windows this apprears to be their assembler of choice.
Also see the Hardware page for links to hardware emulators. These are useful when debugging software.