簡體   English   中英

解開匯編語言意粉代碼

[英]Unravelling Assembly Language Spaghetti Code

我繼承了用8051匯編語言編寫的10K行程序,需要進行一些更改。 不幸的是,它是用最好的意大利面條代碼傳統寫成的。 程序 - 作為單個文件編寫 - 是CALL和LJMP語句的迷宮(總共約1200個),子程序具有多個入口和/或出口點,如果它們可以被識別為子程序的話。 所有變量都是全局的。 有評論; 有些是正確的。 沒有現有的測試,也沒有重構預算。

關於應用程序的一些背景知識:代碼控制當前在國際上部署的自動售貨應用程序中的通信中心。 它同時處理兩個串行流(在單獨的通信處理器的幫助下),並且可以與最多四個不同的物理設備通信,每個物理設備來自不同的供應商。 其中一個設備的制造商最近做了一個改變(“是的,我們做了一個改變,但軟件完全相同!”)這導致一些系統配置不再起作用,並且不想改變它(無論它是什么他們沒有改變)。

該計划最初由另一家公司編寫,轉讓給我的客戶,然后由另一位顧問在九年前進行了修改。 原始公司和顧問都不是資源。

基於對其中一條串行總線上的流量的分析,我想出了一個看似有效的黑客攻擊,但它很難看並且沒有解決根本原因。 如果我對該計划有更好的理解,我相信我可以解決實際問題。 在代碼被凍結之前,我還有大約一周的時間來支持月末發貨日期。

原始問題:我需要很好地理解程序,以便在不破壞的情況下進行更改。 有沒有人開發過處理這種亂七八糟的技術?

我在這里看到一些很棒的建議,但受到時間的限制。 但是,我將來可能有另一個機會去尋求一些更復雜的行動方案。

首先,我會嘗試與那些最初開發代碼或至少在我之前維護代碼的人取得聯系,希望獲得足夠的信息以便對代碼進行基本的理解,以便您可以開始添加有用的注釋它。

也許你甚至可以讓某人為代碼描述最重要的API(包括它們的簽名,返回值和目的)。 如果函數修改了全局狀態,那么這也應該是明確的。 同樣,開始區分函數和過程,以及輸入/輸出寄存器。

您應該向雇主明確說明這些信息是必需的,如果他們不相信您,讓他們在您描述您應該做的事情以及您必須做什么的同時在這段代碼前與您坐下來它(逆向工程)。 擁有具有計算和編程背景的雇主在這種情況下實際上會有所幫助!

如果您的雇主沒有這樣的技術背景,請他帶另一位程序員/同事向他解釋您的步驟,這樣做實際上會向他表明您對此非常認真,因為這是一個真正的問題 - 而不僅僅是從你的角度來看(確保有同事了解這個'項目')。

如果可行且可行的話,我也會非常明確地說,簽約(或至少是聯系)以前的開發人員/維護人員(如果他們不再為貴公司工作,那就是)幫助記錄這些代碼將是一個預先 - 在短時間內實際改進代碼並確保將來可以更輕松地維護代碼的必要條件。

強調整個情況是由於先前軟件開發過程中的缺點,並且這些步驟將有助於改進代碼庫。 因此,當前形式的代碼庫是一個日益嚴重的問題,現在無論如何處理這個問題都是對未來的投資。

這本身對於幫助他們評估和了解你的情況也很重要:做你現在應該做的事情遠非微不足道,他們應該知道 - 如果只是為了直截了當地設定他們的期望(例如關於截止日期和復雜性任務)。

另外,我個人會開始為那些我理解得很好的部分添加單元測試,以便我可以慢慢地開始重構/重寫一些代碼。

換句話說,良好的文檔和源代碼注釋是一回事,但擁有一個全面的測試套件是另一個重要的事情,沒有任何人可以在沒有任何測試關鍵功能的既定方法的情況下修改不熟悉的代碼庫。

鑒於代碼是10K,我還會考慮將子例程分解為單獨的文件,以使組件更易於識別,最好使用訪問包裝而不是全局變量以及直觀的文件名。

此外,我會研究通過降低復雜性來進一步提高源代碼的可讀性的步驟,具有多個入口點的子例程(甚至可能是不同的參數簽名?)看起來像是不必要地混淆代碼的可靠方法。

同樣,巨大的子程序也可以重構為較小的子程序,以幫助提高可讀性。

因此,我要做的第一件事就是確定那些使得代碼庫變得非常復雜然后重新編寫這些部分的東西,例如將具有多個入口點的巨大子例程拆分為不同的東西。相反調用的子例程。 如果由於性能原因或調用開銷而無法完成此操作,請改用宏。

另外,如果它是一個可行的選項,我會考慮使用更高級別的語言逐步重寫部分代碼,或者使用C的子集,或者至少通過過度使用匯編宏來幫助標准化代碼基礎,但也有助於本地化潛在的錯誤。

如果C中的增量重寫是一個可行的選項,一種可能的入門方法是將所有明顯的函數轉換為C函數,這些函數的主體在開始時從匯編文件中復制/粘貼,因此最終得到C具有大量內聯匯編的函數。

