基於ARM處理器的反彙編器軟體簡單設計及實現

来源:http://www.cnblogs.com/chenpi/archive/2016/02/18/5199226.html
-Advertisement-
Play Games

寫在前面 2012年寫的,僅供參考 反彙編的目的 缺乏某些必要的說明資料的情況下, 想獲得某些軟體系統的源代碼、設計思想及理念, 以便複製, 改造、移植和發展; 從源碼上對軟體的可靠性和安全性進行驗證,對那些直接與CPU 相關的目標代碼進行安全性分析; 涉及的主要內容 分析ARM處理器指令的特點,以


寫在前面

2012年寫的,僅供參考

反彙編的目的

缺乏某些必要的說明資料的情況下, 想獲得某些軟體系統的源代碼、設計思想及理念, 以便複製, 改造、移植和發展;

從源碼上對軟體的可靠性和安全性進行驗證,對那些直接與CPU 相關的目標代碼進行安全性分析;

涉及的主要內容

  1. 分析ARM處理器指令的特點,以及編譯以後可執行的二進位文件代碼的特征;
  2. 將二進位機器代碼經過指令和數據分開模塊的加工處理;
  3. 分解標識出指令代碼和數據代碼;
  4. 然後將指令代碼反彙編並加工成易於閱讀的彙編指令形式的文件;

下麵給出個示例,彙編源代碼,對應的二進位代碼,以及對應的反彙編後的結果

源代碼:

二進位代碼:

 

反彙編後的結果:

 

反彙編軟體要完成的工作就是在指令和數據混淆的二進位BIN文件中,分解並標識出指令和數據,然後反彙編指令部分,得到易於閱讀的彙編文件,如下圖:

ARM體繫結構及指令編碼規則分析

略,請參考相關資料,如ARM Limited. ARM Architecture Reference Manual [EB/OL]. http://infocenter.arm.com/等;

主要可參考下圖,ARM指令集的編碼:

ARM可執行二進位BIN文件分析

目前主要的ARM可執行文件種類:

ELF文件格式:Linux系統下的一種常用、可移植目標文件格式;

BIN文件:直接的二進位文件,內部沒有地址標記,裡面包括了純粹的二進位數據;一般用編程器燒寫時,從0開始,而如果下載運行,則下載到編譯時的地址即可;

HEX格式:Intel HEX文件是記錄文本行的ASCII文本文件;

本文主要研究BIN文件的反彙編;

BIN映像文件的結構

  ARM程式運行時包含RO,RW,ZI三部分內容,RO(READONLY),是代碼部分,即一條條指令,RW(READWRITE),是數據部分,ZI,是未初始化變數。其中RO和RW會包含在映像文件中,因為一個程式的運行是需要指令和數據的,而ZI是不會包含在映像文件的,因為其中數據都為零,程式運行前會將這部分數據初始化為零。
  ARM映像文件是一個層次性結構的文件,包括了域(region),輸出段(output section)和輸入段(input section)。一個映像文件由一個或者多個域組成,每個域最多由三個輸出段(RO,RW,IZ)組成,每個輸出段又包含一個或者多個輸入段,各個輸入段包含了目標文件中的代碼和數據。

  • 域(region):一個映像文件由一個或多個域組成。是組成映象文件的最大結構。所謂域指的就是整個bin映像文件所在的區域,又分為載入域和運行域,一般簡單的程式只有一個載入域。
  • 輸出段(output section):有兩個輸出段,RO和RW。
  • 輸入段(input section):兩個輸入段,CODE和DATA部分,CODE部分是代碼部分,只讀的屬於RO輸出段,DATA部分,可讀可寫,屬於RW輸出段。

ARM的BIN映像文件的結構圖

舉一個例子,ADS1.2自帶的examples里的程式

AREA Word, CODE, READONLY       ; name this block of code
num     EQU     20                ; Set number of words to be copied
        ENTRY                     ; mark the first instruction to call
start
        LDR     r0, =src            ; r0 = pointer to source block
        LDR     r1, =dst            ; r1 = pointer to destination block
        MOV     r2, #num            ; r2 = number of words to copy
