I want to access the local variables of a Delphi procedure from its nested assembly procedure. Although the compiler does allow the references of the local variables, it compiles wrong offsets which only work if the EBP/RBP values are hacked. In the x86 environment I found a fairly elegant hack, but in x64 I couldn't find yet any decent solution.
In the x86 environment the workaround below seems to work fine:
procedure Main;
var ABC: integer;
procedure Sub;
asm
mov ebp, [esp]
mov eax, ABC
end;
...
In the above code, the compiler treats the variable ABC
as it would be in the body of Main
, so hacking the value of EBP in the fist assembly line solves the problem. However, the same trick won't work in the x64 environment:
procedure Main;
var ABC: int64;
procedure Sub;
asm
mov rbp, [rsp]
mov rax, ABC
end;
...
In the above code, the compiler adds an offset when it references the variable ABC
which isn't correct neither with the original (Main
) value of the RBP, nor with its new (Sub
) value. Moreover, changing the RBP in a 64-bit code isn't recommended, so I found the workaround below:
procedure Main;
var ABC: int64;
procedure Sub;
asm
add rcx, $30
mov rax, [rcx + OFFSET ABC]
end;
...
As the compiler passes the initial value of RBP in RCX, and the reference to the variable ABC
can be hacked to be RCX rather than RBP based, the above code does work. However, the correction of $30
depends on the number of variables of Main
, so this workaround is kind of a last resort stuff, and I'd very much like to find something more elegant.
Does anyone have a suggestion on how to do this in a more elegant way?
Note that:
- Of course: in my real code there are a large number of local variables to be accessed from the ASM code, so solutions like passing the variables as parameters are ruled out.
- I'm adding x64 compatibility to x86 code, and there are dozens of codes like this, so I'd need a workaround which transforms that code with small formal changes only (accessing the local variables in a fundamentally different way would become an inexhaustible source of bugs).
UPDATE:
Found a safe but relatively complicated solution: I added a local variable called Sync
to find out the offset between the RBP values of Main
and Sub
, then I do the correction on the RBP:
procedure Main;
var Sync: int64; ABC: int64;
procedure Sub(var SubSync: int64);
asm
push rbp
lea rax, Sync
sub rdx, rax
add rbp, rdx
mov rax, ABC
pop rbp
end;
begin
ABC := 66;
Sub(Sync);
end;