就個人而言,我也會嘗試在模擬器/模擬器中運行代碼以輕松地逐步完成代碼,並希望開始了解最重要的構建塊(同時檢查寄存器和堆棧的使用情況),一個帶有內置調試器的8051模擬器應該是如果你真的必須自己做這件事,你可以使用它。

這也可以幫助您提出初始化序列和主循環結構以及調用圖。

也許,你甚至可以找到一個很好的開源80851模擬器,可以很容易地修改,也可以自動提供一個完整的調用圖,只是做一個快速搜索,我發現gsim51 ,但顯然有其他幾個選項,各種專有的選項。

如果我在你的情況下,我甚至會考慮外包修改我的工具以簡化使用這個源代碼的工作,即許多sourceforge項目接受捐贈,也許你可以和你的雇主討論贊助這樣的修改。

如果不經濟,也許你提供相應的補丁?

如果您已經在使用專有產品,您甚至可以與該軟件的制造商交談並詳細說明您的要求,並詢問他們是否願意以這種方式改進此產品,或者他們是否至少可以允許接口允許客戶進行此類自定義(某種形式的內部API或甚至簡單的膠水腳本)。

如果他們沒有回應,請表明您的雇主一直在考慮使用不同的產品一段時間,並且您是唯一堅持使用該特定產品的人...... ;-)

如果軟件需要某些I / O硬件和外設,您甚至可能希望編寫相應的硬件仿真循環以在仿真器中運行該軟件。

最后,我知道一個事實,我個人更喜歡定制其他軟件的過程,以幫助我理解這樣的意大利面條代碼怪物,而不是手動單步執行代碼並自己玩模擬器,無論我加多少加侖咖啡得到。

從開源8051模擬器中獲取可用的調用圖不應該比周末(最多)花費更長的時間,因為它主要意味着查找CALL操作碼並記錄其地址(位置和目標),以便將所有內容轉儲到文件供以后檢查。

訪問模擬器的內部實際上也可以是進一步檢查代碼的一種方法,例如為了找到重復的操作碼模式(比如20-50 +),這可能會被考慮到獨立的函數/過程中,這實際上可能是有助於進一步減少代碼庫的大小和復雜性。

下一步可能是檢查堆棧和寄存器使用情況。 並確定所用函數參數的類型/大小,以及它們的值范圍 - 以便您可以設想相應的單元測試。

使用dot / graphviz等工具可視化初始化序列的結構和主循環本身,與手動完成所有這些操作相比,將是一種純粹的樂趣。

此外,您實際上最終會獲得有用的數據和文檔,從長遠來看,它們可以作為更好的文檔的基礎。

我擔心這種問題沒有靈丹妙葯。 我發現唯一的解決方案是打印出ASM文件然后安靜地去模擬在你腦海中逐行運行程序(同時在記事本上寫入寄存器和內存位置的內容)。 過了一段時間,你發現這不會像你期望的那樣長。 准備好花很多時間做這個並喝加侖咖啡。 過了一會兒,你將了解它在做什么,你可以考慮改變。

8051是否有任何未使用的IO端口? 如果確實如此,並且在調用某些例程時無法解決問題,則添加代碼以將這些備用端口發送到高或低。 然后當程序運行時用示波器觀察這些端口。

祝好運

我知道這聽起來很瘋狂......但我失業了(我選擇了錯誤的時間告訴大多數伙伴下地獄)並且有空閑時間。 我願意看看它。 我曾經為蘋果[和原版PC]編寫程序集。 如果我可以在模擬器上玩你的代碼幾個小時,我可以給你一個想法,如果我有機會為你記錄它(沒有運行我的計划外假期)。 由於我對8051一無所知這對像我這樣的人來說可能是不可能的,但模擬器看起來很有希望。 我不想要任何錢來做這件事。 它足以讓我們接觸8051嵌入式開發。 我告訴過你這聽起來很瘋狂。

找到另一份工作 - 認真! 如果沒有“使用遺留代碼有效地工作”這本書可能會有所幫助 - 盡管我認為它將遺留代碼稱為沒有單元測試的代碼。

我曾經做過幾次這樣的事情。 一些建議:

  • 首先回顧一下原理圖,這可以幫助您了解所需更改對哪些端口和引腳產生影響。
  • 使用grep查找所有調用,分支,跳轉和返回。 這有助於理解流程並識別代碼塊。
  • 查看復位向量和中斷表以識別主線。
  • 使用grep為所有代碼標簽和數據引用創建交叉引用(如果匯編程序工具無法為您執行此操作)。

請記住Hofstadter定律: 即使考慮到Hofstadter定律,它也總是比您預期的要長

祝好運。

您對這段代碼運行的硬件平台有多了解?

  • 是否已進入斷電模式(Pcon = 2)以節省電量如果是這樣,它是如何被喚醒的。 (復位或硬件中斷)

  • 在進行串行通信之前,是否必須等待上電后振盪器穩定

  • 是否已進入睡眠模式(Pcon = 1)

現場有不同版本的硬件嗎?

確保您具有要測試的所有不同硬件變體。

