【问题标题】:Delphi assembler: understanding the Result registerDelphi 汇编器:了解结果寄存器
【发布时间】:2014-11-25 17:44:14
【问题描述】:

我在 Delphi 中使用 ASM。据我了解,EAX 持有 Result。下面我必须把RET放在最后,否则Result不正确(如果输入为0则正确)。我做错了什么,或者我应该说,我对此有什么不明白的地方?

function MSb(const Val: Integer): Integer;
label
  Go;
asm
  CMP       EAX, 0
  JNZ       Go
  MOV       EAX, -1
  RET
Go:
  BSR       EBX, EAX
  MOV       EAX, EBX
  RET
end;

如果我这样说:

  MOV       Result, EBX

然后我得到以下编译:

  MOV [EPB-$04], EBX
  MOV EAX, [EPB-$04]

但是,我上面的代码有以下后记:

  MOV       EAX, EDX
  RET

【问题讨论】:

    标签: delphi delphi-7 inline-assembly basm


    【解决方案1】:

    至少在未启用优化的情况下,您的函数具有前导码和后同步码。看看吧:

    项目 46.dpr.13:asm 0041A1F4 55推ebp 0041A1F5 8BEC 移动 ebp,esp 0041A1F7 83C4F8 添加 esp,-$08 0041A1FA 8945F8 mov [ebp-$08],eax Project46.dpr.14:CMP EAX,0 0041A1FD 83F800 cmp 每个,00 美元 Project46.dpr.15:JNZ Go 0041A200 7506 jnz $0041a208 Project46.dpr.16:移动 EAX,-1 0041A202 B8FFFFFFFF 移动 eax,$ffffffff Project46.dpr.17:RET 0041A207 C3 RET Project46.dpr.19:BSR EBX、EAX 0041A208 0FBDD8 bsr ebx,eax Project46.dpr.20:MOV EAX、EBX 0041A20B 89D8 mov eax,ebx Project46.dpr.21:RET 0041A20D C3 RET Project46.dpr.22:结束; 0041A20E 8B45FC mov eax,[ebp-$04] 0041A211 59 流行音乐 0041A212 59 流行音乐 0041A213 5D pop ebp 0041A214 C3 RET

    所以前导码设置了堆栈帧。它保存了ebp 寄存器,并修改了ebpesp 寄存器。还要注意后序。您确实需要执行该代码来恢复保存的寄存器。

    通常的处理方法是跳转到函数的末尾而不是使用ret。所以写你的代码是这样的:

    function MSb(const Val: Integer): Integer;
    asm
      CMP       EAX, 0
      JNZ       @@go
      MOV       EAX, -1
      JMP       @@exit
    @@go:
      BSR       EBX, EAX
      MOV       EAX, EBX
    @@exit:
    end;
    

    这样可以确保执行后同步码。您确实应该养成以这种方式编写代码的习惯,以确保执行任何前导码。


    现在,除此之外,我怀疑您在问题中提到的问题实际上与与您使用 Pascal 标签而不是 asm 标签有关的编译器错误有关。好吧,也许这是一个编译器错误,但也许使用 Pascal 标签只是一个错误。考虑以下程序:

    {$APPTYPE CONSOLE}
    
    function MSb(const Val: Integer): Integer;
    asm
      CMP       EAX, 0
      JNZ       @@Go
      MOV       EAX, -1
      JMP       @@exit
    @@Go:
      BSR       EBX, EAX
      MOV       EAX, EBX
    @@exit:
    end;
    
    function MSb2(const Val: Integer): Integer;
    label
      Go;
    asm
      CMP       EAX, 0
      JNZ       Go
      MOV       EAX, -1
      RET
    Go:
      BSR       EBX, EAX
      MOV       EAX, EBX
    end;
    
    begin
      Writeln(Msb(0));
      Writeln(Msb(1));
      Writeln(Msb2(0));
      Writeln(Msb2(1));
      Readln;
    end.
    

    优化编译后的输出为:

    -1 0 -1 4

    那么,那个相当奇怪的4 怎么样。好吧,让我们看看Msb2 的汇编代码,它本质上就是你的代码:

    004059E8 83F800 cmp eax,$00 004059EB 7506 jnz $004059f3 004059ED B8FFFFFFFF mov eax,$ffffffff 004059F2 C3 RET 004059F3 0FBDD8 bsr ebx,eax 004059F6 89D8 移动 eax,ebx 004059F8 8BC2 移动 eax,edx 004059FA C3 RET

    为什么edx 的值是一个尚未赋值的易失寄存器,在函数返回之前被移入eax。这是您报告的问题。我的猜测是使用 Pascal 标签会使汇编程序感到困惑。坚持使用 asm 标签。

    这是Msb的汇编代码:

    004059D4 83F800 cmp eax,$00 004059D7 7506 jnz $004059df 004059D9 B8FFFFFFFF mov eax,$ffffffff 004059DE C3 RET 004059DF 0FBDD8 bsr ebx,eax 004059E2 89D8 移动 eax,ebx 004059E4 C3 RET

    这更像是它!注意编译器是如何知道这里没有可发布的,并将jmp @@exit 替换为直接的ret

    【讨论】:

    • 评论不用于扩展讨论;这个对话是moved to chat
    • @Flexo 谢谢。我的看法是这里的评论线程很好,有用且信息丰富。
    【解决方案2】:

    我做错了什么,或者我应该说,我不明白什么 这个?

    基本上清楚地说明了前后同步码,但在使用汇编代码时也要小心。根据调用约定,参数以不同的方式传递。如果您删除 labelRET 并使用汇编程序标签 (@@),您的代码将在 Pascal(具有讽刺意味的过时)调用约定下运行良好。最好始终指明汇编代码的调用约定,因为Result 可能确实引用EAX 或用于保存它的local 变量。当使用 Register(默认)调用约定时,EAX 直接映射到 Result

    【讨论】:

      猜你喜欢
      • 2012-12-29
      • 2021-03-25
      • 2017-07-02
      • 2016-11-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-01-30
      相关资源
      最近更新 更多