Delphi汇编程序:了解Result寄存器

时间:2014-11-25 17:44:15

标签: delphi delphi-7 inline-assembly basm

我在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

2 个答案:

答案 0 :(得分:3)

至少在没有启用优化的情况下,您的功能具有前导码和后置码功能。看看吧:

Project46.dpr.13: asm
0041A1F4 55               push ebp
0041A1F5 8BEC             mov ebp,esp
0041A1F7 83C4F8           add esp,-$08
0041A1FA 8945F8           mov [ebp-$08],eax
Project46.dpr.14: CMP       EAX, 0
0041A1FD 83F800           cmp eax,$00
Project46.dpr.15: JNZ       Go
0041A200 7506             jnz $0041a208
Project46.dpr.16: MOV       EAX, -1
0041A202 B8FFFFFFFF       mov 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: end;
0041A20E 8B45FC           mov eax,[ebp-$04]
0041A211 59               pop ecx
0041A212 59               pop ecx
0041A213 5D               pop ebp
0041A214 C3               ret 

因此pre-amble设置堆栈帧。它会保存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             mov eax,ebx
004059F8 8BC2             mov 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             mov eax,ebx
004059E4 C3               ret 

那更像是它!请注意编译器如何知道此处没有可检测项,并用jmp @@exit替换ret

答案 1 :(得分:1)

  

我做错了什么,或者我应该说,我不明白的是什么   此?

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