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.