不要在模擬器上浪費你的時間 - 這是非常難以使用的,你必須對硬件做出很多假設。 獲得一個In Circuit Emulator(ICE)並在硬件上運行。

該軟件是用匯編語言編寫的,原因是你需要找出原因。 即 - 內存約束 - 速度限制

可能有一個原因,這個代碼是一團糟

看看鏈接文件:

XDATA SPACE,IDATA SPACE和CODE SPACE:

如果沒有免費代碼空間或Xdata或Idata?

原作者可能已經優化它以適應可用的內存空間。

如果是這種情況, 您需要與原始開發人員交談以了解他的所作所為

我對8052軟件有一些非常類似的問題。 所以公司繼承了這樣一個野獸,代碼ROM滿(64K字節),大約1.5兆的裝配意大利面模塊和兩個3000行PL / M模塊組成了這個編碼怪物。 該軟件的原始開發人員已經死了(這並不意味着沒有人,但實際上沒有人能夠理解它),編譯這些軟件的編譯器來自運行在MDS-70仿真器上的80年代中期,以及幾個關鍵的模塊處於這些編譯器的極限。 就像添加一個全局符號一樣,鏈接器會崩潰。 再向ASM文件添加一個符號,編譯器就會崩潰。

那么如何才能開始削減它?

首先,你需要工具。 例如Notepad ++是一件非常好的事情,因為它可以用於一次跨多個文件進行交叉搜索,非常適合查找哪些模塊引用全局符號。 這可能是最關鍵的因素。

如果可能,請獲取您可以在軟件上找到的任何文件。 這些野獸要解決的最直接的問題是要了解它們是如何粗略組成的,它們的架構是什么。 這通常不包含在軟件本身中,即使它被正確評論也沒有。

要自己獲取架構,首先可以嘗試構建調用圖 它比數據流圖更簡單,因為通常與全局變量相比,跨文件調用和跳轉更少。 對於此調用圖,只考慮全局符號,假設源文件應該是模塊(不一定是真的,但通常應該是這樣)。

要執行此操作,請使用您的工具進行跨文件搜索,創建一個大型列表(例如在OpenOffice Calc中),您可以在其中收集在哪個文件中定義的符號,以及哪些文件引用此符號來調用它。

然后從繪圖儀中偷取一些大的(!)紙張,然后開始繪制草圖。 如果你非常精通某些圖形軟件,你可以使用它,但除非是這樣,否則它更有可能阻止你。 因此,繪制一個調用圖表,顯示哪個文件調用了哪些其他文件(不顯示符號本身,有50個左右的文件,您將無法管理它)。

最有可能的結果是意大利面。 我們的目標是理順它,使其成為一個帶有根的分層樹(它將是包含程序入口點的文件),沒有循環。 你可以在這個過程中吞下幾張紙,反復拉直野獸。 您可能還會發現某些文件是如此混雜,以至於沒有循環就無法表示它們。 在這種情況下,很可能單個“模塊”以某種方式分成兩個文件,或者更多的概念模塊被糾纏在一起。 返回到您的呼叫列表,並對符號進行分組,以便在較小的獨立單元中剪切有問題的文件(您還需要檢查文件本身,以便在此處進行本地跳轉以查看您的假定切割是否可行)。

最后,除非您已經為了自己的利益而在其他地方工作,否則您將獲得帶有概念模塊的分層調用圖。 由此可以推斷出軟件的有意架構並進一步發揮作用。

下一個目標是架構 通過先前制作的地圖,您需要瀏覽軟件,找出它的線程(中斷和主程序任務),以及每個模塊/源文件的粗略目的。 如何做到這一點以及你在這里得到的更多取決於應用程序域。

當這兩個完成后,“休息”相當簡單。 通過這些,你應該基本知道事物的每個部分應該做什么,因此你知道當你開始處理源文件時你可能會處理什么。 重要的是,每當你在源代碼中發現“魚腥”的東西時,程序似乎做了一些無關緊要的事情,回到你的架構並調用圖形,並在必要時進行修正。

其他方面提到的方法很適用。 我剛剛概述了這些內容,以便對在真正可怕的情況下可以做些什么有所了解。 我希望我當時只有10K行代碼來處理...

您不需要特殊的預算來進行重構和測試 - 它們可以節省您的資金並讓您更快地工作 - 實現目標。 這是你應該使用的技術來添加對遺留代碼的繼承,因為這是最簡單的方法,沒有“沒有破壞”。

大多數時候,我認為有一個權衡,你可以獲得更多的質量來換取花費更多的時間,但是對於你不熟悉的遺留代碼,我認為進行測試更快 - 你必須先運行代碼你發貨吧?

這是為數不多的幾次,我建議你將軟技能用於工作,並向你的PM /經理/ CXO提供你重寫的理由,以及這項工作所帶來的時間/成本節省

把它切成碎片。

我會說IanW的答案(只是將其打印出來並繼續跟蹤)可能是最好的。 也就是說,我有一個略微偏離牆的想法:

嘗試通過可以重建C代碼的解析器運行代碼(可能是二進制代碼)(如果可以找到8051的代碼)。 也許它會識別一些你不能(輕松)的例程。

也許它會有所幫助。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM