如果eax包含self,为什么eax为零?

时间:2014-04-17 08:53:51

标签: delphi basm

根据"Using Assembler in 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 个答案:

答案 0 :(得分:13)

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

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

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

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

  begin
005A82A4 55               push ebp
005A82A5 8BEC             mov ebp,esp
005A82A7 33C9             xor ecx,ecx
005A82A9 51               push ecx
005A82AA 51               push ecx
005A82AB 51               push ecx
005A82AC 51               push ecx
005A82AD 51               push ecx
005A82AE 33C0             xor eax,eax
005A82B0 55               push ebp
005A82B1 6813835A00       push $005a8313
005A82B6 64FF30           push dword 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注入函数的中间,则不能指望像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;

在这种情况下,代码很简单,编译器就可以单独留下eaxSum的优化版本构建代码为:

  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 

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


说实话,内联汇编,作为asm块放在Pascal函数中,不是很有用。关于编写汇编的事情是你需要完全理解寄存器和堆栈的状态。在ABI定义的函数的开头和结尾处定义良好。

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

答案 1 :(得分: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...
      ......