【问题标题】:Assembly calls to System unit functions on FreePascal x64FreePascal x64 上对系统单元函数的汇编调用
【发布时间】:2013-05-15 08:06:53
【问题描述】:

我有一些 Delphi/汇编代码,可以为 Win32、Win64 和 OSX 32 编译和工作正常 (XE2)。但是,由于我需要它在 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;

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

【问题讨论】:

  • 有些人可能想知道,为什么当它与 FillChar 具有相同的参数并以相同的顺序时,FillMemCell 根本存在。为什么不直接调用 FillChar?
  • 在 x64 中的 FillChar 或 Writeln 实现似乎存在问题,因为如果您在没有 MakeString 函数的情况下直接调用它会引发异常。
  • StringOfChar 似乎已经完成了您的功能。
  • @RobKennedy 我的猜测是,这已被大量削减,而真正的代码可能有充分的理由去做看起来毫无意义的事情
  • @RobKennedy 确实,上面的代码毫无意义。但正如大卫建议的那样,它被删减只是为了重现和说明问题。

标签: delphi assembly 64-bit freepascal basm


【解决方案1】:

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

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

解决方法,例如对 FillMemCall 使用 nostackframe 指令或在执行 jmp 之前调整堆栈可能是可能的,但可能会因将来的编译器更改而中断。

【讨论】:

  • 完美!这就解释了。 Delphi 在这里隐含地和 .NOFRAME 做同样的事情(没有生成堆栈帧),而 FPC 没有......只是没有想到这种可能性......
  • 没错。这明显违反了 x86-64 调用约定。
  • @WarrenP 本身不是。没有要求 每个 函数都需要一个堆栈帧(叶函数通常可以在没有堆栈帧的情况下逃脱。注意我不是在谈论影子空间),这只是编译器的不同。在 asm 级别,无论如何处理这些事情是您的责任。
  • @WarrenP 你确定吗?使用这种直接重定向,例如在库的动态链接期间,例如当您调用操作系统 API 时。一个普通的jmp 可能是安全的,如果不涉及堆栈帧,并且异常映射到目标代码中,正如 Win64 所期望的那样。
  • 哦,我明白了。就像在跳台上一样。我的错。是的。
【解决方案2】:

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

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

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

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

而且会比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;

【讨论】:

  • 我原则上同意这一点,但上面的代码是更复杂代码的简化,这根本不是一个选项。 (asm 是有原因的,也是我打扰的原因)
  • @PhiS 所以你最好用正确的代码创建一个新问题。处理堆栈帧非常特定于所涉及的代码。例如,它将取决于局部变量、传递多少参数以及函数是否返回一些变量——包括处理引用计数类型的变量......
  • 再次,原则上同意。但是我的问题已经解决了,当我问这个问题时,我只是不知道这是手头的问题(我在上面已经指出这是一个简化的事实)。无论如何,感谢您的反馈。顺便说一句,你做得很好。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-01-16
  • 1970-01-01
  • 2010-12-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多