wordcopy
        LDR     r3, [r0], #4            ; a word from the source
        STR     r3, [r1], #4            ; store a word to the destination
        SUBS    r2, r2, #1             ; decrement the counter
        BNE     wordcopy             ; ... copy more
stop
        MOV     r0, #0x18           ; angel_SWIreason_ReportException
        LDR     r1, =0x20026        ; ADP_Stopped_ApplicationExit
        SWI     0x123456            ; ARM semihosting SWI
        AREA BlockData, DATA, READWRITE
src      DCD     1,2,3,4,5,6,7,8,1,2,3,4,5,6,7,8,1,2,3,4
dst      DCD     0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
        END

可以看出,該程式由兩部分組成,CODE和DATA,即代碼部分和數據部分。其中代碼部分,READONLY,屬於RO輸出段;數據部分,READWRITE,屬於RO輸出段。

接下來再看看上述代碼經過編譯生成的BIN映像文件的二進位形式,及該映像文件反彙編後的彙編文件,如下圖:

   

  從圖中我們很容易發現,BIN文件分成了兩部分,指令部分和數據部分。先看一下左圖,從中我們發現,BIN文件的第一條指令編碼是0xe59f0020,即右圖中的00000000h到00000003h,由於存儲方式的原因,小端模式,指令的低位元組存放在低地址部分,不過這不影響我們的分析。在BIN文件中從00000000h開始一直到00000027h都是指令部分,即RO輸出段,最後一條指令0xef123456存儲在在BIN文件的00000024h到00000027h。剩下的為數據部分,即RW輸出段,有興趣的讀者可以對照源代碼一一查找之間的對應關係。

ARM反彙編軟體設計要解決的主要問題

一、指令與數據的分離

  馮·諾依曼機器中指令和數據是不加區別共同存儲的,以 0、1 二進位編碼形式存在的目標代碼對於分析人員來說,很難讀懂其含義。二進位程式中指令和數據混合存放,按地址定址訪問,反彙編如果採取線性掃描策略,將無法判斷讀取的二進位編碼是指令還是數據,從而無法實現指令和數據的分離。
  那麼,怎樣才能實現指令和數據的分離?
  眾所周知,凡是指令,控制流是必經之處,凡是數據,數據流是必到之處,存取指令一定會訪問,對於一般指令,控制流是按地址順序遞增而走向的,只有在出現各種轉移指令時,控制流才出現偏離。因此,抓住控制流這一線索,即跟蹤程式的控制流[9]走向而遍歷整個程式的每一條指令,從而達到指令與數據分開的目的。
    怎樣才能跟蹤程式的控制流呢?
  一般來說控制流與控制轉移指令有關,控制轉移指令一般可分為兩大類:

  1. 單分支指令,即直接跳轉,如B;BL;MOV PC,**;LDR PC,**等等;
  2. 雙分支指令,有條件的跳轉,如BNE;MOVNE  PC,**等等

      

  當該指令為雙分支指令時,會有兩個轉向地址,我們把條件滿足時的轉向地址稱為顯示地址,條件不滿足時的地址稱為隱式地址。
     在跟蹤控制流的過程中,還要設置三個表:
  (1)段表,將所有轉移指令除條件轉移的轉移地址填入此表包括本指令地址和轉向地址。其實可以不要這個表,但是加進去,會得到若幹段的代碼段,比較清晰明瞭。
  (2)返回表,用於記錄程式調用時的返回地址。
  (3)顯示表,碰到雙分支指令時,將其顯示地址和現場(程式各寄存器的值)填入該表中。
  以上都準備好之後,就可以開始跟蹤程式控制流了具體步驟如下:

    (1)將程式的起始地址填入段表,且作為當前地址。
    (2)按當前地址逐條分析指令類型,若非無條件轉移指令及二分支指令,則直至終結語句並轉(7), 否則轉(3)。
    (3)若為無條件轉移指令(B指令,MOV PC,0x16等),則將此指令所在地址填段表,其顯式地址填段表,且將顯式地址作為當前地址,然後轉(2),否則轉(4)。
    (4)若為無條件轉移指令子程式調用指令(BL指令),則將此指令所在地址填段表,返回地址和現場信息填入返回表,顯式地址填段表,且將顯式地址作為當前地址,然後轉 (2), 否則轉(5)。
    (5)若為無條件轉移指令中的返回指令(MOV PC,LR),則在返回地址表中按“後進先出”原則找到返回地址,將此指令所在地址填段表,其返回地址填段表,且將返回地址作為當前地址,然後轉(2),否則轉(6)。
    (6)若為二叉點指令(BEQ,MOVEQ PC,0x16等等), 則將顯式地址和現場信息填入顯式表,然後將隱式地址作為當前地址轉(2)。
    (7)判顯式表空否,若空則演算法終止,否則從顯式表中按“先進先出”順序取出一個顯式地址作為當前地址,且恢復當時的現場信息轉(2)。

  經過以上處理,可以遍歷到所有的指令,且當訪問到該條指令後,要把改地址處的指令標記為指令。接下來可以採用線性掃描策略,當遇到標記為指令的二進位代碼時,把它反彙編成彙編指令即可。

  不過,在實現跟蹤程式控制流過程中還有一個比較難處理的問題,就是間接轉移類指令的處理,因為這類指令的轉移地址隱含在寄存器或記憶體單元中,無法由指令碼本身判斷其值,而且這些隱含在寄存器內或記憶體單元中的值往往在程式執行時,被動態地進行設置或修改,因此很難判斷這類指令的轉移地址,從而難以完整的確定程式的指令區。
    本軟體處理這個問題的方法是設置多個寄存器全局變數,在緊跟程式控制流的過程中,實時更新各寄存器的值,因此可以確定這類指令的轉移地址。

