使用Delphi内联汇编程序创建类实例

时间:2012-05-16 13:54:31

标签: delphi inline-assembly basm

我想要做的是,使用程序集,创建一个类实例,调用其中一个方法,然后释放实例。

我知道我错过了一些非常重要的东西,可能很简单,但我不知道是什么。

program Project2;

{$APPTYPE CONSOLE}

uses
  SysUtils;

type
  TSomeClass = class(TObject)
  private
    FCreateDateTime: string;
  public
    constructor Create;
    procedure SayYeah;
  end;

constructor TSomeClass.Create;
begin
  FCreateDateTime := DateTimeToStr(Now);
end;

procedure TSomeClass.SayYeah;
begin
  Writeln('yeah @ ' + FCreateDateTime);
end;

procedure Doit;
asm
  CALL TSomeClass.Create; // <= Access Violation
  CALL TSomeClass.SayYeah;
  CALL TSomeClass.Free;
end;

begin
  try
    Doit;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
  Readln;
end.

仅供参考:我想了解如何在低级别实现这一目标,而不是另一种方式。

更新

感谢Andreas Rejbrand,我找到了罪魁祸首:

UPDATE2

感谢Arnaud使用EBX而不是PUSH / POP EAX找到漏洞

var
  TSomeClass_TypeInfo: Pointer;

procedure Doit;
asm
  MOV DL, $01;
  MOV EAX, TSomeClass_TypeInfo;
  CALL TSomeClass.Create;
  PUSH EAX;
  CALL TSomeClass.SayYeah; // call method
  POP EAX;
  MOV DL, $01;
  CALL TSomeClass.Free; // pointer to instance(Self) is expected in EAX
end;

begin
  TSomeClass_TypeInfo := TSomeClass;
  try
    Doit;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
  Readln;
end.

2 个答案:

答案 0 :(得分:10)

您的asm代码不正确。

您正在重载ebx寄存器,必须保留该寄存器。全局变量技巧没有意义。

更好的编码应该是:

procedure Doit(ClassType: pointer);
asm // eax=TList
  mov dl,true // hidden boolean 2nd parameter
  call TObject.Create
  push eax
  call TList.Pack
  pop eax
  call TObject.Free
end;

DoIt(TList);

但它不能用try...finally来保护实例。 :)

关于mov dl,true参数,请参阅this official page from the EMB wiki

  

构造函数和析构函数使用与其他调用约定相同的调用约定   方法,除了传递一个额外的布尔标志参数   表示构造函数或析构函数调用的上下文。

     

值   构造函数调用的flag参数中的False表示该   构造函数是通过实例对象或使用   继承关键字。在这种情况下,构造函数的行为类似于   普通方法。 a的flag参数中的值为True   构造函数调用表明构造函数是通过一个调用的   课堂参考。在这种情况下,构造函数创建一个实例   Self给出的类,并返回对新创建的引用   EAX中的对象。

     

析构函数调用的flag参数中的值为False   使用inherited关键字调用析构函数。在这   例如,析构函数的行为类似于普通方法。值为True   在析构函数调用的flag参数中指示   析构函数是通过实例对象调用的。在这种情况下,   析构函数释放出Self之前给出的实例   返回。

     

flag参数的行为就像它在所有其他参数之前声明一样   参数。根据注册约定,它在DL中传递   寄存器。在pascal惯例下,它被推到了所有其他之前   参数。在cdecl,stdcall和safecall约定下,它是   在Self参数之前推送。

     

由于DL寄存器指示构造函数还是析构函数   是调用堆栈中最外层的,必须恢复DL的值   在退出之前,以便BeforeDestruction或AfterConstruction可以   称呼得当。

这是一个替代的有效编码,因为eax我们的对象不是nil所以我们可以直接调用析构函数,可能是:

procedure Doit(ClassType: pointer);
asm // eax=TList
  mov dl,true
  call TObject.Create
  push eax
  call TList.Pack
  pop eax
  mov dl,true
  call TList.Destroy
end;

在所有情况下,来自asm的对象访问并不意味着以这种方式完成。您无法直接访问类型信息,因此使用它可能非常困难。使用现有的class实例,您可以使用asm方法执行任何操作;但要创建实例,并使用类类型,asm绝对不是自然的方式!

答案 1 :(得分:2)

您可以在Delphi程序集编程的优秀指南中阅读此内容,最初找到here。很遗憾,该网站已关闭,但您可以找到已归档的版本here。请特别注意page 5