程序集调用FreePascal x64上的系统单元功能

时间:2013-05-15 08:06:53

标签: delphi assembly 64-bit freepascal basm

我有一些Delphi /汇编代码可以为Win32,Win64和OSX 32编译和工作正常(XE2)。但是,因为我需要它在Linux上工作,所以我一直在寻找编译它的FPC版本(所以far,Win32 / 64,Linux32 / 64)。

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

  jmp System.@FillChar

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

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

[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。

2 个答案:

答案 0 :(得分:7)

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

长答案:x86-64代码要求堆栈是16字节对齐的,因此FillMemCall在入口点包含一个编译器生成的子rsp,8和一个add rsp,8在出口处(其他8个字节被添加/通过呼叫/ ret对移除)。另一方面,Fillchar是手工编写的汇编程序,并使用nostackframe指令,因此它不包含编译器生成的子/添加对,并且很快就会填充fillchar,堆栈被搞乱,因为FillChar之前不包含添加rsp,8 ret指令。

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

答案 1 :(得分:3)

最简单的是在这种情况下摆脱汇编程序,并且只使用pascal代码:

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

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

它适用于所有平台和CPU(甚至是手臂)。

它会比asm jmp @System.FillChar end技巧更快,因为该过程被声明为inline:不会生成任何代码,调用FillMemCall会直接调用fillchar,那就是它会生成以下代码:

function MakeString (c: AnsiChar; len: longword): AnsiString;
begin
  Setlength (Result, len);
  if len > 0 then 
    fillchar(pointer(Result)^, len, c);
end;