Amiga Asm: Simple ‘Hello World’ (CLI Output)

The aim is to write assembler to output the text “Hello World” to the shell window from which it is launched. This will use the Amiga OS libraries rather than to co-processor, somewhat easier as we don’t have to deal with creating copper lists and bitplanes (that will come later).

Libary’s

Utilising the libraries of the operating system will perform all the heavy lifting of the graphical output of the text to be displayed. We need to remember that text output to a screen is actually graphical, mearly pixels streamed to the screen. These libraries are available via the internal kickstart chip, the Amiga ROM.

System Base and System Library

The main kernel Library (exec.library) is accessed by a dynamic pointer that points to the location of the library in memory, it has to be dynamic as it will differ between Amiga models, OS versions and even on reboot. So how do we access it? Well there is a fixed memory address where the pointer is stored and that is $00000004 known as ‘system base’. Below this address towards higher memory resides its data and above resides a jump table that holds pointers to the functions themselves.

The library functions have arguments that have to be placed into data registers. The details of which registers are described in its documentation (autodocs). To call these functions the system base memory address is placed into an address register, say Address Register 6 (A6). We then use the JSR command which is passed a byte offset to desired command in the jump table followed by the address register in brackets i.e. JSR  -552(A6) will jump to the -552 bytes away from the address in register A6

For ‘Hello world’ three library calls are needed. We need to use two from the kernel library to open and close the dos library. One one from the dos library itself to output our text. Within the kernel library the OpenLibrary pointer to the function resides -552 from base, and CloseLibrary at -414.

example 1: Note:The result from OpenLibray will be placed in data register zero.

ExecBase = 4                  ;location of the exec.lib
OpenLib = -552 ;offset to the openLibrary function
OpenLibVersion = 34 ;minimum version to use
MOVE.L #OpenLibVersion,D0 ;place version of lib in data reg 0
LEA    DosName,A1 ;place dos.lib name into add reg 1
 MOVE.L ExecBase,A6 ;point to exec.lib's jump table
 JSR    OpenLib(A6) ;make the jumpfrom exec.lib jump table
DosName: DC.B "dos.library",0 ;zero-terminated ASCII lib name

In high level language would be something like the following

let pointerToDosLibrary = ExecLib.openLibrary(34,"dos.library");

Where ‘ExecLib’ is a static class containing a function called openLibrary that requires the arguments version number and the library name to open. This will return either an error code or a pointer to the memory address of the library itself.

Utilising the DOS Libary

The DOS libraries have a number of functions including one that will output an ascii string to the cli. In versions 30 + of the library (hence passing the version number) lies that function that we need, PutStr(). The instruction pointer for this function is at -948 bytes from the dos library’s base address within its jump table.

Once the dos library has loaded a check needs to made to make sure that everything is OK. The jump to the exec.library openLibrary will open the dos library and store a pointer in the form of a memory address in Data Register 0 (D0). If this fails i.e. a versions of library equal to or above to what has been specified is not present then the D0 register will be zero rather than a memory address. It’s best practice to check the register and branch if the library has failed to load even if we are certain the correct library version are available. The new lines are in bold.

ExecBase = 4                  ;location of the exec.lib
OpenLib = -552 ;offset to the openLibrary function
OpenLibVersion = 34 ;minimum version to use
MOVE.L #OpenLibVersion,D0 ;place version of lib in data reg 0
LEA    DosName,A1 ;place dos.lib name into add reg 1
 MOVE.L ExecBase,A6 ;point to exec.lib's jump table
 JSR    OpenLib(A6) ;make the jumpfrom exec.lib jump table
TST.L D0 ;check to see if D0 equals zero (fail)
BEQ.B NoLibError ;branch to the NoLibError routinue

NoLibError: CLR.L D0 ;clear the register to zero
RTS ;quit

DosName: DC.B "dos.library",0 ;zero-terminated ASCII lib nam

The pointer to the dos library base address is now stored in D0, this is moved to an address register (as it’s an address) so we reuse A6. The PutString call requires the data to be display in the cli to be moved into Data Register 1 (D1). Now the dos library function ‘PutStr’ can be executed. By jumping to the dos library base memory address + the offset to the PutStr pointer stored in the jump table ( -948) we execute the call. Again the new additions are in bold.

ExecBase = 4                  ;location of the exec.lib
OpenLib = -552 ;offset to the openLibrary function
OpenLibVersion = 34 ;minimum version to use
PutString = -948 ;offset to ptrStr() function
MOVE.L #OpenLibVersion,D0 ;place version of lib in data reg 0
LEA    DosName,A1 ;place dos.lib name into add reg 1
 MOVE.L ExecBase,A6 ;point to exec.lib's jump table
 JSR    OpenLib(A6) ;make the jump from exec.lib jump table
TST.L D0 ;check to see if D0 equals zero (fail)
BEQ.B NoLibError ;branch to the NoLibError routinue
MOVE.L D0,A6 ;addr reg 6 = base addr of dos lib
MOVE.L #DisplayString,D1 ;data reg 1 = 'Hello World'
JSR PutString(A6) ;make the jump from dos.lib jump table

NoLibError: CLR.L D0 ;clear the register to zero
RTS ;quit
DosName: DC.B "dos.library",0 ;zero-terminated ASCII lib name
DisplayString: DC.B "Hello World",10,0

Close Down and Clean Up

We now have sent the output to the display. Now time to clean up by closing the dos library. Obviously the exec library is our main kernel library so shouldn’t be closed. Close library requires the address of the library to close in the A1 register.

ExecBase = 4                  ;location of the exec.lib
OpenLib = -552 ;offset to the openLibrary function
OpenLibVersion = 34 ;minimum version to use
CloseLib = -414 ;offset to closeLibrary function
PutString = -948 ;offset to putStr() function
MOVE.L #OpenLibVersion,D0 ;place version of lib in data reg 0
LEA    DosName,A1 ;place dos.lib name into add reg 1
 MOVE.L ExecBase,A6 ;point to exec.lib's jump table
 JSR    OpenLib(A6) ;make the jump from exec.lib jump table
TST.L D0 ;check to see if D0 equals zero (fail)
BEQ.B NoLibError ;branch to the NoLibError routinue
MOVE.L D0,A6 ;addr reg 6 = base addr of dos lib
MOVE.L #DisplayString,D1 ;data reg 1 = 'Hello World'
JSR PutString(A6) ;make the jump from dos.lib jump table
MOVE.L A6,A1 ;base addr of dos lib moved to addr reg 1
MOVE.L ExecBase,A6 ;exec.lib addr move to addr reg 6
JSR    CloseLib(A6)
;make jump from the exec.lib jump table
NoLibError: CLR.L D0 ;clear the register to zero
RTS ;quit
DosName: DC.B "dos.library",0 ;zero-terminated ASCII lib name
DisplayString: DC.B "Hello World",10,

Compile and Run

Now we can assemble with Vasm or another assembler:

C:\Users\mangojellysolutions\vbcc\source> 
vasmm68k_mot -kick1hunks -Fhunkexe -o helloworld -nosym "helloworld.asm"

Remember that this is a cli program only so must be run via the shell. It will crash if you run it straight from the Amiga workbench as it is missing the necessary start up code. Copy the output binary to the Amiga’s hard drive, open up and shell window and execute the file.

That’s it! We have successfully created ‘hello world’ in Amiga 68000 assembler.

Be the first to comment

Leave a Reply

Your email address will not be published.


*