陸乾杰,趙 心,錢 銳,吳海燕
(中核核電運行管理有限公司 維修五處,浙江 海鹽 314300)
重水堆核電站采用電站控制計算機(Digital Control Computer,以下簡稱DCC)進行全廠集中控制,負責反應堆反應性控制、主回路壓力和裝量控制、蒸發器壓力和液位控制等,是整個核電站的控制中樞。國內唯一的秦山三期重水堆電站控制系統由加拿大原子能公司設計,硬件采用SSCI 890(S)系列計算機[1]。該系列計算機為16 位8MHz 集成電路數字機,操作系統(V70 Omnitask Real-time Executive,VORTEX)和核心控制程序均采用SSCI V70 指令集,系統中所有程序均以二進制代碼形式運行。雖然大部分程序均有其對應的未編譯源碼,但是系統中仍然存在大量沒有源碼的二進制程序,例如系統引導程序、系統測試程序等。對于這些二進制程序進行邏輯功能分析是非常困難和復雜的,因此本文將通過反匯編的方法對二進制程序進行反編譯研究,將二進制代碼轉化成便于理解和閱讀的匯編代碼。

圖1 反編譯的解決思路Fig.1 Disassembly pipeline diagram
在DCC 系統備份和恢復過程中,DCC 程序均以COR 文件類型進行存儲。COR 文件是二進制代碼,無法直接打開和閱讀。而在DCC 的缺陷分析和國產化研究中,出現了對于DCC 二進制程序反匯編的需求,主要來源于以下幾個方面:
1)當系統出現異常時,需要對備份出來的數據進行檢查和代碼分析,通過回溯其代碼執行的過程來定位缺陷的位置。
2)在變更或者參數修改時,可能需要查看歷史備份數據COR 文件中對應的數值。
3)在執行系統測試時,可能會出現一些無法意料的報錯,就需要對測試程序的邏輯進行分析,而現存所有的測試程序均以二進制碼的形式存儲,無法直接進行分析。
4)在Processor 處理器板卡國產化時,需要對其引導程序進行分析,而引導程序只有二進制碼,無法進行分析。
基于以上需求,本文需要對DCC 系統的二進制碼進行反匯編研究,只要能將二進制碼COR 文件轉變成具有可閱讀性的文本,以上問題就能迎刃而解。
完成對于二進制程序COR 文件的反匯編工作,首先要對SSCI V70 指令集數據格式進行分析,根據其格式標準對COR 文件格式的分析,將COR 文件中二進制數據從文件中取出來,并且以八進制的形式進行展現。
其次要對SSCI V70 指令集和尋址方式進行分析,對各類指令進行分類,提取特征,根據總結出來的特征完成對于單條指令的反匯編。
最后在完成單條指令反匯編的基礎上,對整個程序進行整體反匯編,主要解決整體反匯編中出現的數據區和指令區無法識別的問題,最終形成具有可閱讀性的文本。
SSCI V70 指令集是16 位指令集,所有的數據和命令僅包含16 位。數據的BIT0-14 表示數值,BIT15 為符號位S,符號位S 為0 表示正數,符號位S 為1 表示負數。在DCC 中負數的數值使用補碼表示。地址的BIT15 為指針位I,若指針位I 為1 表示這個地址為指針,實際地址為BIT0-14 地址所指向空間的數值。
當程序直接讀取COR 文件只能得到字節流數據,如圖2。

圖2 COR文件字格式圖Fig.2 COR Type file in origin format
根據對SSCI V70 指令集數據格式的分析,所有數據均是16 位數據,因此可以將COR 文件轉化為16 位數據為單元的數據列表。由于讀取的值為8 位/字節,因此可以做如下轉換:

bytes 為讀入字節流,idata 為轉化后16 位數據。
在對COR 文件進行轉化后發現COR 的前128 字節為描述信息,其中包括了COR 的程序名、程序位置、程序長度等信息。因此實際代碼數據從128 字節開始進行轉化,INIT 程序轉化后用八進制顯示如圖3。

