我有一些可以在Win32,Win64和OSX 32上编译并正常工作的(XE2)的Delphi/汇编代码。但是,由于我需要在Linux上运行它,因此我一直在寻找它的FPC版本(到目前为止,Win32/64,Linux32/64)。

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

  jmp System.@FillChar

这似乎在FPC Win32/Linux32上具有预期的效果,但是失败了,但FPC Win64/Linux64 上出现了异​​常。 (我非常熟悉平台之间的调用约定差异,因此请不要认为这是原因。)

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

[Edit1] ---为了回应David的评论,下面是一个简化的程序来说明问题(至少我希望它能如此精确地完成):
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]\FPC\bin\i386-linux fpcx64example.dpr,[Linux64:] ppcrossx64.exe -Tlinux -XPx86_64-linux- -FD[FPCpath]\bin\x86_64-linux fpcx64example.dpr

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

解决方案(感谢FPK):

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

最佳答案

简短的答案:正确的方法是使用调用指令。

长答案:x86-64代码要求堆栈是16个字节对齐的,因此FillMemCall在入口点包含编译器生成的sub rsp,8和在导出处添加rsp,8(其他8个字节由调用/重拨对)。另一方面,Fillchar是手动编码的汇编程序,并使用nostackframe指令,因此它不包含编译器生成的子/添加对,并且一旦保留fillchar,堆栈就被搞砸了,因为FillChar不包含add rsp,8 ret指令。

可能会使用诸如针对FillMemCall的nostackframe指令或在执行jmp之前调整堆栈之类的变通办法,但将来任何编译器更改都可能会破坏这些变通办法。

10-05 22:17