关于VMT地址

s11ss 2011-12-31 10:34:45
unit Unit1;

interface

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, Contnrs;

type
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
S: TLIst;
public
{ Public declarations }
end;

var
Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.FormCreate(Sender: TObject);
begin//此处下断点
S := TList.create
end;

end.


调试时的反汇编代码:
Unit1.pas.27: begin
004552B0 53 push ebx
004552B1 8BD8 mov ebx,eax
Unit1.pas.28: S := TList.create
004552B3 B201 mov dl,$01
004552B5 A17C1D4100 mov eax,[$00411d7c]
004552BA E811E7FAFF call TObject.Create
004552BF 898360030000 mov [ebx+$00000360],eax
Unit1.pas.29: end;
004552C5 5B pop ebx
004552C6 C3 ret
004552C7 90 nop

请问红色部分的地址是肿么得到的?
(我跟进TObject.Create,发现eax、edx实际上是System.pas的_ClassCreate的第一、二个参数)
...全文
301 20 打赏 收藏 转发到动态 举报
写回复
用AI写文章
20 条回复
切换为时间正序
请发表友善的回复…
发表回复
s11ss 2012-01-07
  • 打赏
  • 举报
回复
代码跳动 2012-01-06
  • 打赏
  • 举报
回复
其实我觉得倒也不用那么深入吧 ,看到这类地址,就是个常量, 游戏外挂常说的找基址; 编译后就保存在那里的, 用文件特征码定位可以定位到。 或者用od类调试器加载这个地址的时候访问断点看调用情况。
s11ss 2012-01-06
  • 打赏
  • 举报
回复
我终于搞明白了!

12L 结论修改如下:
因为在创建对象S后,S的头4个字节保存了指向VMT的指针,所以PPointer(S)其实就是指向VMT的指针,进而有Integer(PPointer(S)^) 等于 Integer( TList)。

14L 结论是错误的,4L 的是正确的。

补充说明:
mov eax,[$00411d7c]中的[$00411d7c]才是TList的VMT地址,而$00411d7c是指向Tlist的VMT地址的指针!
erhan 2012-01-05
  • 打赏
  • 举报
回复
implementation

{$R *.dfm}
{$IMAGEBASE $00800000}

加上后地址变成8xxxxx了,缺省是$00400000
武稀松 2012-01-05
  • 打赏
  • 举报
回复
每个类在编译的时候VMT地址就是确定的了.所以mov eax,[$00411d7c]里面写死的常量.
另外如果是DLL的话如果被加载地址不是ImageBase的预定地址,操作系统的加载器会根据DLL的重定位节中所指的地址进行纠偏.就是把这个常量地址+(实际加载地址-ImageBase地址).
一般这种写死的常量地址编译器都会把它放在重定位节中.
蓝色光芒 2012-01-05
  • 打赏
  • 举报
