Hi...
I've been messing around with Glass for some months. I've started to get the gist of it, being it the very first Z80 assembler I've used somewhat seriously.I'm liking it a lot, its cross compiling capabilities are fantastic, etc, etc... I'm using the latest "development version" .jar file. So far, all good.
My doubt will be explained after giving some context:
Lately my MSX needs have started to move into a recognizable pattern. I'm using some memory managment routines/libraries I'm starting to create, that live and are defined inside ROM (page 1) and that need to live in RAM (page 2 or 3, doesn't matter) to really be useful for my needs.
What does it mean? I don't really know how/where to write them inside my .asm file for them to be deployed onto RAM (automatically or manually).
Well I know how to do it manually, I'm somewhat managing to do it, but I'm somewhat lost among the different compiling dierctives of GLASS, as I'd like to know how to do it if I want my routines to be somewhat relocatable.
Typical thing I do: declare my routine in rom (page 1).
org 4000 RAM_ADDRESS: equ 0F000H ld hl,routine ld de, RAM_ADDRESS ld bc, end_routine - routine ldir jp RAM_ADDRESS routine: ld ld ld ld ld end_routine:
With this approach I deploy my rom defined routine onto RAM F000, which is what I want, but I'd like to know how to do it in the case my routine needs to have labels and conditional jumps inside (something that will happen whenever it becomes a bit more complex)... As far as I understand, relocating my routine onto RAM, would break all jumps, as they're defined onto .rom space inside my .asm file? Am I right?
Or even better I'd like to deploy my routines onto a RUNTIME calculated RAM address, instead of a compilation time defined one. Can Glass be of any help for this kind of dynamic deployment (I think) I need?
I've read the SECTION part of Glass manual but it's still unclear if it can serve my needs.
I've also come accross defining different org directives around my code, for the compiler to decide the deployment/compiling address... But then, does Glass "deploy" my rom defined routines onto ram automatically? Can't see how...
So here I am, asking what's the typical approach when dealing with a situation like this:
how to define a relocatable routine in rom (or at least in my asm file), that will live onto some defined|dynamic ram address on runtime, after I copy its code onto RAM?
I hope I've somewhat explained myself. Thanks in advance and kudos to Grauw for this fantastic small and compact assembler.
Greetings.
Isn't it a matter of using jr instead of jp inside the relocatable routine?
You can use jr if it fits your routine lenght. Else, if you already know the address you want to copy your routine, you can hardcode the jump addresses. Simply assemble your routine in a .bin file accordignly ORGed and copy labels' addresses from the symbol file...
I'm dealing with 2 or 3 routines as of now, but sadly there's one of them which is big enough to start needing JP's instead of JR's... That's my point...
I'd like not to depend on hardcoded approaches (as I manually do now with EQU's and so), as the ideas I have in mind would like to allocate dinamically these routines.
Something like this:
ld hl, 0xf100 ; dest address for my routine (decision made at runtime, not at compilation time) call deploy_routine_onto_hl_address "call hl" ; I have some tricky routine to do this.
I'll study thegeps approach to see if it fits my automatic Makefile process to build my programs... Unsure about that... It will be later today anyway.
Thanks!
Well, then you can set a lookup table, based of a pre assembled routine, containing the labels' offsets, that you can add to your start address and "fix" jump addresses after allocation...
Call HL:
call JumpHL ; .... JumpHL: jp hl JumpDE: push de ret JumpBC: push bc ret JumpIX: jp ix JumpIY: jp iy
Relocatable to a static address is easy, use org
to set the address of the RAM section to the destination address, like:
ld hl,RAM_sourceAddress ld de,RAM_startAddress ld bc,RAM_endAddress - RAM_startAddress ldir ; ... RAM_sourceAddress: org 0C000H RAM_startAddress: ; ... RAM stuff here RAM_endAddress: org RAM_sourceAddress + (RAM_endAddress - RAM_startAddress)
This code is not specific to Glass by the way, it works like that in any assembler. And yes you need to take care of copying it yourself, there is no “runtime” which does it for you.
With Glass you can use the SECTION
directive to simplify your life a bit, because stuff that needs to be in RAM does not need to be defined in a single place:
ld hl,RAM_sourceAddress ld de,RAM_startAddress ld bc,RAM_endAddress - RAM_startAddress ldir ; ... RAM_sourceAddress: org 0C000H RAM_startAddress: RAM: ds 2000H RAM_endAddress: org RAM_sourceAddress + (RAM_endAddress - RAM_startAddress) ; ... and elsewhere have several SECTION RAM ; any code in a RAM section ends up in the DS above. ENDS
As for relocation to a dynamic location, that is more difficult unless you use jr
only. You need to track all address pointers and rewrite them dynamically. One way to do so is to store two copies of the RAM routines at different addresses (so e.g. INCLUDE it twice), and then as you copy it to upper memory compare the bytes from the two copies. If they are different, then it is a relocated address pointer which you need to adjust appropriately.
How many hints and proofs of concept!
As I can confirm reading you replies, many assumptions I already made were true (mainly: a big routine is not being easily realocatable if it needs JP's inside)...
Otherwise I'm sure I'll manage to do something with that info. Thanks!
So from an assembler perspective what could improve here is that it could support generating relocatable object files and linking. Then you could have a script generate a relocation table and link it and the object file against a runtime. That’s not what the assembler is set up for at the moment though. Also it places restrictions on the mathematical operations you can do on addresses in the assembly code.
What you can do to simulate that approach though, is to define labels with a particular prefix at every place where you have a reference to an address that is relocated. Like call Xxx
, REL_callingXxx: equ $ - 2
. Then use a script to generate a table of those from the symbol file generated by the assembler, and use that in your source as a list of addresses to rewrite.
But I think the two-copies-at-different-addresses approach is easier. And of course relocating to a static address rather than a dynamic one is the easiest.
Here's an example of relocatable code using a fixup (relocation) table. It needs a known address which has RAM and two bytes free. I've used 0FB36h (SAVSP, used only for the PLAY instruction) for this purpose.
TwoBytesFree equ 0FB36h ld hl,0E9E1h ; opcodes for POP HL / JP (HL) ld (TwoBytesFree),hl call TwoBytesFree Ofs: ld bc,-Ofs ; subtract real address add hl,bc ld b,h ld c,l ld hl,FixupTable ; get address of fixup table rel. to 0 add hl,bc ; fix up this address itself ex de,hl NextFixup: ld a,(de) inc de ld l,a ; Grab next entry into HL ld a,(de) ld h,a or l ; A zero indicates end of fixup table jr z,FixupEnd inc de add hl,bc ; Add correct offset ld a,(hl) ; Fixup by adding BC to (HL) add a,c ld (hl),a inc hl ld a,(hl) adc a,b ld (hl),a jr NextFixup FixupEnd: ; Let's test it! ld hl,TextOfs Fixup1 equ $-2 LoopPrint: ld a,(hl) inc hl or a ret z rst 18h jp LoopPrint Fixup2 equ $-2 TextOfs: db "It worked!" db 0 FixupTable: dw Fixup1, Fixup2 dw 0 ; End marker