二、代碼部分的反彙編

  ARM指令集中的每天指令都有一個對應的二進位代碼,如何從二進位代碼中翻譯出對應的指令,即代碼部分反彙編所要完成的工作。

  ARM 指令的一般格式可以表示為如下形式:

  <opcode>{condition}{S}<operand0>{!},<operand1>{, <operand2>}

  指令格式中<·>符號內的項是必須的,{·}符號內的項是可選的,/ 符號表示選其中之一,其中opcode 表示指令操作符部分,尾碼 conditon、S 及!構成了指令的條件詞,operand0、operand1、operand2 為操作數部分。指令反彙編就是將指令的各組成部分解析出來。

  為了使指令反彙編更加清晰、高效,可以採用分類的思想方法來解決該問題,把那些具有相同編碼特征的指令分成同一種類型的指令,然後為每一類指令設計一個與之對應的處理函數。

  指令的反彙編的步驟,首先是判斷哪條指令,可以由操作符及那些固定位來確定,然後是指令條件域的翻譯,這個與二進位編碼也有唯一的對應關係,然後操作數的翻譯,在操作數的翻譯過程中,可能涉及到各種操作數的計算方法,移位操作等情況。

ARM反彙編軟體的設計

ARM反彙編軟體的總體設計方案如下圖:

       其中,各模塊主要完成以下功能:

  • 輸入模塊:要解決如何從外部文件中讀取二進位格式的文件,另外對讀進來的目標代碼要合理組織和存儲,便於接下來的後續處理。
  • 指令和數據分離模塊:在記憶體中對讀進來的二進位源代碼進行分析,指令流可以到達的部分標識為代碼,最後剩下的指令流未經過的部分為數據,這樣就分離出代碼和數據。
  • 指令反彙編模塊:對分離出來的代碼段部分,按照各條指令編碼的對應關係進行反彙編,生成目標代碼對應的彙編文件,包括ARM指令地址,ARM指令,可以用於閱讀。
  • 輸出模塊:即要將反彙編後的彙編文件顯示在視窗,且可以生成文件在磁碟上。

ARM處理器反彙編軟體流程

ARM反彙編軟體的整體工作流程如下圖所示,首先是讀取目標二進位文件至記憶體,然後採用兩遍掃描的策略,第一遍掃描的目的是區分指令和數據,第二遍掃描是將指令部分反彙編成彙編指令形式,將數據部分直接翻譯成數據,最後將結果輸出顯示。

模塊設計-輸入模塊