圖3 COR文件八進制格式圖Fig.3 COR Type file recording in octal
將COR 文件轉化為16 位數據為單元的數據列表后可以對程序內容進行反匯編。
所有的指令可分為單字指令和雙字指令,單字指令指的是有16 位二進制數據組成的指令,雙字指令是有兩個16 位二進制數據組合而成的指令。
對于DCC 指令集共有10 類指令[2],分別為,存取指令(Load/store)、算術指令(Arithmetic)、邏輯指令(Logic)、位移指令(Shift/Rotation)、寄存器指令(Register Transfort / Modification)、跳轉指令(Jump)、調用指令(Jump-and-mark)、執行指令(Execution)、控制指令(Control)、IO 指令(Input/Output)。
一般一條單字指令都由操作碼(OP CODE)、尋址方式(M)和操作數(A)構成。操作碼指示這條指令的作用和功能,尋址方式表示指令要操作的數據存放形式,與操作數配合可以在內存空間或者寄存器中找到需要操作的數據。
對于編譯器而言,明確了操作碼、操作數和尋址方式后就可以根據以上規則轉化為二進制碼。但是據上文所述對于不同指令而言其操作數、操縱碼、尋址方式在16 位或者32 位指令空間中的位置不同,反編譯器無法根據二進制碼直接應用規則來將二進制碼反編譯。
便于反編譯器的執行,通過對Varian-70 所有指令共176 種進行統計后,根據指令BIT12-15、BIT9-11和BIT6-8 的值將指令分為38 小類:

表1 指令集分類表Table 1 Instruction types summary
根據以上分類,可以通過指令前7 位的數值定位到指令類型和指令功能,然后根據后9 位(單字)或者后9+16 位(雙字)提供的信息完成具體操作,舉例如下:
不帶數據單字舉例:005002,其前四位為0,中三位為5,根據分類屬于單字寄存器指令,進一步得知后9 位中前3 位為0 表示寄存器間數據轉換,中3 位為0 表示數據清零,后3 位為2 表示B 寄存器,因此005002 反匯編為“TZB”。
帶數據單字舉例:020612,其前四位為2,中三位為0,根據分類屬于直接單字取指令(B 寄存器),進一步得知后9 位表示直接尋址的地址,因此020612 反匯編為“LDB 612”。
雙字舉例:006010,030300,第一個字中前四位為0,中三位為6,根據分類屬于雙字存取指令,進一步得知后9 位表示數據取到A 寄存器,第二個字為取值地址,因此006010,030300 反匯編為“LDA 030300”。
通過以上方法可以完成單條指令的反匯編,并且根據類型分類可以獲知下個字的類型。
反匯編算法可以分為靜態掃描算法和動態掃描算法。靜態掃描算法是對目標程序從頭到尾進行掃描,直接將所遇到的二進制碼全部翻譯為對應的匯編指令,缺點在于無法區分程序段中的數據區。動態掃描算法是模擬程序運行來區分指令和數據,但是缺點在于動態掃描時需要處理間接調用和多層調用問題,且編譯的覆蓋面低于靜態掃描算法[3]。
對于靜態掃描算法,最大的問題在于解決指令和數據的區分。對DCC 源碼分析發現,DCC 的數據區一般處于程序開頭或者結尾,且大規模出現,很少出現少量數據與指令混雜在一起的情況。因此采用靜態掃描的反編譯算法較為合適,同時本文根據DCC 源碼特征采用動態標記和卷積標記兩種策略來解決數據與指令無法區分的問題。
小規模出現的數據區往往會被上下文通過單指令直接索引的方式調用,因此在第一次靜態掃描時,當掃描到單指令直接索引的指令時將索引的地址進行標注為數據區,進行動態標記。當第二次掃描時不進行反編譯,僅僅對標記為數據區的數據轉為數據顯示。例如:

當第一次掃描時,會將數據反編譯為:

由于第一次掃描時會將023411 地址標記為數據,因此第二次掃描后為:

