The Z-80 has no direct way to read the program counter contents. However, there are a few techniques that can work with minimal assumptions. If your code is loaded into RAM it can use self-modification to find itself:
di
ld hl,0
ld a,(hl)
look: ld (hl),a
dec hl
ld a,(hl)
ld (hl),0
loc: jr look
ld (hl),a ; repair, HL = loc + 1
When this completes HL will point to loc + 1
. The program marches down from the top of memory temporarily setting each memory location to 0. When it gets to loc + 1
the jr
there will be changed to go to the next instruction. At which point the jr is fixed and the program now knows where it is in memory.
If your program is in a ROM at an unknown location you could do something more complicated in terms of checksumming memory in order to identify your code. Or look for some random "signature" bytes. Or if you know there are two bytes of RAM at a certain memory location you can do this:
ld de,($3c00)
ld hl,$e9e1
ld ($3c00),hl ; write $e1 $e9 which is POP HL; JP (HL)
call $3c00
loc: ld ($3c00),de ; HL = loc
If you only know one byte of RAM, this will do:
di
find: ld hl,mem
ld a,(hl)
ld (hl),$c9 ; "RET"
call mem
loc: ld (hl),a ; restore
dec sp
dec sp
pop hl ; now HL==loc
Though it does have to be careful that the RAM modified does not happen to be one of the two bytes of the stack used. There are ways around this but for most systems you're highly likely to be able to pick a RAM location that does not conflict.
The DI
instruction stops most interrupts which would change the stack address if one occurs after the ret
is executed. However, the non-maskable interrupt (NMI) can't be stopped. One workaround is to validate the address loaded into HL say by ensuring it points to a ld (hl),a
instruction. Add this code:
ld a,(hl)
cp $77 ; LD (HL),A opcode
jr nz,find
Obviously that could be fooled by an unlucky data being placed on the stack. Further checks can be made to reduce the chances of such a coincidence to practical certainty.
Or you could make the almost perfectly safe assumption that an NMI will merely change the top of the stack to an address within your program. In that case you only need to search back for the ld (hl),a
instruction to lock down the position:
di
ld hl,mem
ld a,(hl)
ld (hl),$c9 ; "RET"
call mem
loc: ld (hl),a ; restore
ld hl,0
add hl,sp
dec hl
ld a,(hl)
dec hl
ld l,(hl)
ld h,a
look: ld a,(hl)
cp $77 ; LD (HL),A opcode
dec hl
jr nz,look
; now HL==loc - 1
These solutions may not be entirely practical but they do point out how to work with minimal system knowledge.
.com
programs.