3

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:

  1. 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.
  2. 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; 
AmigoJack
  • 5,234
  • 1
  • 15
  • 31
  • As you see now comments can't deal with multiline text/code. Consider answering your own question or editing this new information into your question (while also adapting Tom's formatting additions). In any case, consider taking the [tour]. – AmigoJack Nov 27 '22 at 18:47
  • If you have found solution (and it seems like you have), then please post that as an answer instead of adding it to the question. If someone has better answer they can still post their answer regardless of yours. – Dalija Prasnikar Nov 27 '22 at 22:26
  • Please also edit your question to include the actual compiler version you're using or at least mention the Delphi version you use. Maybe using [FPC](https://www.freepascal.org/down/x86_64/win64-canada.html) produces different results that help you finding a solution. – AmigoJack Nov 30 '22 at 20:40

1 Answers1

0

So far nobody came with a solution, so I consider the code below to be the best known solution:

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; 

BTW, as this very much looks like a Delphi bug, I posted this to the Embarcadero as a bug report.