輸入模塊的流程圖如下圖所示,首先從目標二進位代碼中讀取四個位元組存放到Content對象里,再將Content對象存放到Vector容器里,然後按上述操作繼續讀取文件,直到文件結尾。這裡有一點要說明的是,由於時間等原因,本軟體只考慮32位ARM指令的反彙編,Thumb指令反彙編不會涉及,所以每次都讀取4個位元組。

模塊設計-指令和數據分離模塊

指令和數據分離模塊的設計如下圖所示,由於指令和數據分離模塊設計的關鍵是跟蹤程式的控制流,標識出每一條指令,所以此模塊的關鍵就是要遍歷每一條指令,而遍歷每一條指令的關鍵是要緊跟PC值。

關於段表,顯示表,返回表的概念前面已經說明過了。另外,在程式流程圖中的“根據具體轉移指令,更新PC值,顯示表,返回表”,這裡的具體情況如下:

(1)若為無條件轉移指令(B指令等,MOV PC,0x16),則將此指令所在地址填段表,其顯式地址填段表,且將顯式地址作為當前PC地址。

(2)若為無條件轉移指令子程式調用指令(BL指令),則將此指令所在地址填段表,返回地址填入返回地址表,顯式地址填段表,且將顯式地址作為當前PC地址

(3)若為無條件轉移指令中的返回指令(MOV PC,LR),則在返回地址表中按“後進先出”原則找到返回地址,將此指令所在地址填段表,其返回地址填段表,且將返回地址作為當前PC地址。

(4)若為二叉點指令(BEQ,MOVEQ PC,0x16等等), 則將顯式地址填入顯式地址表(還要保存當時的寄存器值),然後將隱式地址作為當前PC地址。

模塊設計-反彙編模塊

在反彙編模塊中除了要反彙編指令,還要翻譯數據。總的設計思想是依次從裝滿對象的contentVector容器中依次取出對象,判斷該對象是指令還是數據,指令的話,就反彙編成彙編指令形式,數據的話,直接翻譯成數據的值,如下圖所示。

上述流程圖是總的反彙編模塊,在圖中的指令反彙編部分,是該模塊的重點,也是整個反彙編軟體設計的重點,其流程圖如下圖所示。

模塊設計-輸出模塊

關於顯示模塊,比較簡單,直接把結果顯示出來即可,該模塊跟第三個模塊反彙編模塊聯繫最緊密,在反彙編模塊中,其實已經包含了輸出模塊。不過輸出模塊也有自己的特殊任務。比如說如何以十六進位形式顯示一個32位數(顯示地址的時候)以及如何顯示一個8位數(顯示讀進來數據的編碼,因為是一個位元組一個位元組讀進來的,存放的時候也是一個位元組一個位元組存放在Content對象中),如下圖就是一個顯示32位數的流程圖。

ARM反彙編軟體的具體實現

  ARM反彙編軟體主要有四個大模塊組成,二進位可執行代碼讀取模塊、指令和數據分離模塊、指令反彙編模塊、輸出模塊。本章節將結合程式中的源代碼主要介紹ARM反彙編軟體各模塊具體實現。由於時間等因素影響,本軟體設計只考慮到了ARM指令,THUMB指令不在考慮範圍,不過其實都是同樣道理的事情,ARM指令能夠實現的話,THUMB指令添加進去只是時間的問題。

一、數據組織方式

讀進來的內容的組織如下所示:

class Content
{
public:
        unsigned int            addr;                //地址
        bool                isInstruction;        //指令標記
        bool                isVisited;            //訪問標記    
        bool                isHasLable;            //是否有標簽標記
        unsigned int         firstB;                //第1個位元組
        unsigned int         secondB;
        unsigned int         thirdB;
        unsigned int             fourthB;
};

在該類中,addr表示該內容的地址,是邏輯位元組地址;isInstruction判斷該內容是否是指令代碼;isVisited判斷是否被訪問過;isHasLable,判斷該指令前面要不要加一個標簽,firstB表示內容從左到右表示的第一個位元組,secondB、thirdB、fourthB以此類推。

讀源文件類

class MyRead
{
public:
           vector <Content>  contentVector;        //內容存儲容器
        void readSourceFile(char * addr);        //讀取源文件函數
};

內容容器contentVector,用於存儲從源文件讀進來的內容,4個位元組為一個元素;readSourceFile方法,讀取源文件二進位代碼,並按個位元組一個存儲在容器里。