第一次靜態掃描時對于無法反編譯的數據(未在指令集)標記為數據,同時針對字符串編譯一般經過反編譯后會出現大量單字節操作指令,例如下文所示(取自AACKN程序示例):


通過對大量連續出現如上指令的片段數據標記為數據區。
由于大塊數據區的數據可能正好可以被反匯編,因此當第一次靜態掃描后會出現零零散散的指令分布在數據區內。卷積在離散數學中的意義是對當前數據和卷積核進行加權求和,這樣可以將當前數據與周圍數據信息融合,再經過一層濾波就可以將零散分布在大塊數據區的指令去除,實現大塊數據區的識別。
卷積公式如下:x 表示輸入序列,h 表示卷積核,y 表示輸出結果,N 為數據長度(f 長度+h 長度-1)。

經過第一次靜態反編譯標記和動態標記后代碼標記區可以用一個一維矩陣矩陣形式表示x[n],其中n 表示數據數量,若為數據類型則為1,若為指令則為0。
以SGL 程序的30077-30125 的數據段為例,數組x表示標記區數據,1 表示為數據,0 表示為指令:

以python 語言為例使用一個[1,1,1,1,1]的卷積核h 與x 做卷積運算:

得到結果y 如下:

再經過>1 的濾波器后得到數組z:

由此可以看到通過卷積濾波之后夾雜在數據區的指令均被過濾,這是由于卷積時卷積核會將當前數據周圍信息也包含進來,通過濾波后指令類型就消失了,就能能到完整的數據區。

圖4 反編譯器程序邏輯圖Fig.4 Disassembly compiler logic diagram
反匯編器整體實現的流程如下:
1)從COR 文件中讀取二進制數據,并且將二進制數據轉化成八進制數據,放入數據數組中。
2)第一遍靜態反編譯,對單個數據依次進行反編譯,同時對于單指令直接索引和無法反編譯的數據進行動態標記,標記于對應的標記數組中。
3)第二遍循環,使用卷積核對標記數組進行卷積運算,得到新的標記數組。
4)第三遍循環,對于標記數組中大于某值的數據均轉為數據類型顯示。
5)最后,將所有顯示數據輸出到文本中,完成反編譯過程。
通過以上的研究,完成了對于反編譯器的代碼編寫,實際以DCC 備份SGL.COR 運行結果如下,左側為原編譯過程中產生的標記文件,右側為COR 文件反編譯的結果片段:
由于反編譯后數據缺少交叉編譯時的符號,因此指令后面仍保持數據顯示。

圖5 反編譯對比Fig.5 Disassembly result comparing with source code
對于以上結果,當系統出現異常時,就可以通過反編譯后文本回溯執行過程來定位缺陷的位置,同時對歷史備份數據COR 文件進行反匯編后可以快捷地查看相應的參數數值。
對于測試程序和引導程序,可以直接在Virtual Console 機上加載程序完畢后,通過“C”命令對其內存數據進行顯示和保存。通過這種方法可以直接得到八進制碼,無需進行COR 數據的轉化。最后通過反編譯,可以得到可閱讀的文本進行邏輯分析,為國產化研究和測試程序執行流程的研究解決了數據無法直接閱讀的難題。
本文通過對DCC 指令集的數據格式的分析,根據16位數據中關鍵位信息將指令分為了39 小類。根據分類,可以通過指令前7 位的數值定位到指令類型和指令功能,然后根據后9 位(單字)或者后9+16 位(雙字)提供的信息完成單條指令的反匯編操作。結合DCC 代碼特點,采用靜態編譯的方式進行反編譯,使用動態標記和卷積過濾的方法區分了數據區和指令,解決了靜態編譯中數據指令無法區分的缺點,完成了整體二進制數據的反編譯工作。反編譯后數據雖然缺少交叉編譯時的符號,但是已經具備了可讀性。通過本文的方法可以將未知的二進制碼反匯編為具有可讀性的匯編代碼,有利于重水堆控制計算機的缺陷查找和系統學習。