Introduction to Delphi Programming (91): Inline Assembly – Assembly Process and Functions

This article mainly covers the following content:

  • Compiler Optimization

  • Function Results

  • Exceptions

Complete programs and functions can be written using inline assembly language code without the need to include<span>begin...end</span> statements.

Compiler Optimization

The compiler performs various optimizations:

For example, the types of functions that can be written are as follows:

function LongMul(X, Y: Integer): Longint;asm  MOV EAX, X  IMUL Yend;

The compiler performs several optimizations on these routines:

  • For all value parameters of string types and other sizes that are not 1, 2, or 4 bytes, no code is generated to copy them to local variables. Such parameters must be treated like var parameters within the routine.
  • If the function does not return a string, variant, or interface reference, the compiler does not allocate a function result variable; references to<span>@Result</span> are erroneous. For strings, variants, and interfaces, the caller always allocates a<span>@Result</span> pointer.
  • The compiler only generates a stack frame for nested routines, routines with local parameters, or routines where parameters are on the stack.
  • <span>Locals</span> indicates the size of local variables, while<span>Params</span> indicates the size of parameters. If both<span>Locals</span> and<span>Params</span> are zero, there is no entry code, and the exit code consists only of a<span>RET</span> instruction.

The automatically generated entry and exit code for routines is as follows:

PUSH EBP         ; Exists if Locals is not equal to 0 or Params is not equal to 0MOV EBP, ESP     ; Exists if Locals is not equal to 0 or Params is not equal to 0SUB ESP, Locals  ; Exists if Locals is not equal to 0; …MOV ESP, EBP     ; Exists if Locals is not equal to 0POP EBP          ; Exists if Locals is not equal to 0 or Params is not equal to 0RET Params       ; Always exists

If local variables include variants, long strings, or interfaces, they will be initialized to zero but will not be finalized.

Function Results

Assembly language functions return their results as follows:

32-bit:

  • Integer values are returned in AL (8-bit value), AX (16-bit value), or EAX (32-bit value).
  • Real numbers are returned in the ST(0) of the coprocessor register stack. (Currency values multiplied by 10000.)
  • Pointers, including long strings, are returned in EAX.
  • Short strings and variants are returned in a temporary location pointed to by<span>@Result</span>.

64-bit:

  • Values not exceeding 8 bytes are returned in RAX.
  • Real numbers are returned in XMM0.
  • Other types are returned by reference, with the pointer value residing in RAX, and memory allocated by the calling routine.

Intel 64 Features (Pseudo-operations)

x64 functions must be entirely written in assembly or Pascal, meaning that assembly statements are not supported, only inline assembly functions.

The following pseudo-operations are provided to help manage stack usage in x64:<span>.PARAMS</span>, <span>.PUSHNV</span>, <span>.SAVENV</span>, and <span>.NOFRAME</span>.

Pseudo-operation Description
.PARAMS Used to set up a backup storage area for register parameters according to the x64 calling convention when calling external functions, as this is often not done by default. When used, a pseudo-variable<span>@params</span> is obtained for passing stack parameters to the called function. Use<span>@params</span> as a byte array, where the first stack parameter will be<span>@params[32]</span>, with positions 0-31 representing 4 register parameters.
.PUSHNV Generates code to save and restore non-volatile general-purpose registers in the prologue and epilogue.
.SAVENV Has the same function for non-volatile XMM registers as .PUSHNV.
.NOFRAME Forces the generation of stack frames to be disabled as long as no local variables are declared and the parameter count <= 4. Only used for leaf functions.

Stack Unwinding for PC-Mapped Exceptions

Assembly routines cannot catch exceptions. Additionally, care must be taken when calling assembly routines due to specific stack unwinding rules. This is particularly important when non-assembly code calls assembly code, which in turn calls other non-assembly code. When an exception is thrown, the stack unwinder clears all stack frames that the exception has passed through. In this process, the unwinder needs to restore several critical registers, including the stack pointer and base pointer.

Certain operations in assembly code may make this unwinding process impossible to complete and should be avoided. A key point is that if the stack pointer is modified by a variable amount without auxiliary information, the unwinder will not be able to handle that frame correctly. For example:

sub esp, eax

or

sub esp, 0xFand esp, 0xFFFFFFF0

Either of these constructs in assembly code will cause the unwinder to be unable to correctly handle the specified procedure. To allow the unwinder to handle procedures containing such instructions, a stack frame must be added to the code:

push ebpmov ebp, esp

Creating a stack frame must be the first two instructions in the code. When the assembly code is compiled, the compiler recognizes this instruction sequence and generates special unwinding information for that routine. If this frame is missing, the compiler will check the entire assembly routine, attempting to analyze how the stack is modified in order to perform the necessary unwinding repairs when an exception occurs.

This mechanism ensures that even in complex application scenarios, such as applications written in a mix of assembly and high-level languages, program stability and reliability can be effectively managed and maintained. Understanding and following these guidelines is crucial for writing efficient and reliable assembly code.

Leave a Comment