指令編碼內容的組織

指令編碼的組織其實還是很關鍵的,好的組織方式可以大大節約後續工作的時間,後續開發或者維護都會變得更簡單。由於篇幅關係,這裡將介紹一些比較常用到的指令內容的組織。這裡介紹的指令內容的組織都將採用結構體的形式,其實也可以用類來實現。

typedef unsigned  int QByte;                    //32位無符號數

(1)SWI指令

typedef struct swi                                
{
        QByte comment : 24;
        QByte mustbe1111 : 4;
        QByte condition : 4;
} SWI;

SWI指令的編碼格式:

由指令的編碼格式我們可以看到,comment的內容表示immed_24,正好24位;mustbe1111是固定的,所有SWI指令的[27:24]都是1111,condition表示條件域,總共有16種情況,用4位表示即可。

(2) 分支指令

typedef struct branch {        
        QByte offset : 23;
        QByte sign : 1;
        QByte L : 1;
        QByte mustbe101 : 3;
        QByte condition : 4;
} Branch;

分支指令的編碼格式:

從指令的編碼格式中,我們發現,Offset其實是確定了轉向地址的絕對值;sign表明正負數;L用於區分是否要把返回地址寫入R14中;mustbe101表明這是一條分支指令,固定的,cond是條件域。

(3)載入/存儲指令

typedef struct singledatatrans {    
        QByte offset : 12;
        QByte Rd : 4;
        QByte Rn : 4;
        QByte L : 1;
        QByte W : 1;
        QByte B : 1;
        QByte U : 1;
        QByte P : 1;
        QByte I : 1;
        QByte mustbe01 : 2;
        QByte condition : 4;
} SingleDataTrans;

載入/存儲指令編碼格式:

其中offset, I, P, U, W, Rn共同決定計算出記憶體中的地址,Rd是目的寄存器,

L位用於區分是LDR還是STR指令;B位用於區分unsigned byte (B==1) 和 a word (B==0)。

(4)數據處理指令

typedef struct dataproc {
        QByte operand2 : 12;
        QByte Rd : 4;
        QByte Rn : 4;
        QByte S : 1;
        QByte opcode : 4;
        QByte I : 1;
        QByte mustbe00 : 2;
        QByte condition : 4;
} DataProc;

對應的編碼格式如下:

其中operand2是第二操作數,其計算方式也有多種形式,可參考ARM手冊上的“Addressing Mode 1 - Data-processing operands on page A5-2”;Rd為目的寄存;Rn為第一操作數;S位用於區分是否影響CPSR;opcode用於明確是那種數據操作,如MOV,ADD等;I用於區分第二操作數是寄存器形式還是立即數形式;mustbe00是固定的;Cond為條件域。

段表、顯示表、返回表的組織

為簡單起見,本程式中這些表統統用容器Vector來實現

typedef struct Elem{
         int re[16];
          unsigned int addr;
}Elem;
static vector <unsigned int>     segmentTable;        //段表
static vector < unsigned int >             returnAddrTable;        //返回表
static vector <Elem>         showAddrTable;        //顯示表

如上所述,其中Elem結構體用於存儲顯示表裡面的元素。segmentTable為段表,returnAddrTable為返回表,showAddrTable為顯示表。

所有的填表操作都是用push_back 函數來實現的,還有就是操作顯示表和返回表時要記住返回表是後進先出的。

指令共用體

typedef union access32 {
        QByte qbyte;
        SWI swi;                            //SWI instruction
        CPRegTrans cpregtrans;             // Coprocessor registesr transfers
        CPDataOp cpdataop;                    // Coprocessor data processing
// Coprocessor load/store and double register transfers[6]
        CPDataTrans cpdatatrans;  
//Branch and branch with link and change to Thumb
        Branch branch;                        
        Undefined4  undefined4;                //Undefined Ins[4]
        LoadStoreMultiple loadstoremultiple;    //load/store multiple
        Undefined47 undefined47;            //Undefined instruction [4,7]
        Undefined   undefined;                //Undefined instruction
//Load,store immediate offset,Load,store register offset
        SingleDataTrans singledatatrans;
        MSRImeToSReg  msrimetosreg;        //Move immediate to status register
        Undefined3    undefined3;                //Undefined instruction [3]
        DataProc dataproc;
        DataProcImmediate dataprocimme;
        MultiplyExtraLoadStore  multiextraloadstore;    //Multiplies, extra load/stores: 
        MiscellanesInstructOne    miscelinstrone;        //Miscellaneous instructions
        DataProcRegShift        dataprocregshift;
        MiscellanesInstructTwo    miscelinstrtwo;        //Miscellaneous instructions:            DataProcImmeShift     dataprocimmeshit;
} Access32;
View Code

