5

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

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

  jmp System.@FillChar

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

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

[Edit1] ---回应大卫的评论,这是一个说明问题的简化程序(至少我希望它准确无误):

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。

4

2 回答 2

7

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

长答案:x86-64 代码要求堆栈是 16 字节对齐的,因此 FillMemCall 在入口点包含一个编译器生成的 sub rsp,8 和一个 add rsp,8 在出口处(其他 8 个字节由呼叫/撤回对)。另一方面,Fillchar 是手动编码的汇编程序,并使用 nostackframe 指令,因此它不包含编译器生成的子/添加对,并且一旦留下填充字符,堆栈就会混乱,因为 FillChar 之前不包含 add rsp,8 ret 指令。

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

于 2013-05-15T15:55:06.930 回答
3

在这种情况下,最简单的方法是去掉汇编程序,只使用帕斯卡代码:

procedure FillMemCall (p: pointer; len: longword; val: byte); inline; 
begin
  fillchar(p^,len,val);
end;

inline它适用于 FPC 和 Delphi(适用于已知的较新版本)。

它适用于所有平台和 CPU(甚至 arm)。

而且会比asm jmp @System.FillChar endtrick快,因为程序声明为inline:不会生成代码,调用FillMemCall会直接调用fillchar,即会生成如下代码:

function MakeString (c: AnsiChar; len: longword): AnsiString;
begin
  Setlength (Result, len);
  if len > 0 then 
    fillchar(pointer(Result)^, len, c);
end;
于 2013-05-15T17:45:23.730 回答