为什么eax包含self时会返回零?

9
根据"在Delphi中使用汇编语言"eax将包含Self。然而,如所示,eax的内容为0。我想知道哪里出了问题?
procedure TForm1.FormCreate(Sender: TObject);
var
  X, Y: Pointer;
begin
  asm
    mov X, eax
    mov Y, edx
  end;
  ShowMessage(IntToStr(NativeInt(X)) + ' ; ' + IntToStr(NativeInt(Y)));
end;
2个回答

13

在调试设置下编译时生成的代码如下:

  begin
005A9414 55               推入 ebp
005A9415 8BEC             将 esp 赋值给 ebp
005A9417 83C4E4           减少 esp $1c
005A941A 33C9             ecx 异或 ecx
005A941C 894DEC           将 ecx 存储在 [ebp-$14]
005A941F 894DE8           将 ecx 存储在 [ebp-$18]
005A9422 894DE4           将 ecx 存储在 [ebp-$1c]
005A9425 8955F0           将 edx 存储在 [ebp-$10]
005A9428 8945F4           将 eax 存储在 [ebp-$0c]
005A942B 33C0             eax 异或 eax
005A942D 55               推入 ebp
005A942E 6890945A00       推入 $005a9490
005A9433 64FF30           推入 dword ptr fs:[eax]
005A9436 648920           将 esp 存储在 fs:[eax]
  将 X 赋值为 eax
005A9439 8945FC           将 eax 存储在 [ebp-$04]
  将 Y 赋值为 edx
005A943C 8955F8           将 edx 存储在 [ebp-$08]

当代码开始执行时,eax确实是 self 指针。但编译器选择将其存储到 ebp-$0c,然后将eax置零,这真的取决于编译器。

在释放设置下的代码非常相似。编译器仍然选择将 eax 置零。当然,您不能依赖于编译器做到这一点。

  begin
005A82A4 55               推入 ebp
005A82A5 8BEC             将 esp 赋值给 ebp
005A82A7 33C9             ecx 异或 ecx
005A82A9 51               推入 ecx
005A82AA 51               推入 ecx
005A82AB 51               推入 ecx
005A82AC 51               推入 ecx
005A82AD 51               推入 ecx
005A82AE 33C0             eax 异或 eax
005A82B0 55               推入 ebp
005A82B1 6813835A00       推入 $005a8313
005A82B6 64FF30           推入 dword ptr fs:[eax]
005A82B9 648920           将 esp 存储在 fs:[eax]
  将 X 赋值为 eax
005A82BC 8945FC           将 eax 存储在 [ebp-$04]
  将 Y 赋值为 edx
005A82BF 8955F8           将 edx 存储在 [ebp-$08]

请记住,参数传递定义了函数开始执行时寄存器和堆栈的状态。接下来会发生什么,函数如何解码参数取决于编译器。编译器没有义务保留用于参数传递的寄存器和堆栈不变。

如果您在函数中间注入汇编语言,不能期望易失性寄存器(例如eax)具有特定值。它们将保存编译器最近放入其中的任何内容。

如果您想在函数开始执行时检查寄存器,请使用纯汇编函数以确保避免编译器修改用于参数传递的寄存器:

var
  X, Y: Pointer;
asm
  mov X, eax
  mov Y, edx
  // .... do something with X and Y
end;
编译器的选择很大程度上取决于函数其余代码。对于您的代码,组合要传递给 ShowMessage 的字符串的复杂性会导致相当大的序言。请改用以下代码:
type
  TForm1 = class(TForm)
    procedure FormCreate(Sender: TObject);
  private
    i: Integer;
    function Sum(j: Integer): Integer;
  end;
....
procedure TForm1.FormCreate(Sender: TObject);
begin
  i := 624;
  Caption := IntToStr(Sum(42));
end;

function TForm1.Sum(j: Integer): Integer;
var
  X: Pointer;
begin
  asm
    mov X, eax
  end;
  Result := TForm1(X).i + j;
end;

在这种情况下,代码足够简单,编译器会保持 eax 不变。对于 Sum 的优化版本的发布构建代码为:

  begin
005A8298 55               push ebp
005A8299 8BEC             mov ebp,esp
005A829B 51               push ecx
  mov X, eax
005A829C 8945FC           mov [ebp-$04],eax
  Result := TForm4(X).i + j;
005A829F 8B45FC           mov eax,[ebp-$04]
005A82A2 8B80A0030000     mov eax,[eax+$000003a0]
005A82A8 03C2             add eax,edx
  end;
005A82AA 59               pop ecx
005A82AB 5D               pop ebp
005A82AC C3               ret 

当你运行代码时,窗体的标题会更改为预期的值。


老实说,在 Pascal 函数内部放置 asm 块的内联汇编不是很有用。编写汇编语言的问题在于您需要完全了解寄存器和堆栈的状态。这在函数的开始和结束时由 ABI 定义,状态明确。

但是在函数的中间,该状态完全取决于编译器所做的决策。将 asm 块注入其中需要您了解编译器所做的决策。这也意味着编译器无法理解您所做的决策。这通常是不切实际的。事实上,对于 x64 编译器,Embarcadero 禁止使用此类内联 asm 块。我个人从未在代码中使用过内联 asm 块。如果我写汇编语言,我总是编写纯汇编函数。


感谢您的专业评论! - SOUser

0

只需使用 Push/Pop 获取 SELF 的指针,然后就可以自由地使用属性,像这样:

    asm
      push Self
      pop edx                  //Now, [edx] is the pointer to Self

      mov   ecx, [edx].FItems  //ecx = FItems
      mov   eax, [edx].FCount  //eax = FCount
      dec   eax                //test zero count!
      js    @Exit              //if count was 0 then exit as -1
    @Loop:                     //and so on...
      ......

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