這個指令共用體的設置應該說是十分巧妙的,涵蓋了所有類型的指令。在反彙編過程中,首先是從目標二進位代碼中讀取一個32位數到記憶體,然後將這個32位數從記憶體拷貝到共用體變數中,由於共用體的存儲空間是共用的,定義一個上面的共用體,其實也就只是4個位元組大小的空間,但是他可以指代任何一條指令,因此用這個共用體變數來判斷讀進來的32位數是那條指令非常方便和直觀,具體判斷的將在接下來的指令反彙編模塊介紹。

其他數據結構

協處理器寄存器:

static const char *cregister[16] = {    
        "cr0",  "cr1",  "cr2",  "cr3",
        "cr4",  "cr5",  "cr6",  "cr7",
        "cr8",  "cr9",  "cr10", "cr11",
        "cr12", "cr13", "cr14", "cr15"
};

寄存器:

static const char *registers[16] = {
        "R0", "R1", "R2", "R3",
        "R4", "R5", "R6", "R7",
        "R8", "R9", "R10","R11",
        "R12", "R13", "R14", "PC"
};

條件域:

static const char *condtext[16] = {
        "EQ",                    //"Equal (Z)" 
        "NE",                    //"Not equal (!Z)" 
        "CS",                    //"Unsigned higher or same (C)" },
        "CC",                    //"Unsigned lower (!C)" },
        "MI",                    //"Negative (N)" },
        "PL",                    //"Positive or zero (!N)" },
        "VS",                    //"Overflow (V)" },
        "VC",                    // "No overflow (!V)" },
        "HI",                    // "Unsigned higher (C&!Z)" },
        "LS",                    //"Unsigned lower or same (!C|Z)" },
        "GE",                    // "Greater or equal ((N&V)|(!N&!V))" },
        "LT",                    //"Less than ((N&!V)|(!N&V)" },
        "GT",                    //"Greater than(!Z&((N&V)|(!N&!V)))" },
        "LE",                    //"Less than or equal (Z|(N&!V)|(!N&V))" },
        "",                        //"Always" },         //AL,可以省略掉
        "NV"                   //, "Never - Use MOV R0,R0 for nop" }
};

數據處理指令的操作碼編碼,從0到15,之所以弄成以下形式是為了增加程式的可讀性。

typedef enum operatecode
{
        AND,EOR ,SUB ,RSB                    //AND=0;    EOR=1.
           ,ADD,ADC ,SBC ,RSC
           ,TST, TEQ,CMP,CMN
           ,OPR ,MOV ,BIC,MVN
}OperCode;

數據處理指令字元:

static const char *opcodes[16] =
{
        "AND", "EOR", "SUB", "RSB",
        "ADD", "ADC", "SBC", "RSC",
        "TST", "TEQ", "CMP", "CMN",
        "ORR", "MOV", "BIC", "MVN"
};

註意裡面的順序,要跟其編碼規則對應。

二、二進位可執行代碼讀取模塊

二進位可執行代碼的讀取模塊主要完成從外部文件中讀取二進位代碼到記憶體中,並要合理組織數據。

讀取函數如下所示:

void MyRead::readSourceFile(char *addr)
{
        ifstream f1(addr,ios::binary);
        if(!f1)
        {
            cerr<<addr<<"不能打開"<<endl;
            return;
        }
        unsigned char x;    
        int k=0,i=0;
        while(f1.read(( char *)&x,1))
        {
            Content temp;                // 定義一個Content臨時對象
            if(i==0)                
            {
                temp.fourthB=x;            //一個字里的第4個位元組
            }
            else if(i==1)
            {
                temp.thirdB=x;         //一個字里的第3個位元組

        }
        else if(i==2)
        {
            temp.secondB=x;    //一個字里的第2個位元組
        }
        else if(i==3)                
        {
            temp.firstB=x;        //一個字里的第1個位元組
        }
        i++;
        if(i==4)                
        {
            i=0;
            temp.addr=4*k;         //給地址賦值
            temp.isVisited = false;
            temp.isHasLable = false;
            k++;
            this->contentVector.push_back(temp);        //將temp存放到容器里
            Content temp;
        }
    }
    f1.close();
}
View Code

讀取模塊首先要做的是打開目標文件,然後一個位元組一個位元組的讀取進來,這裡所謂的第一個位元組FirstB指的是一個32位數從左往右數算起第一個的,總共四個位元組。然後要設置一個Content 類型的temp中間值,將讀進來的位元組依次往temp賦值,賦完四個位元組後,把地址,是否訪問標誌等等也都初始化一下;最後將這個temp中間值壓入contentVector容器中,這個容器裡面存放的就是目標代碼讀進來的二進位代碼,四個位元組為一個元素存放。

三、指令和數據分離並標識模塊

前面已經講了指令和數據分離的思想,主要是跟蹤程式的控制流,這裡講結合程式的實現繼續說明一下。首先看一下指令和數據分離的函數。

void MyDisassemble::separateI_D(MyRead &read)
{
        int k = 0;
        segmentTable.push_back(0);                        
        r[15] = 0;            //PC設為0
        disPart(read);
        while(true)
        {
            if(k == showAddrTable.size()) break;        //顯示表都訪問結束
            for(int i = 0;i<16;i++) r[i] = showAddrTable[k].re[i];    //恢復現場
            if(read.contentVector[((showAddrTable[k].addr)/4)].isVisited == false) 
//是否訪問 
disPart(read);    
                k++;
        }
}
void MyDisassemble::disPart(MyRead &read)
{
            for(int i =r[15]/4;;i = r[15]/4)
            {
                unsigned int foo=((read.contentVector[i].firstB)<<24)+
((read.contentVector[i].secondB)<<16)+
((read.contentVector[i].thirdB)<<8)+read.contentVector[i].fourthB;
                read.contentVector[i].isInstruction = true;    //設置是指令                            read.contentVector[i].isVisited = true;    //已訪問過
                disArm(foo,read);        //反彙編指令
                if ((0x0F&read.contentVector[i].firstB)==0x0f) //程式結束
                {    
                    segmentTable.push_back(r[15]);                break;
                }
                r[15]=r[15]+4;                                                }
}
View Code

上述separateI_D函數的目的是為了跟蹤程式的控制流,遍歷每一條指令(有點類似有向圖的遍歷),從而標識出指令。首先是segmentTable.push_back(0),將起始地址填入段表;然後按當前地址逐條分析指令類型disPart(read);在disPart函數中,當訪問這條指令時,首先要做的是把該指令的isInstruction和isVisited標誌設置為true;然後disARM,disARM是反彙編函數,這個函數可以反彙編出該條指令是哪條指令,在下麵的反彙編模塊將會有更加詳細的說明,在這裡只要知道他可以識別出是哪條指令即可。

當反彙編出來的指令是無條件轉移指令時,以B指令為例,要執行以下代碼:

segmentTable.push_back(r[15]);            
    segmentTable.push_back(r[15] + addr);    
r[15]=(r[15]+addr-4);    
    read.contentVector[(r[15]/4)].isHasLable = true;

r[15]指pc,r[15] + addr為顯示地址。(1)首先將此指令所在地址填入段表,(2)然後顯示地址填入段表,(3)再然後顯示地址作為當前地址,由於已經設置PC自動加了,所以這裡先減一下,(4)同時轉向地址處的指令前面是一個標簽,把isHasLable設置為true;

       若為無條件轉移指令子程式調用指令,以BL為例,要執行以下代碼:

r[14] = r[15] + 4;                    
    segmentTable.push_back(r[15]);            
    returnAddrTable.push_back(r[14]);        
    segmentTable.push_back(r[15] + addr);    
    r[15]=(r[15]+addr-4);
    read.contentVector[((r[15]+4)/4)].isHasLable = true;