回复
BASM中,不能直接得到类地址(TList),如MOV EAX , TList,当然了LEA指令也是不行的,在BASM中,类地址没被编译进去(类地址直接编译成了[00000000].不知道是否有编译开关可以打开

可以变相引用
如:

Const
PPList : Pointer = TList;

procedure TForm1.FormCreate(Sender: TObject);
var
n : integer;
begin
Memo1.Lines.Add(IntToHex(Integer(TList) , 8));
asm
PUSH PPList;
POP n;
end;
Memo1.Lines.Add(IntToHex(n , 8));
end;
erhan 2012-01-05
  • 打赏
  • 举报
回复
我也搂一眼
蓝色光芒 2012-01-05
  • 打赏
  • 举报
回复
编译器在编译完TList时,地址就已经确定了,就好比你调用函数,函数已经被编译了,地址也就确定了.
不过这个函数是有区别的
s11ss 2012-01-05
  • 打赏
  • 举报
回复
进一步问一下,红色部分的地址在basm中是肿么得到的?

如果真如2L所说,是编译器决定的,那这个地址在basm中就是不能得到的了?
s11ss 2012-01-05
  • 打赏
  • 举报
回复
谢谢哥几个儿的回复,我先根据你们说的研究一下啊,再看看怎么给分!其他童鞋可以继续发表高见哈!
s11ss 2012-01-05
  • 打赏
  • 举报
回复
mov到eax的其实是指向Tlist类的指针地址
--------
我觉得这句说得不对!
mov eax,[$00411d7c]
其实$00411d7c是TList的VMT地址,而mov到eax的[$00411d7c]就是TList的VMT内容的前四个字节,即TList的父类TObject的VMT地址!
至于Integer(PPointer(S)^) + vmtSelfPtr,就是Self指针,而它指向的是TList的VMT地址!
——不知我理解得对不对?

[Quote=引用 4 楼 flcop 的回复:]
mov到eax的其实是指向Tlist类的指针地址,为 Integer(PPointer(S)^) + vmtSelfPtr, 它的值为 PInteger(Integer(PPointer(S)^) + vmtSelfPtr)^ 也就是 Integer( TList),Tlist的入口地址
[/Quote]
erhan 2012-01-05
  • 打赏
  • 举报
回复
一个类实例的生成需要经过对象内存分配、内存初始化、设置对象执行框架三个步骤。
编译器首先调用 System._ClassCreate 进行对象内存分配、内存初始化的工作。而 System._ClassCreate 调用 TObject 类的虚方法 NewInstance 建立对象的实例空间,继承类通常不需要重载 TObject.NewInstance,除非你使用自己的内存管理器,因此缺省是调用 TObject.NewInstance。TObject.NewInstance 方法将根据编译器在类信息数据中初始化的对象实例尺寸(TObject.InstanceSize),调用系统缺省的 MemoryManager.GetMem 过程为该对象在堆(Heap)中分配内存,然后调用 TObject.InitInstance 方法将分配的空间初始化。InitInstance 方法首先将对象空间的头4个字节初始化为指向对象类的 VMT 的指针,然后将其余的空间清零。如果类中还设计了接口,它还要初始化接口表格(Interface Table)。
当对象实例在内存中分配且初始化后,开始设置执行框架。所谓设置执行框架就是执行你在 Create 方法里真正写的代码。设置执行框架的规矩是先设置基类的框架,然后再设置继承类的,通常用 Inherited 关键字来实现。
上述工作都做完后,编译器还要调用 System._AfterConstruction 让你有最后一次机会进行一些事务的处理工作。System._AfterConstruction 是调用虚方法 AfterConstruction 实现的。 在 TObject 中 AfterConstruction 中只是个 Place Holder,你很少需要重载这个方法,重载这个方法通常只是为了与 C++ Builder 对象模型兼容。
最后,编译器返回对象实例数据的地址指针。

-----------------------
摘自:http://hi.baidu.com/xingwei0005/blog/item/8c177200991335df277fb5cb.html
s11ss 2012-01-05
  • 打赏
  • 举报
回复
因为在创建对象S后,S的头4个字节保存了Self指针,而Self指向TList的入口地址(即VMT),所以PPointer(S)其实就是指向VMT的指针,进而有Integer(PPointer(S)^) 等于 Integer( TList)。
——不知我理解得对不对?
[Quote=引用 4 楼 flcop 的回复:]
mov到eax的其实是指向Tlist类的指针地址,为 Integer(PPointer(S)^) + vmtSelfPtr, 它的值为 PInteger(Integer(PPointer(S)^) + vmtSelfPtr)^ 也就是 Integer( TList),Tlist的入口地址
[/Quote]
flcop 2012-01-02
  • 打赏
  • 举报
回复
mov到eax的其实是指向Tlist类的指针地址,为 Integer(PPointer(S)^) + vmtSelfPtr, 它的值为 PInteger(Integer(PPointer(S)^) + vmtSelfPtr)^ 也就是 Integer( TList),Tlist的入口地址
pathletboy 2012-01-02
  • 打赏
  • 举报
回复

procedure TForm1.FormCreate(Sender: TObject);
begin
ShowMessage(IntToHex(Cardinal(TList), 8));
TList.Create;
end;


红色部分$00411d7c实际应为@TList,但是这样语法是错误的,也不需要使用这种表示方法。
frtrnr 2012-01-02
  • 打赏
  • 举报
回复
编译器统筹规划安排的。
s11ss 2012-01-02
  • 打赏
  • 举报
回复
DELPHI中方法的类型及其覆盖、重载 1、静态方法是方法的缺省类型,对它就像对通常的过程和函数那样调用,编译器知道这些方法的地址,所以调用一个静态方法时它能把运行信息静态地链接进可执行文件,所以,静态方法速度最快,但它们却不能被覆盖来支持多态性。 2、虚拟方法和静态方法的调用方式相同。由于虚拟方法能被覆盖,在代码中调用一个指定的虚拟方法时编译器并不知道它的地址,因此,编译器通过建立虚拟方法表(VMT)来查找在运行时的函数地址。所有的虚拟方法在运行时通过VMT来高度,一个对象的VMT表中除了自己定义的虚拟方法外,还有它的祚的所有的虚拟方法,因此虚拟方法比动态方法用的内存要多,但它执行得比较快。 3、动态方法跟虚拟方法基本相似,只是它们的高度系统不同。编译器为每一个动态方法指定一个独一无二的数字,用这个数字和动态方法的地址构造一个动态方法表(DMT)。不像VMT表,在DMT表中仅有它声明的动态方法,并且这个方法需要祖先的DMT表来访问它蓁的动态方法。正因为这样动态方法比虚拟方法用的内存要少,但执行起来罗慢,因为有可能要到祚对象的DMT中查找动态法。 4、OP通过覆盖使一方法在不同的派生类间表现出不同的行为。OP中能被覆盖的方法是在声明时被标识为virtual或dynamic的方法。为了覆盖一个方法,在派生类的声明中用override代替virtual或dynamic。用了override后,编译器就会用新的方法替换VMT中原先的方法,而原先的方法也还存在,而如果用override再次声明一个静态方法,则是真正的覆盖,是用新的方法完全替换在祖先类中的同明方法。 5、方法重载的意义:比如说,你要写一个求两数商的函数(当然只是个比喻),你希望这个函数可以处理所有的数值类型,但PASCAL的运算对类型实行严格检查,你不得不用不同的程序来运算不同类型的数值,这样你就必须为每一种类型写一个同样功能的函数,并使用不同的函数名,坏处我想你是知道的。而重载却可以解决这个问题,同样的函数名,编译器可以用不同的形参类型决定调用哪个函数。Top www.ip8000.com sql8.net
内联函数(Inlining) D7中的inline关键字作为保留字并不会对编译器产生实际作用,在2009中此关键字起到内嵌到代码中起到实际作用。语法如下: function foo: Integer; inline; 内部函数/过程也可以使用,但在D2009测试版中,类方法的内部函数使用inline后不认Self指针;类的子过程/子函数,也可以使用inline关键字,但没有实际效果,且虚方法/继承方法(virtual/override)不能使用。 重载运算符(Operator Overloading) 可以重载部分运算符,如+、-、类型转换等,在D2006只支持到record,但从2007开始支持到Class,以下示例修改自官网: TMyClass = class // Addition of two operands of type TMyClass class operator Add(a, b: TMyClass): TMyClass; // Subtraction of type TMyClass class operator Subtract(a, b: TMyClass): TMyclass; // Implicit conversion of an Integer to type TMyClass class operator Implicit(a: Integer): TMyClass; // Implicit conversion of TMyClass to Integer class operator Implicit(a: TMyClass): Integer; // Explicit conversion of a Double to TMyClass class operator Explicit(a: Double): TMyClass; end; class operator TMyClass.Add(a, b: TMyClass): TMyClass; begin //... end; var x, y: TMyClass begin x := 12; // Implicit conversion from an Integer y := x + x; // Calls TMyClass.Add(a, b: TMyClass): TMyClass end; 类助手(Class Helpers) Helper是对原Class的扩展,是我们在不修改原类的基础上增加类方法,并加入原类的空间中。在Delphi中,对对象的调用实际上采用了两个步骤,首先是把对象地址放入eax寄存器中,然后call类方法,所以如果不使用继承类增加数据的话,用父类调用继承类的方法是没问题的,所以其实这样的方法在D7中也可以使用,但却很麻烦。所以Class Helper起到的就是这个作用,在Class Helper中可以增加的就是与实例无关的内容,所以任何需要增加实例Size的活VMT的功能不能声明,例如变量、虚方法等,但只占用类空间的没关系,如class var。在应用上我们可以通过这种方法方便的给VCL一类控件加上某个属性。 TFoo = class helper for TControl private function GetA: Integer; public class var X: Integer; procedure MSG(var Message: TMessage); message WM_MYMESSAGE; procedure ProcFoo; property A: Integer read GetA; end; // ... procedure TForm1.Foofoo; begin ProcFoo; // TControl -> TWinControl -> TScrollingWinControl-> TCustomForm -> TForm -> TFrom1: Call TFoo.ProcFoo end; strict关键字(Keyword “strict”) 众所周知,在Delphi中,类的private和protected域中的变量可以被同一单元中可以自由的被访问(Delphi的类没有“友元”的概念,但同一个unit中可以说自动友元化了),而并非是真正的私有或只能被继承类访问。而strict关键字的作用就是使该内容变成严格OO意义上的private/protected作用域,这点没有什么多说的。语法: strict private // Blah... strict protected // Blah... 结构方法(Records with Methods) 也没什么特别的,就是和class差不多,就一个不用创建和销毁、不能继承、没有作用域之类的类,很容易掌握,所以这里就不多介绍了。但是很有意思的是带参数的constructor可以通过编译,可能是为了初始化的方便吧。 抽象类和固实类(Abstract and Sealed Classes) 这两个概念在OO中也并不陌生,抽象类是不应该创建实例的(但D2006起的编译器就不给检查,连个Warning都没有,这还有啥用啊 -.- ),而固实类是不能被继承的。语法: TAnAbstractClass = class abstract // or (TParentClass) // Blah... end; TASealedClass = class sealed(TAnAbstractClass) // or empty // Blah... end; 类常量、类变量、类属性与静态类方法(Class const/var/property and Static Class Methods) 老的Delphi中只提供了类方法,而没有提供类变量、类常量和类属性,这真的是很不方便。这里先区分一下我所使用的类(Class)和对象(Object)即类的实例(Instance of Class)。当在Delphi中声明一个类的时候,这个类是有实际地址的,该地址记录了许多类的相关信息,比如实例的Size啊、虚方法信息啊一堆东西,而创建一个对象的时候则把类实例化,在堆(Heap)中分配一块地址,包括内部数据和VMT之类的东西。在调用实例的时候,首先要知道对象地址,然后才能访问内部变量和调用方法时使用Self指针即实例地址;而在调用类方法的时候,eax中的并不是实例的地址而是类的地址,然后再call方法,这时的Self指针并非实例地址而是类地址。所以对于每一个类和继承类来说,包括它和它的继承类的所有实例,类变量、常量都是同一个,这样就存在了一个唯一的可供使用的变量或常量,方便同步并且不需要使用较多的内存(可以参考C#中的类,不过C#中不允许从实例直接访问类变量、常量、方法)。而静态类方法则是在使用这个类方法的时候不传入class地址,也就是说没有Self指针,这样的类方法的访问开销要小于普通的类方法;这自然也就意味着,该类方法不能被继承(不能virtual/override)。另外,类属性的get/set方法必须使用静态类方法。 TFooClass = class private class procedure SetFoo(const Value: Integer); static; // A Static Class Method protected class var FX : Integer; // class var public const FC: Integer = 10; // class const class procedure VirtualProc; virtual; class property X: Integer read FX write FX; // class property class property Foo: Integer read FC write SetFoo; end; 类内部类型与嵌套类(Class Types and Nested Classes) 可以说现在的Class的域几乎相当于原来的整个unit,以前不能放里面的元素现在都可以放里面了,这个也没什么好多说的,试验一下就很容易明白了。 终方法(Final Methods) 这个也是建立在虚方法的基础上的,在override后使用final关键字,则表示该虚方法不能再被子类继承下去了。 TAClass = class public procedure Foo; virtual; end; TFinalMethodClass = class(TAClass) public procedure Test; override; final; // A Final Method end; For-in循环(For-in Loop) 这个应该是受.Net影响吧,支持遍历一个数组或提供了GetEnumerator函数的类。GetEnumerator要求返回一个类的实例,该类包含有Current属性和MoveNext方法。 procedure Foo(List: TStrings); i : Integer; lst : array[0..100]of Integer; s : string; begin for i in lst do ; for s in List do ; // Support of TStrings.GetEnumerator end;

16,748

社区成员

发帖
与我相关
我的任务
社区描述
Delphi 语言基础/算法/系统设计
社区管理员
  • 语言基础/算法/系统设计社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