I did some classic m68k Amiga code development that uses the SAS C compiler recently (see plip2slip). Everything was set up in a P-UAE-based AmigaOS 3.9 environment and worked fairly confortable. While switching between TextMate on my Mac I used for editing the code and the CLI window in P-UAE I had a thought: “It would be fairly cool to have something like Wine for AmigaOS… Then I’d simply run the SAS C compiler Amiga binary on my Mac directly…”
That was the beginning of my newes project: vamos – The Virtual AMiga OS emulator.
Read on to learn more about the birth of vamos and its first major milestone: run the SAS C compiler in my new “Developer’s Diary” series 😉
If you know Wine then the goal is fairly clear: write a native Mac OS X program that “executes” the Amiga binary, trap all native library calls (to Exec or DOS libary) and replace them with native versions, i.e. an Open() call to Amiga’s dos.library will directly open a file on my Mac.
I want to focus on Exec and Dos library as those provide the functions required for typical command line tools including compilers. I will start with all trapped Exec/Dos libs with empty functions and then step-by-step fill in the functions called by my first target tool: the SAS C compiler…
The thing in vamos that’s more difficult than in Wine is the machine architecture that differs: the Amiga binary is written in m68k code and my Mac runs an Intel x86 based CPU… So the first thing vamos will need is a m68k CPU emulation. I searched the web and found the Musashi CPU emulator.
I wanted to write (the first prototype of) vamos in Python 2.x as it allows to me to quickly get started and let’s me play around with different approaches fairly fast. If everything works out and some portions are slow due to Python scripting I can gradually move them to C later on…
So the first thing was to attach the native m68k CPU emulator to Python. With the help of Python’s ctype module binding the native code was very easy. Now the only thing required to run this virtual CPU is memory. In a first attempt I routed all memory accesss from Musashi to Python and managed memory there, but that was too slow (a 0.2 MHz CPU 🙁 ) and so I moved to a C-based memory implementation. Now a single memory “RAM block” is allocated (e.g. 1 MiB) and accessed by the virtual CPU (200 MHz CPU 🙂 ).
The native Python interface now allows to create the memory, read/write data to the block and control the CPU: read/write its registers and let it run…
Now I was ready for a first experiment: I used my Python HunkReader from my amitools library to load a Amiga LoadSeg()able binary and relocate it into my still empty memory location. Then point the virtual CPU to the first instruction of the first code segment and let the CPU run and boom! To see how far we got I early added extensive memory debugging to have a close look what is fetched from memory… I got as far as the first read to exec base at location 4… so we need some environment to run our binary in vamos!
Setting up the Environment
Ok. We need the exec lib structure in our virtual memory filled with the essential parts, i.e. mainly the jump vectors to the lib functions. And then I need a method to trap the calls so that I know that the virtual CPU will call a library function and can redirect it to my Python code.
In a new experiment I crafted a faked Exec Lib structure in memory and for the call trapping I tried the RESET opcode. Musashi provides a callback whenever this opcode occurrs and I wanted to use this for the lib traps. The idea is to use the RESET callback to enter my Python code then look at the CPU’s PC to find out which entry of the jump vector was selected. In my python code I will emulate the effect of the lib call (or do nothing) and then the next opecode will be executed. I placed an RTS there so the jsr libcall will return to the caller.
So my lib jump table with a trap looks like this (compared to a original Amiga jump):
vamos lib entry: <RESET opcode.w> <RTS opcode.w> <lib_id.w> amiga lib entry: <JMP opcode.w> <func_addr.l>
Both entries have 6 bytes so they are binary compatible… Good. The lib_id word will be used later to identify which lib was trapped…
In my Python code I fetch the PC and then retrieve the library base (here of EXEC) to call the offset of the trapped function. Then I know the function to be called and can emulate it. First thing here was to setup the full exec structure and add traces for all trapped lib calls…
My experiment was done with the Type command of Workbench 3.1… And voila! The code ran until a first call to Exec… Its OpenLibrary!
Stack and Terminate
I almost forgot the stack.. Both PC and initial stack have to be paced at address 0 and 4 before powering up the virtual CPU. PC is the first instruction of my loaded binary. For the stack I simply reserved some static memory region in my RAM block and set the stack pointer…
I can now imagine how running the code would work. But leaving vamos was still an open question: The Amiga binary is called by jsr()ing into the code and it will end with an RTS. So at the end of the code the last value is fetched from the stack and jumped there. So a simple solution for the termination problem was to initialise the stack with a jump to address 0 and there I placed another RESET opcode to trigger my callback. The callback was then extended to watch out for a PC of 0. In this case no lib trap occurred but the program wants to exit. I quit vamos then and termination was solved…
A very simply program only consisting of a empty main() code block was now able to run in my Mac based vamos enviroment! A more complex Amiga program was already able to fetch exec base and to do jumps into exec. Those jumps are trapped but the python code only does logging for now. The exchange of parameters in the trapped calls is done by readingw/writing the “virtual” CPU registers…
All in all I was very happy with these first results and really looked forward to get more Amiga code running on vamos…
To be continued…
That’s it for today… Next time we will have a look on more features of vamos.
If you can’t wait then just head over to the amitools page and download the vamos code there and play around with it… enjoy!
Very neat! With a tiny fix for dos/Args.py (line 113 should be del args), I can run the AmigaE compiler:
MBPro15:amitools pelle$ ./vamos E/Bin/EC
ERROR: reading sourcefile didn`t succeed
22:42:48.042 lib:WARNING: [ exec.library] ? CALL: 132 Forbid( ) from PC=00798c -> d0=0 (default)
22:42:48.042 lib:WARNING: [ exec.library] ? CALL: 138 Permit( ) from PC=00799c -> d0=0 (default)
I can even compile a small program:
MBPro15:amitools pelle$ ./vamos E/Bin/EC HelloWorld.e
22:45:38.247 lib:WARNING: [ exec.library] ? CALL: 132 Forbid( ) from PC=007904 -> d0=0 (default)
22:45:38.247 lib:WARNING: [ exec.library] ? CALL: 390 FindPort( name[a1]=7794 ) from PC=00790c -> d0=0 (default)
22:45:38.247 lib:WARNING: [ exec.library] ? CALL: 354 AddPort( port[a1]=4b47c ) from PC=007956 -> d0=0 (default)
22:45:38.248 lib:WARNING: [ exec.library] ? CALL: 138 Permit( ) from PC=007982 -> d0=0 (default)
lexical analysing …
parsing and compiling …
22:45:38.251 lib:WARNING: [ exec.library] ? CALL: 132 Forbid( ) from PC=00798c -> d0=0 (default)
22:45:38.251 lib:WARNING: [ exec.library] ? CALL: 138 Permit( ) from PC=00799c -> d0=0 (default)
MBPro15:amitools pelle$ file helloworld
helloworld: AmigaOS loadseg()ble executable/binary
Nice to hear that it works for other compilers, too!
Thanks for the patch! Applied it.
Pingback: Vamos â€“ The Virtual AMiga OS emulator | Vintage is the New Old