各條語句的意思依次是

(1)保存返回地址到R14  

(2)此指令所在地址填入段表

(3)返回地址填入返回地址表

(4)顯示地址填入段表

(5)顯示地址作為當前地址,由於已經設置PC自動加了,所以這裡先減一下

(6)該地址處指令前面有個標簽。

若為無條件轉移指令中的返回指令,以MOV PC,LR為例,如下:

unsigned int raddr;
    if(returnAddrTable.size()>0) {
            raddr = returnAddrTable[returnAddrTable.size()-1];  
return AddrTable.pop_back(); } segmentTable.push_back(r[
15]); segmentTable.push_back(raddr); r[15] = raddr-4; read.contentVector[(raddr/4)].isHasLable = true;

依次做了以下事情,

(1)返回地址表中按“後進先出”原則找到返回地址

(2)刪除返回表最後一個元素

(3)指令所在地址填段表

(4)返回地址填段表

(5)設置當前的PC值

(6)設置標簽標誌

若為二分支指令,以BNE為例,如下:

Elem temp;temp.addr = r[15] + addr; for(int ii=0;ii<16;ii++) temp.re[ii] = r[ii];
    showAddrTable.push_back(temp);    
    read.contentVector[(( r[15] + addr)/4)].isHasLable = true;

依次做了以下事情,

(1)保存“現場”

(2)顯式地址填入顯式表(包括當時的寄存器值)

(3)設置標簽標誌。

       若不是轉移指令,則繼續按PC值自增下去,直到終結語句,然後還要判斷顯示表是否空,這裡用了個K變數來實現,實際上並沒有真的從容器中刪除取出的顯示表裡的元素。if(k == showAddrTable.size()) break;用來判斷是否顯示表裡的地址都訪問過了。如果沒有,則繼續按這條指令的地址disPart下去。直到把顯示表裡的地址都訪問一遍。

       執行完以上的代碼後,就實現了按控制流遍歷每條指令,是指令的基本上也都做上了標識。

四、指令反彙編模塊

指令反彙編模塊在巨集觀上主要體現在disArm這個函數上,代碼如下:

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 講的是mysql的命令行程式依賴【<】,【>】的事。我的觀點是一個命令行程式,不應該依賴【<】,【>】,這是錯誤的設計,且其他程式沒有這賴皮習慣。歡迎凡是玩資料庫的園友參與討論。
  • 原創文章,轉載必需註明出處:http://www.ncloud.hk/%E6%8A%80%E6%9C%AF%E5%88%86%E4%BA%AB/introduce-for-sqlserver-s-cursor-1/ 最近一段時間做項目寫的T-Sql代碼老是用到游標(Cursor),所以就研究研究它的
  • 1Led硬體原理簡單介紹 Led的電路比較簡單,一般是使用三極體搭建一個控制電路。如下圖所示,是原理圖中兩個Led的控制電路。KP_COL0和VDD50_EN網路控制Led的通斷。
  • 在free命令中有個參數l,它表示 show detailed low and high memory statistics。其實最先是對High Memory總是為零有些不解(Linux是64為)。其實更不解的是關於low memory、high memory。那麼關於low memory和hig...
  • 1. 常用命令 ls 顯示當前目錄下的文件和文件夾; -ltr 按時間順序顯示文件和文件夾的詳細信息,不帶參數的時候 只顯示文件夾和文件。 vi 打開文件的內容 tar -cvf file.tar file 壓縮成tar包 tar -xvf file.tar file 解壓縮tar包,後面的file
  • 轉載自:http://blog.csdn.net/luo86106/article/details/6946255 .gz 解壓1:gunzip FileName.gz 解壓2:gzip -d FileName.gz 壓縮:gzip FileName .tar.gz 解壓:tar zxvf File
  • ubuntu系統自帶截圖功能使用介紹 ubuntu自定義截圖快捷鍵:Shift+PrtSc 截取當前視窗快捷鍵:Alt+PrtSc 保存全屏截圖:PrtSc
  • 改進uwsgi啟動腳本,使其支持多個獨立配置文件。
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...