将Turbo Pascal内联代码转换为Object Pascal

4

在将旧的 Turbo Pascal 单元转换为现代的 Object Pascal 时,我遇到了以下问题:

function Less (var a, b; Relation : POINTER) : boolean;
    inline($5B/$59/$0E/$E8/$00/$00/$58/$05/$08/$00/$50/$51/$53/$CB);

该代码应该调用一个外部函数 {$F+} function VariableLess(var a, b : Index) : boolean; {$F-},收集结果并将其传递给调用函数。该函数用于提供未类型化数据的二叉树的单元中。
procedure InsVarBBTree(var B: BBTree; var E; S: word; A: pointer; var ok: boolean);
{ puts variable E of size S into tree B. The order relation address is A. }

因此,单元本身无法提供比较功能,这是定义有效负载的单元的工作。
使用在线反汇编器,我发现这对应于:
{$ASMMODE intel}
function Less (var a, b; Relation : POINTER) : boolean; assembler;

asm
  pop  bx
  pop  cx
  push cs
  call 6
  pop  ax
  add  ax, 8
  push ax
  push cx
  push bx
  retf
end;

然而,编译器不支持 push 语句。我应该怎么做才能让它在现代的64位机器上工作?我意识到这段代码是16位的。


1
call 6 是一个调用指令,跳转到下一条指令 (e8 00 00)。我不太理解这段代码的工作原理。它似乎会影响返回地址。紧接在此函数下面定义的是什么函数? - fuz
1
请注意,此代码似乎滥用了Turbo Pascal编译的某些特定细节。您不能简单地翻译指令以将其移植到amd64。 - fuz
进一步阅读后,此代码弹出返回地址,在堆栈上将函数地址放在“Less”之后,然后返回。如果“Less”的调用者没有设置堆栈帧并立即返回,则返回将转到以下函数。我可能错过了什么。如果TP为程序员设置堆栈帧,则两个单词的洗牌可能是其他内容而不是返回地址。 - fuz
3
我给你的问题点了踩,因为你没有按要求提供相关细节。我认为如果没有这些细节,就无法回答这个问题。 - fuz
1
@fuz: call next_insn / pop ax 只是获取 AX=IP。如果我数对了的话,是的,我认为 AX+8 返回地址正在计算 retf 之后。我认为它使用 push/push/retf 作为当前 CS 和指针参数偏移量的远跳转?也许是某种在近距离和远距离调用之间的适配器?这在 x86-64 中都是无意义的,因为你不想进行远程调用。x86-64 具有 RIP 相关 LEA,因此如果您确实想制造虚假的返回地址,则可以获得 RIP,不匹配的 call/ret 对性能不利。 - Peter Cordes
显示剩余3条评论
3个回答

8

我刚刚在 Turbo Pascal 5 for MS-DOS 上编译了一些内联函数,以检查 Turbo Pascal 如何生成代码:

对于非内联函数调用,Turbo Pascal 将所有函数参数推送到堆栈中。第一个参数首先被推入(因此 SS:SP 指向最后一个函数参数)。然后执行 (far) call。函数使用 retf n 返回,这意味着被调用的函数从堆栈中移除所有参数。

在内联函数中,给定的原始字节简单地取代了 call 指令。这意味着 SS:SP 指向参数,而不是返回地址。内联机器语言代码必须从堆栈中 pop 参数。并且它不能使用 ret 返回,而是简单地在 inline 代码之后继续执行代码。

有了这个知识,就可以分析汇编代码:

使用给定的汇编代码,您可以通过编写一个帮助函数(在您的情况下为 Less),该函数具有与要调用的函数相同的参数加上指向实际函数的附加参数,间接地调用 任何 functionprocedure(在您的情况下为 VariableLess)。

该代码相当于以下的 DelphiFreePascal 代码:

type
    TMyCompare = function(var a, b) : boolean;

function Less (var a, b; Relation : TMyCompare) : boolean;
begin
    Less := Relation(a, b);
end;

