本文介绍了在 FreePascal x64 上对系统单元函数的汇编调用的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一些 Delphi/汇编代码可以在 Win32、Win64 和 OSX 32 上编译并正常工作 (XE2).但是,由于我需要它在 Linux 上运行,我一直在考虑编译它的 FPC 版本(所以远,Win32/64,Linux32/64).

I have some Delphi/assembly code that compiles and works fine (XE2) for Win32, Win64, and OSX 32. However, since I need it to work on Linux, I have been looking at compiling FPC versions of it (so far, Win32/64, Linux32/64).

总的来说,它运行良好,但我无法开始工作的一件事是调用/跳转到 Delphi System 单元函数,例如:

By and large, it works well, but the one thing I have not been able to get to work are calls/jumps to Delphi System unit functions, like such:

  jmp System.@FillChar

这似乎对 FPC Win32/Linux32 产生了预期效果,但 失败,但在 FPC Win64/Linux64 上.(我很熟悉平台之间的调用约定差异,所以不要认为这是原因.)

This appears to have the desired effect on FPC Win32/Linux32, but fails with an exception on FPC Win64/Linux64. (I am quite familiar with the calling convention differences among the platforms, so don't think that's the reason.)

在 x64 平台的 FPC 上执行此操作的正确方法是什么?

[Edit1] --- 为了回应 David 的评论,这里有一个简化的程序来说明问题(至少我希望它如此准确):

--- In response to David's comment, here is a simplified program that illustrates the problem (at least I hope it does so accurately):

program fpcx64example;
{$IFDEF FPC}
  {$MODE DELPHI}
  {$ASMMODE INTEL}
{$ELSE}
  {$APPTYPE CONSOLE}
{$ENDIF}

procedure FillMemCall (p: pointer; len: longword; val: byte);
asm
  // this function and the System function have the same parameters
  // in the same order -- they are already in their proper places here
  jmp System.@FillChar
end;

function MakeString (c: AnsiChar; len: longword): AnsiString;
begin
  Setlength (Result, len);
  if len > 0 then FillMemCall (PAnsiChar(Result), len, byte(c));
end;

begin
  try
    writeln (MakeString ('x',10));
  except
    writeln ('Exception!');
  end;
end.

使用 FPC 编译:[Win32:] fpc.exe fpcx64example.dpr, [Win64:] ppcrossx64.exe fpcx64example.dpr,[Linux32:] fpc.exe -Tlinux -XPi386-linux- -FD[path]FPCini386-linux fpcx64example.dpr, [Linux64:] ppcrossx64.exe -Tlinux -XPx86_64-linux- -FD[FPCpath]inx86_64-linux fpcx64example.dpr.

To compile with FPC:[Win32:] fpc.exe fpcx64example.dpr, [Win64:] ppcrossx64.exe fpcx64example.dpr, [Linux32:] fpc.exe -Tlinux -XPi386-linux- -FD[path]FPCini386-linux fpcx64example.dpr, [Linux64:] ppcrossx64.exe -Tlinux -XPx86_64-linux- -FD[FPCpath]inx86_64-linux fpcx64example.dpr.

适用于 Delphi (Win32/64).对于 FPC,删除上面的 jmp System.@FillChar 可以消除 x64 上的异常.

Works fine with Delphi (Win32/64). For FPC, removing jmp System.@FillChar above gets rid of the exception on x64.

解决方案(感谢 FPK):

Delphi 和 FPC 在完全相同的条件下不会为函数生成堆栈帧,因此 RSP 寄存器可能在两者编译的版本中具有不同的对齐方式.解决方案是避免这种差异.对于上面的 FillMemCall 示例,这样做的一种方法如下所示:

Delphi and FPC do not generate stack frames for functions under the exact same conditions, so that the RSP register may have a different alignment in the versions compiled by the two. The solution is to avoid this difference. One way of doing so, for the FillMemCall example above, would look like such:

{$IFDEF CPU64} {$DEFINE CPUX64} {$ENDIF} // for Delphi compatibility
procedure FillMemCall (p: pointer; len: longword; val: byte);
  {$IFDEF FPC} nostackframe; {$ENDIF} //Force same FPC behaviour as in Delphi
asm
  {$IFDEF CPUX64}
    {$IFNDEF FPC} .NOFRAME {$ENDIF} // To make it explicit (Delphi)...
    // RSP = ###0h at the site of the last CALL instruction, so
    // since the return address (QWORD) was pushed onto the stack by CALL,
    // it must now be ###8h -- if nobody touched RSP.
    movdqa xmm0, dqword ptr [rsp + 8] // <- Testing RSP misalignment -- this will crash if not aligned to DQWORD boundary
  {$ENDIF}
  jmp System.@FillChar
end;

这不是很漂亮,但现在它适用于 Delphi 和 FPC 的 Win/Linux 32/64.

This isn't exactly beautiful, but it now works for Win/Linux 32/64 for both Delphi and FPC.

推荐答案

简答:正确的做法是使用 call 指令.

Short answer: the correct way to do this is using a call instruction.

长答案:x86-64 代码要求堆栈按 16 字节对齐,因此 FillMemCall 在入口点包含编译器生成的 sub rsp,8 和出口处的 add rsp,8(其他 8 个字节已添加/通过调用/返回对删除).另一方面,Fillchar 是手工编码的汇编程序,并使用 nostackframe 指令,因此它不包含编译器生成的 sub/add 对,并且只要留下 fillchar,堆栈就会混乱,因为 FillChar 之前不包含 add rsp,8ret 指令.

Long answer: x86-64 code requires that the stack is 16 byte aligned, so FillMemCall contains at the entry point a compiler generated sub rsp,8 and an add rsp,8 at the exit (the other 8 bytes are added/remove by the call/ret pair). Fillchar is on the other hand hand-coded assembler and uses the nostackframe directive so it does not contain a compiler generated sub/add pair and as soon fillchar is left, the stack is messed up because FillChar does not contain an add rsp,8 before the ret instruction.

诸如对 FillMemCall 使用 nostackframe 指令或在执行 jmp 之前调整堆栈等变通方法可能是可行的,但可能会被任何未来的编译器更改所破坏.

Workarounds like using the nostackframe directive for FillMemCall or adjusting the stack before doing the jmp might be possible but are subject to be broken by any future compiler change.

这篇关于在 FreePascal x64 上对系统单元函数的汇编调用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

08-29 08:12