2013-05-15 5 views
4

У меня есть код Delphi/сборки, который компилируется и работает отлично (XE2) для Win32, Win64 и OSX 32. Однако, поскольку мне нужно, чтобы он работал в Linux, я был глядя на компиляцию его версий FPC (до сих пор Win32/64, Linux32/64).Абонентские вызовы функций системного блока на FreePascal x64

По большому счету, он хорошо работает, но одна вещь, которую я не смог попасть на работу являются звонки/перескакивает на Delphi System единицы функции, такие как, например:

jmp [email protected] 

Это, как представляется, имеют желаемый эффект на FPC Win32/Linux32, но сбой с исключением на FPC Win64/Linux64. (Я хорошо знаком с развязкой вызывающих конвенций среди платформ, поэтому не думаю, что это причина.)

Каков правильный способ сделать это на FPC для платформ x64?

[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 [email protected] 
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 [email protected] выше избавляет от исключения на x64.

Раствор (Благодаря ФПК):

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 [email protected] 
end; 

Это не совсем красиво, но теперь он работает на Win/Linux 32/64 как для Delphi и FPC.

+0

Некоторые могут задаться вопросом, почему, когда он имеет те же параметры, что и FillChar в том же порядке, FillMemCell существует вообще. Почему бы просто не назвать FillChar? –

+0

Кажется, что проблема с реализацией FillChar или Writeln в x64, поскольку она вызывает исключение, если вы вызываете ее напрямую без функции MakeString. –

+0

['StringOfChar'] (http://www.freepascal.org/docs-html/rtl/system/stringofchar.html), похоже, делает то, что делает ваша функция. –

ответ

7

Короткий ответ: правильный способ сделать это - использовать инструкцию вызова.

Длинный ответ: код x86-64 требует, чтобы стек был выровнен по 16 байт, поэтому FillMemCall содержит в точке входа компилятор сгенерированный sub rsp, 8 и add rsp, 8 на выходе (добавлены остальные 8 байтов/удалить с помощью пары call/ret). Fillchar - это, с другой стороны, ассемблер с ручным кодированием и использует директиву nostackframe, поэтому он не содержит сгенерированную компилятором пару sub/add, и как только fillchar остается, стек перепутался, потому что FillChar не содержит add rsp, 8 до инструкция ret.

Обходные пути, такие как использование директивы nostackframe для FillMemCall или настройка стека перед выполнением jmp, могут быть возможны, но могут быть нарушены любым будущим изменением компилятора.

+0

Отлично! Что объясняет его. Delphi неявно делает то же самое, что и .NOFRAME здесь (без фрейма стека), в то время как FPC не ... Просто не думал об этой возможности ... – PhiS

+0

Точно. Это явное нарушение соглашений о вызовах x86-64. –

+1

@WarrenP Не по себе. Нет требования, чтобы функции _every_ требовалось фрейм стека (функции листьев часто могут уйти без них. NB Я НЕ говорю о теневом пространстве), это просто разница в компиляторах. На уровне asm вы все равно должны заботиться об этих вещах. – PhiS

3

Проще всего избавиться от сборщика в этом случае, и использовать только паскаль код:

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

И он будет работать как с FPC и Delphi (для более новых версий, где inline известно).

И он будет работать на всех платформах и процессоре (даже на руке).

И это будет быстрее, чем 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; 
+0

Я согласен с этим в принципе, но приведенный выше код упрощает более сложный код, где это просто не вариант. (Есть причина для asm и почему я беспокоюсь) – PhiS

+0

@PhiS Итак, вам лучше создать новый вопрос с правильным кодом. Работа с кадрами стека очень специфична для задействованного кода. Например, он будет зависеть от локальных переменных, от того, сколько параметров передано, и если функция возвращает некоторую переменную, включая обработку ссылочного подсчитанного типа переменных ... –

+0

Опять же, согласовано в принципе. Но моя проблема решена, и когда я задал вопрос, я просто не знал, что это проблема (и я указал выше на то, что это было упрощение). В любом случае, спасибо за ваши отзывы. Btw, отличная работа, которую вы делаете. – PhiS