5

Win32、Win64、および OSX 32 用にコンパイルして正常に動作する (XE2) Delphi/アセンブリ コードがいくつかあります。ただし、Linux で動作させる必要があるため、FPC バージョンのコンパイルを検討しています (これまでのところ、Win32 /64、Linux32/64)。

概して、それはうまく機能しますが、私がうまくいかなかったのはSystem、次のような Delphi ユニット関数への呼び出し/ジャンプです。

  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この 2 つによってコンパイルされたバージョンでレジスタの配置が異なる場合があります。解決策は、この違いを回避することです。これを行う 1 つの方法は、上記の 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

簡単な答え: これを行う正しい方法は、call 命令を使用することです。

長い答え: x86-64 コードでは、スタックが 16 バイトにアラインされている必要があるため、FillMemCall には、エントリ ポイントにコンパイラが生成したサブ rsp,8 と、出口に追加 rsp,8 が含まれています (他の 8 バイトは、 call/ret ペア)。一方、Fillchar は手作業でコーディングされたアセンブラーであり、nostackframe ディレクティブを使用するため、コンパイラによって生成された sub/add ペアが含まれず、fillchar が残るとすぐにスタックが台無しになります。レット命令。

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;

また、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;
于 2013-05-15T17:45:23.730 に答える