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;
4

2 回答 2

13

我在调试设置下编译它时生成的代码是这样的:

  开始
005A9414 55推ebp
005A9415 8BEC 移动 ebp,esp
005A9417 83C4E4 添加 esp,-$1c
005A941A 33C9 异或 ecx,ecx
005A941C 894DEC mov [ebp-$14],ecx
005A941F 894DE8 mov [ebp-$18],ecx
005A9422 894DE4 mov [ebp-$1c],ecx
005A9425 8955F0 移动 [ebp-$10],edx
005A9428 8945F4 mov [ebp-$0c],eax
005A942B 33C0 xor eax,eax
005A942D 55推ebp
005A942E 6890945A00 推 $005a9490
005A9433 64FF30 推双字 ptr fs:[eax]
005A9436 648920 mov fs:[eax],esp
  mov X, eax
005A9439 8945FC 移动 [ebp-$04],eax
  mov Y, edx
005A943C 8955F8 mov [ebp-$08],edx

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

发布设置下的代码非常相似。编译器仍然选择归零eax。当然,你不能依赖编译器来做这件事。

  开始
005A82A4 55推ebp
005A82A5 8BEC 移动 ebp,esp
005A82A7 33C9 异或 ecx,ecx
005A82A9 51 推 ecx
005A82AA 51 推 ecx
005A82AB 51 推 ecx
005A82AC 51 推 ecx
005A82AD 51 推 ecx
005A82AE 33C0 xor eax,eax
005A82B0 55推ebp
005A82B1 6813835A00 推 $005a8313
005A82B6 64FF30 推双字 ptr fs:[eax]
005A82B9 648920 mov fs:[eax],esp
  mov X, eax
005A82BC 8945FC mov [ebp-$04],eax
  mov Y, edx
005A82BF 8955F8 mov [ebp-$08],edx

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

如果将 asm 注入函数的中间,则不能期望 volatile 寄存器喜欢eax具有特定值。它们将保存编译器最近放入其中的任何内容。

如果要在函数执行开始时检查寄存器,则需要使用纯 asm 函数来确保避免编译器修改用于参数传递的寄存器:

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是:

  开始
005A8298 55推ebp
005A8299 8BEC 移动 ebp,esp
005A829B 51 推 ecx
  mov X, eax
005A829C 8945FC 移动 [ebp-$04],eax
  结果 := TForm4(X).i + j;
005A829F 8B45FC 移动 eax,[ebp-$04]
005A82A2 8B80A0030000 移动 eax,[eax+$000003a0]
005A82A8 03C2 添加 eax,edx
  结尾;
005A82AA 59 流行音乐
005A82AB 5D pop ebp
005A82AC C3 RET

当您运行代码时,表单的标题会更改为预期值。


老实说,作为 asm 块放置在 Pascal 函数中的内联汇编并不是很有用。编写汇编的事情是您需要完全了解寄存器和堆栈的状态。这在 ABI 定义的函数的开头和结尾处得到了很好的定义。

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

于 2014-04-17T09:09:03.710 回答
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...
      ......
于 2017-07-05T08:27:06.337 回答