如果你的编译器支持函数指针(例如 type TMyCompare = function ...),你可以这样做。
或者,您甚至可以将程序中所有出现的 Less(x,y,z) 替换为 z(x,y)。 这将更有效率。
当然,如果您这样做,指向函数的指针 (VariableLess) 不能具有类型 pointer ,而应该是TMyCompare类型。
如果您的编译器不支持函数指针(如 Turbo Pascal 显然不支持),则可能需要使用汇编语言。
但在这种情况下,不同的编译器将需要不同的汇编代码!
因此,不知道您的编译器内部结构,无法翻译汇编代码。
编辑:
我不确定您的编译器的工作方式。 但是,如果我的原始代码不起作用,也许以下代码可以工作:
function Less (var a, b; Relation : Pointer) : boolean;
type
    TMyCompare = function(var a, b) : boolean;
var
    Relation2 : TMyCompare;
begin
    Relation2 := TMyCompare(Relation);
    Less := Relation2(a, b);
end;

1
这就解释了!非常感谢您研究这个奇特的功能。 - fuz
1
据我所知,TP 5具有过程类型(函数指针),但它只能以这种方式进行内联。因此,如果您的调用相对昂贵,则可能是早期x86的速度优化。然而,Delphi 2006+和FPC 2.2+可以跨单元内联函数,因此没有必要手动执行此操作。 - Marco van de Voort
这似乎接近解决方案,比汇编代码更优雅,因为汇编代码需要针对每个平台进行更改(想想新的苹果CPU!)。但是,如果函数调用的类型是TMyCompare而不是Pointer,我该如何在InsVarBBTree中调用它,仍然使用地址运算符@VariableLess? - Engelbert Buxbaum
我尝试了一下,但是出现了错误:TestDynam.lpr(110,53) 错误:第4个参数的类型不兼容:得到了“<address of function(var Word;var Word):Boolean;Register>”,期望的是“<procedure variable type of function(var <Formal type>;var <Formal type>):Boolean;Register>”。 - Engelbert Buxbaum
1
@EngelbertBuxbaum 请看我的编辑部分。也许这样会起作用…… - Martin Rosenau
差不多了!类型转换需要在调用InsVarBBTree时进行,可以看一下我下面发布的解决方案。非常感谢。 - Engelbert Buxbaum

2
解决方案如下: 在处理动态类型的单元内进行定义。
 type
      TMyCompare = function(var a, b) : boolean;  
    
    function Less (var a, b; Relation : TMyCompare) : boolean;
    
    begin
      Result := Relation(a, b);
    end;

    procedure InsVarBBTree(var B: BBTree; var E; S: word; A: TMyCompare; 
          var ok: boolean);
    { puts variable E of size S into tree B. The order relation address is A. }

这是从外部调用的函数

{$F+} function VariableLess(var a, b : Index) : boolean; {$F-}
begin
...
end;

InsVarBBTree(Baum, TempStr, SizeOf(TempStr), TMyCompare(@VariableLess), OK)

感谢所有帮助过这件事情的人。
Engelbert

1
据我所知,这将适用于Free Pascal 16位目标,但您可能不感兴趣。对于非16位目标,16位代码将需要重写,内存模型和ABI是不同的。您的反汇编是符号性的,常量并不真正反映源代码的含义,就像原始汇编源代码(.asm)一样。
由于它似乎对ABI进行了假设,因此该过程特别棘手,因此本质上是不可移植的。
此外,即使您成功了,结果也会次优,因为对象Pascal编译器(Delphi、Free Pascal)比TP更具有优化能力。对于短程序使用外部汇编器会降低编译器的内联能力。
我认为Peter Cordes是正确的,这是一种对显示过程之后的实际功能的转向。不,我认为Martin更接近。这有点不合逻辑,但由于编译器无法真正内联(只能转储一个汇编块),因此参数调用保持不变,并且必须在没有访问堆栈帧/局部变量的情况下撤消。TP不会将值保存在寄存器中,因此相对安全。
您也可以尝试将其反汇编,但最好的方法可能是简单地从文档中尝试制定pascal替代方案,并忘记所有微观优化。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接