Indirect CALL from relocatable code

88 views
Skip to first unread message

Robb Bates

unread,
Nov 12, 2025, 2:21:23 AMNov 12
to RC2014-Z80
Trickier than it seems. How could I do that? 

I have other code that isn't relocatable. I'm thinking about adding a helper function that manipulates the stack. 

Like you load HL with the pointer address and then CALL INDIRECT_HELPER

Clear as mud?

Robb

j.skists

unread,
Nov 12, 2025, 3:24:44 AMNov 12
to RC2014-Z80
The trick I know involves having a known address, though, with the helper:

call_hl:
      JP  (HL) 

... then

    LD HL, addr
    CALL call_hl

--
You received this message because you are subscribed to the Google Groups "RC2014-Z80" group.
To unsubscribe from this group and stop receiving emails from it, send an email to rc2014-z80+...@googlegroups.com.
To view this discussion, visit https://groups.google.com/d/msgid/rc2014-z80/db2e8967-a29c-47c9-b8f7-c624ac516795n%40googlegroups.com.

Robb Bates

unread,
Nov 12, 2025, 1:20:41 PMNov 12
to RC2014-Z80
Aha! That's the idea. 

But unfortunately it won't work out in my situation. I'm using HL,IX and IY.

So I'm thinking of doing some hackery and doing a call, stealing my return address from the stack, pushing that to the stack then getting the pointer address, pushing that to the stack and the doing a "call" by executing a RET.

I'd have to offset the return addresses a bit.

I hope you follow.  I gotta try it.

Steve Cousins

unread,
Nov 12, 2025, 1:34:35 PMNov 12
to RC2014-Z80
How about this self modifying code...

Somewhere in RAM
call_de:
      LD  (call_de_jp + 1), DE
call_de_jp:
      JP  0x0000


... then

    LD   DE, addr
    CALL call_de

Robb Bates

unread,
Nov 12, 2025, 2:07:34 PMNov 12
to RC2014-Z80
Oh, That's the trick!  I think that will work.  Thanks

Oh wait.  call_de_jp+1 would have to be hard coded.  But I can put that part in the fixed section.

Robb

j.skists

unread,
Nov 12, 2025, 2:41:54 PMNov 12
to RC2014-Z80
Another option is to do something like PRL file format. 

CP/M information archive : PRL file format https://share.google/q9a36hYZGOWYp06az

Compile/assemble your code for 0x0000, and again at 0x0100. Do a binary compare, and keep a list of offsets where the binary differs. These would be the addresses that need patching.

Load the code compiled at 0x0000 at any 256byte boundary: 0xXX00. Iterate through your list of offsets and add XX to the value stored at the offset.

And there you are: patched code for the location you've put the code. 

Justin


Robb Bates

unread,
Nov 12, 2025, 2:56:00 PMNov 12
to RC2014-Z80
Yeah, I'm aware of that concept.  But my whole TPA program is to fit inside 2048 bytes, so I only have room for something super simple, and the relocatable code will be loaded at the first available byte.  I'm gonna go with the self modifying code.  That's simple enough.

But thanks for offering that option too!

Robb

Michael Druckenmiller Sr

unread,
Nov 12, 2025, 3:09:44 PMNov 12
to rc201...@googlegroups.com

Timex used a lot of relocatable code, but also used a table in the base address space.

https://datassette.s3.us-west-004.backblazeb2.com/livros/timex_sinclair_2068_rom_disassembly.pdf

There may be something in this document that will help.

Use of HL, IX & IY does muck up the works a bit, this is where a table in the base address space is useful.

Say your code was written based on being at 8F00 and you moved to 9A00 all the relative jumps and calls should be fine. Your table has a list of where you are loading HL, IX and IY so you would possibly add 0F00 to the address loaded into those places.

This could be dynamic, that is as part of the call and move of the code-block itself.

Timex/Sinclair code has a problem keeping track of the usage of IX & IY which greatly restricts the hobbyist use of them. But, when it's all your own code...

Your relocatable code could be a code block that includes, say. 
0000 address of where relocatable code actually starts

0002 First address to modify

0004 Next address to modify

If you're using expanded memory spaces then each entry could be 3 or 4 bytes long.

Jonathan Harston

unread,
Nov 12, 2025, 8:08:06 PMNov 12
to RC2014-Z80
Yes, as mentioned, in almost every case you need to know an absolute address
*somewhere* that you can write to to store a JP (HL) instruction in. It is useful
to combine this with a POP HL to create both a 'CALL (HL)' instruction and a
'LD HL,PC' instruction.

For instance on the ZX Spectrum you can write to the calculator workspace:
; set up
LD HL,&E9E1
LD (CALCBASE),HL ; Store POP HL,JP (HL)
...
; to use
CALL CALCBASE+0 ; LD HL,PC
....
CALL CALCBASE+1 ; CALL (HL)
(The ZX Spectrum enters code with BC=entry address, so there's much
easier ways of relocatable code doing stuff, as the code already knows
where it is.)

If you know you are running on CP/M, then there's space below &80 that you
can use. The first FCB is at &005C, so you could use &005A which is defined
as not used by CP/M. So...
; set up
LD HL,&E9E1
LD (&005A),HL ; Store POP HL,JP (HL)
...
; to use
CALL &5A ; LD HL,PC
....
CALL &5B ; CALL (HL)

jgh
Reply all
Reply to author
Forward
0 new messages