C++反匯編第三講,反匯編中識別虛表指針,以及指向的虛函數地址
講解之前,了解下什么是虛函數,什么是虛表指針,了解下語法,(也算復習了)
開發知識為了不碼字了,找了一篇介紹比較好的,這里我扣過來了,當然也可以看原博客鏈接: http://blog.csdn.net/hackbuteer1/article/details/7558868
一丶虛函數講解(復習開發,熟悉內存模型)
1.復習開發知識
首先:強調一個概念
定義一個函數為虛函數,不代表函數為不被實現的函數。
定義他為虛函數是為了允許用基類的指針來調用子類的這個函數。
定義一個函數為純虛函數,才代表函數沒有被實現。
定義純虛函數是為了實現一個接口,起到一個規范的作用,規范繼承這個類的程序員必須實現這個函數。
1、簡介假設我們有下面的類層次:
class A { public: virtual void foo() { cout<<"A::foo() is called"<<endl; } }; class B:public A { public: void foo() { cout<<"B::foo() is called"<<endl; } }; int main(void) { A *a = new B(); a->foo(); // 在這里,a雖然是指向A的指針,但是被調用的函數(foo)卻是B的! return 0; }
這個例子是虛函數的一個典型應用,通過這個例子,也許你就對虛函數有了一些概念。它虛就虛在所謂“推遲聯編”或者“動態聯編”上,一個類函數的調用並不是在編譯時刻被確定的,而是在運行時刻被確定的。由於編寫代碼的時候並不能確定被調用的是基類的函數還是哪個派生類的函數,所以被成為“虛”函數。
虛函數只能借助於指針或者引用來達到多態的效果。
如果看明白上面的開發知識,則我們可以從內存角度看一下虛函數是怎么樣存在的.
2.從內存角度看虛函數
首先我們學習C++的時候,自學或者老師教學的時候,都有談過一個虛表指針的概念.
那我們要知道什么是虛表指針.
2.1
不帶虛表指針的高級代碼:
class MyTest { public: MyTest(); ~MyTest(); void ShowHelloWorld(); int m_Number; }; MyTest::MyTest() { printf("MyTest::MyTest()\r\n"); } MyTest::~MyTest() { printf("MyTest::~MyTest()\r\n"); } void MyTest::ShowHelloWorld() { printf("Hello World!\n"); } //類聲明在上 int main(int argc, char* argv[]) { MyTest obj; //構造obj對象 obj.ShowHelloWorld();//調用成員函數 obj.m_Number = 1; //成員變量賦值 return 0; }
首先看上圖高級代碼,為什么我們說它沒有虛表指針.我們調試查看.
首先經過我們調試
1.obj在監視窗口中只有一個成員變量,且初始化為CCCCC (Debug下)
2.看對象的所在的地址中,發現只申請了4個字節空間,用來存放成員變量.
2.2帶虛表指針的高級代碼
高級代碼還是其高級代碼,唯一不同的則是在類中給成員函數加了一個關鍵字, virtual,讓此成員函數變為一個虛函數.
內存模型:
我們發現加了之后會額外多出4個字節空間,而且監視窗口中加了一項虛表指針變量.
構造一下繼續觀看內存模型.
構造之后發現已經初始化了虛表指針,那么我們進去這個地址后查看有什么內容.
其內容是一個函數指針表,里面存放了虛函數的地址.不相信的話我們打開反匯編窗口,跟進去則可以看到.
總結:
1.沒有虛表指針
1.1沒有虛函數的情況下沒有虛表指針
2.有虛表指針
2.1虛表指針的產生是看你有沒有 virtual這個關鍵字
2.2虛表指針存儲的是虛表的首地址,虛表可以看做是一個數組
2.3虛表中存儲的是虛函數的地址.
二丶熟悉反匯編中虛表指針,以及還原
既然上面我們熟悉了內存模型,也熟悉了虛函數的原理,那么我們從反匯編的角度下看一下.
例子是我們加了虛函數的例子
Debug下的反匯編
在我們構造的時候,會填寫虛表指針,然后核心代碼就在上面
1.將對象存入一個局部變量保存
2.局部變量中轉給eax
3.對eax取內容填寫虛表地址.
總結就是一句話: 取出對象的首4個字節,填寫虛表.
那么現在好辦了,既然找到了虛表,則可以找到構造,析構,以及虛表中存儲的所有虛函數了.
PS: 此圖和上圖的反匯編一樣,只不過高版本的圖表沒法看,所以用低版本,低版本可以打開.
對其位置下一個引用圖表,誰引用了我,則可以看到調用它的所有構造以及析構了,
1.構造的時候會填寫虛表
2.析構的時候會填寫虛表
圖表:
可以看出分別有個構造和析構.那個是構造那個是析構,我們需要跟過去看一下.
根據以前所講的認識構造和析構的方法,可以很簡單的判別出來.
識別虛函數
既然我們找到了虛表指針,則可以雙擊過去,可以找到虛函數了.
有一個虛函數,確實只有一個,我們跳轉過去看看是不是我們定義的
Debug下有跳轉表
里面的跳轉則是我們的虛函數
總結:
1.識別虛表指針可以在構造中或者析構中查看
2.虛表指針雙擊過去則可以看到所有的虛函數的地址
3.對虛表指針來個引用,(誰引用我)可以看到所有的構造和析構
三丶識別虛函數的調用
熟悉了虛表指針, 通過虛表指針找構造,析構,以及虛表指針指向的虛表找虛函數,那么我們看一下普通成員函數調用和虛函數調用有什么區別.
PS:類聲明不截圖,其普通成員函數不加關鍵字 virtual,其虛函數加virtual
高級代碼:
int main(int argc, char* argv[]) { MyTest test; MyTest &obj = test; //可以虛調用 obj.print(); //普通成員函數 obj.ShowHelloWorld(); //虛函數調用 return 0; }
Debug下的反匯編觀察.
認真觀察可以看出
1.普通成員函數調用,直接Call
2.虛函數調用
2.1 首先獲得虛表指針
2.2 間接調用虛表指針指向的虛表的內容(虛成員函數地址)
總結:
識別調用普通成員函數和虛函數的特征則是
1.普通成員函數直接調用Call
2.虛函數會通過虛表指針指向的虛表來間接調用.