檢視 Clang Static Analyzer 的 ExplodedGraph

之前一直好奇爲何 Clang Static Analyzer (CSA) 中 dump 函式都是以 JSON 的形式輸出的,今天纔發現原來是與檢視 ExplodedGraph 相關。

通過 dump.ViewExplodedGraph 這個 checker 可以將當前被分析程式碼的 ExplodedGraph 以 dot 檔案的形式報存下來。但其中的每一個 ExplodedNode 都依然是 JSON 格式的字串。如果需要清晰展現其中的內容,則需要 exploded-graph-rewrite 工具來對這份 dot 檔案進行處理。

該工具位於 LLVM 項目下的 ./clang/utils/analyzer/exploded-graph-rewriter.py 位置。使用該工具可以將剛剛生成的 ExplodedGraph 的 dot 檔案轉換爲對應的 SVG 檔案(其中使用了 GraphViz 負責格式轉換),並同時對每一個 ExplodedNode 中的 JSON 字串進行解析並生成對應的表格化展示,以方便查看。

處理前的 ExplodedNode(JSON 格式):

{ "state_id": 135,
  "program_points": [
    { "kind": "Statement", "stmt_kind": "DeclRefExpr", "stmt_id": 627, "pointer": "0x5556f11554b8", "pretty": "x", "location": { "line": 3, "column": 20, "file": "temp.cpp" }, "stmt_point_kind": "PostLValue", "tag": null, "node_id": 8, "is_sink": 0, "has_report": 0 },
    { "kind": "Statement", "stmt_kind": "DeclRefExpr", "stmt_id": 627, "pointer": "0x5556f11554b8", "pretty": "x", "location": { "line": 3, "column": 20, "file": "temp.cpp" }, "stmt_point_kind": "PostStmt", "tag": null, "node_id": 9, "is_sink": 0, "has_report": 0 }
  ],
  "program_state": {
    "store": { "pointer": "0x5556f1178008", "items": [
      { "cluster": "x", "pointer": "0x5556f1177f70", "items": [
        { "kind": "Direct", "offset": 0, "value": "0 S32b" }
      ]}
    ]},
    "environment": { "pointer": "0x5556f11776d0", "items": [
      { "lctx_id": 1, "location_context": "#0 Call", "calling": "f", "location": null, "items": [
        { "stmt_id": 627, "pretty": "x", "value": "&x" }
      ]}
    ]},
    "constraints": null,
    "equivalence_classes": null,
    "disequality_info": null,
    "dynamic_types": null,
    "dynamic_casts": null,
    "constructing_objects": null,
    "checker_messages": null
  }
}

處理後的同一個 ExplodedNode:

ExplodedNode after being handled

鯨魚背上的大貓咪——在 Linux 電腦上通過 Docker 來運行 Mac OS X

鯨魚,即是 Docker。而 Mac OS X,因其之前使用大型貓咪來命名,故而稱其爲大貓咪。在 Linux 電腦上通過 Docker 來運行 Mac OS X,自然也就是鯨魚背上的大貓咪了。——題記

在處理 D102669 時,遇到了一個僅在 Mac 上纔會觸發的 bug。而手上除了豹豹有一臺公司的 MBP 外,整個家族也就再無觸手可即的 Mac 了。雖說是公司的電腦,但畢竟是豹豹的,所以也就不好意思拿來編譯和 debug。再加上一直以來在 OS X 上都沒有搞掂 debug 相關的事情,於是便萌生了自己來弄一個 Mac 電腦的想法。

作爲一個窮鬼書生,買是買不起的,畢竟家父不是國王。於是,便思考起左道旁門來。比如:進到一家蘋果店裏,把展出的 Mac 電腦連到實驗室的伺服器上開一個反向代理,然後白嫖人家的展示機。不過最後還是鑑於良心上的譴責以及確實登錄會存在問題而放棄了這個方案。(好孩子不可以學我這樣做哦)

因此,看起來比較切實可行的方案就只剩下了黑蘋果和虛擬機。在一次偶然的搜索時,發現了這樣一個項目:Docker-OSX,於是瞬間豁然開朗。這個項目把 QEMU KVM 運行 OS X 虛擬機所必須的文件都封裝到一個 Docker 鏡像中,從而完美地解決了自己配置 OS X 虛擬機搞不掂的問題。使用起來也相當方便且順暢,只需要一條命令把這個 Docker 鏡像運行起來,你便擁有了一個可用的 OS X 環境。

別家的使用教程已經有很多了,這裏就不再贅述,我只給出一些個人的使用經驗。官方教程影片在這裏(https://youtu.be/wLezYl77Ll8),有興趣可自行學習。我這邊使用的是默認版本的鏡像,也就是 Mac OS 10.15 Catalina。啓動之後,默認的鏡像是裸機環境,需要自行安裝系統。安裝大約耗時半小時,安裝完成之後就可以直接使用了。由於沒有 GPU 加速,因此 UI 會比較卡頓。但打開 SSH 登錄之後,直接使用控制檯時並不存在卡頓的問題。因此如果需求是使用 GUI 應用較多的話,還是需要考慮一下配置 GPU 加速的。由於我這邊是跑編譯和 debug,用的工具也是控制檯上運行的 Vim 和 GDB/LLDB,而 GUI 的卡頓對於我的影響也就微乎其微了。

由於編譯需要消耗比較多的記憶體以及 CPU 資源,於是便轉移到了實驗室的伺服器上來運行。這裏便遇到了第一個需要解決的問題:遠端的 QEMU 界面如何在本地顯示。解決的方法就是 X forwarding。首先在 SSH 連接實驗室的伺服器時需要開啓 X forwarding,即 ssh 命令的 -X-Y 參數。同時爲了不卡頓,還需要開啓流量壓縮,即 -C 參數。爲了鑑權方便,我這裏選擇了 -Y 參數。如果是使用其他 SSH 客戶端的話,需要查看一下自己的客戶端如何啓用這兩個選項。

local$ ssh -YC user@hostname

然而,這只是第一步。這只能讓伺服器上運行的 GUI 程式顯示在本地的熒屏上。而對於一個 Docker container 來講,其運行的環境可以認爲完全是另外一臺電腦,因此還需要讓 Docker container 中的 GUI 程式可以利用窩們轉發的 X 數據。需要解決的有兩點:連接和鑑權。這裏採用了直接使用 host 網路來啓動的方式來解決連接問題,即 --net=host。此時,Docker container 中的 DISPLAY 變量不需要額外改動,直接使用外邊的值即可。此外,X server 鑑權所依賴的 $HOME/.Xauthority,也需要給 1000 用戶開放讀取權限並放到 Docker container 中(如果你本人的賬戶 ID 不是 1000 的話)。

remote$ chmod 666 ~/.Xauthority

記憶體和 CPU 用量的設置是通過環境變量來進行的:CPU 是 SMP 以及 CORES,記憶體是 RAM(單位:GiB)。默認參數是 4 核 4 GiB 記憶體。此外,如果想要持久化硬碟中的數據,可以將 Docker image 中的 /home/arch/OSX-KVM/mac_hdd_ng.img 文件拷貝出來,然後在啓動 Docker 的時候放進去即可。

remote$ docker run -it --rm \
          -v /path/to/disk:/tmp/disk \
          --entrypoint=/bin/bash \
          sickcodes/docker-osx:latest
container$ cp /home/arch/OSX-KVM/mac_hdd_ng.img /tmp/disk

啓動 QEMU 時,會將虛擬機 SSH 的 22 端口轉發到 INTERNAL_SSH_PORT(默認爲 10022),以及 VNC 的 5900 端口轉發到 SCREEN_SHARE_PORT(默認爲 5900)。由於前面使用了 host 網路,因此如果這兩個默認佔用的端口如果已被使用,則需要通過這兩個環境變量來重新定義。

remote$ docker run --rm -it \
          --net=host -e DISPLAY=$DISPLAY \
          -v $HOME/.Xauthority:/home/arch/.Xauthority \
          --device /dev/kvm \
          -v /path/to/disk/mac_hdd_ng.img:/home/arch/OSX-KVM/mac_hdd_ng.img \
          -e INTERNAL_SSH_PORT=40022 \
          -e SCREEN_SHARE_PORT=45900 \
          -e SMP=32 -e CORES=32 -e RAM=64 \
          sickcodes/docker-osx:latest

這個時候,如果沒有遇到什麼奇怪的問題的話,應該就可以看到控制檯上刷出一些 QEMU 的啓動 log 然後彈出虛擬機的熒屏窗口了。

至此,你便擁有了一隻鯨魚背上的大貓咪。後面的工作就交給 Homebrew 吧。

從小包包裏掏出一個魔改過的 OpenRA

最近被自己一隻關在了家裏,每天至多能出門四次。除了每天出門吃飯飯以外,甚至見不到一個人、一隻貓。每天的生活也就只剩下了吃飯飯和上班。而碰巧貓貓正好把家裏的電腦給叼跑了,於是百無聊賴的窩就考慮弄個遊戲來玩玩。然而作爲一個玩遊戲的品味更像是油膩的中年大叔的貓貓,一般經常會玩的遊戲也就是曾經火熱一時的那些 RTS 遊戲了。由於家裏只有一臺辦公用的筆記本,所以並不是很想在上面安裝遊戲,也就更不用說 wine 了。思來想去,能有的選項也就只剩下 OpenRA 這個項目了。

OpenRA 項目脫胎自 Westwood 公司的經典 RTS 遊戲 Red Alert,自 EA 將 Red Alert 開源之後便有了這樣一個全平臺的開源項目。再加上由社區接管之後,大家給這個經典遊戲添加了很多更「現代」的操作方式,以及對於更現代化的設備有了更好的支持,因此相較於最原始的 Red Alert 來說,可玩性和兼容性都有了很大的提升。遊戲通過 .Net 框架實現了跨平臺,因此不光支持 Windows 平臺,Mac OS 和 Linux 也都有良好的支持,甚至 OpenRA 都進入了某些激進的發行版的官方倉庫。

Arch Linux 的 Community 倉庫中的 OpenRA 包
Arch Linux 的 Community 倉庫中的 OpenRA 包

「就決定是你啦!」然而可惜的是 Fedora 的倉庫裏面並木有這個遊戲(「人家才不是地溝油!嗚咕!」),而且窩也木有找到別人編譯好的 RPM 包。於是窩只好跑去了 OpenRA 的官網,所幸的是在那裏窩找到了官方打好的 AppImage 鏡像。這對於窩來說真的是非常舒適的,畢竟如果是 RPM 包的話,窩還要裝很多依賴給它,而 AppImage 則不用。即用即下載,就只有單獨一個文件,而不需要了的時候也不需要卸載,直接刪掉這個文件就好了。

鏡像的文件也並不大,只有不到 19 MB,裏面已經包含了所有運行這個程序所需的環境依賴。然而真正讓遊戲能夠跑起來所需要的遊戲資源(音樂、關卡、地圖等)卻有 100 多 MB,好在是初次打開引擎之後就可以自動下載。但提供下載的只是運行引擎所必須的基礎依賴,如果需要其他資源(過場動畫、額外的音樂包等)則需要自己從原版遊戲光盤上提取。

OpenRA 引擎自帶的資源管理器
OpenRA 引擎自帶的資源管理器 (這裏的截圖是已經安裝了所有必須資源之後的狀態)

下載完必須的資源之後重啓引擎,便可以正常啓動遊戲了。遊戲的主菜單更像是照抄了 C&C Generals 系列的主菜單模式:背景上是一張自動執行的動畫地圖,提供了遊戲整體效果的動態演示,然後 logo、選單、新聞等放置於地圖場景之上。(其實窩對於這種放在背景上自動執行的這種動畫地圖還是很感興趣的,最主要的一點就是這種地圖必須要設計的平衡度非常好,要不然一方將另一方擊敗的話動畫就演不下去了)

OpenRA 主菜單
OpenRA 主菜單

進入遊戲之後,遊戲的 UI 佈局和快捷鍵的設置則是基本照抄 Red Alert 3 的了。雷達、建造選項、金錢、電力等信息放置於右上角,超級武器等支援能力的使用選項則放置於左上角。建造選項爲每行三個共四行,對應各項的快捷鍵分別爲 F1F12 鍵。建築、防禦、士兵、載具、飛行、水上的選單分別對應 E ~ I 鍵,而變賣、電力控制、修理則分別對應於 ZXC 三個鍵。幾乎與 Red Alert 3 的完全相同。而建造選單方面不僅相對於原版 Red Alert 加入了單位生產的排程,甚至針對於建築物的建造也可以排程。相信這一點凡是玩過原作的玩家都會覺得是一個非常好的文明。

兩種陣營各自的建造選單
兩種陣營各自的建造選單

到這裏爲止,目前還沒有任何關於標題裏面所提到的「魔改」的內容,下面的內容纔是魔改一詞所涉及的重點。

由於小的時候常常玩的是 Red Alert 2,再加上窩本身玩 RTS 類遊戲玩得是對於全局的掌控感,以及收集了大量資源之後的滿足感,實際上並不喜歡真刀真槍地打來打去,因此每次玩的時候基本上都只是開一個簡單的 bot 然後慢慢玩到老。因此,儘管 OpenRA 對於遊戲體驗有很大程度的改進,玩過幾盤遊戲之後便也厭了。於是,窩又開始打起了 2 代的主意。

非常巧合的一點是窩在查的時候並沒有刪掉 openra 這一關鍵字,於是便發現了一個令我喜出望外的 OpenRA 的 mod:OpenRA-RA2(項目 repo)。在翻看了 YouTube 上的一些演示視頻之後,於是決定自己來上手試試看。然而不巧的是,廢了很大的力氣窩也並木有找到現成的 prebuild 軟件包(這次更衰)。沒辦法,要想玩的話也就只能自己來 build 這個項目了。試着把 repo 上的代碼拉下來,然後照着 Wiki 上的步驟 make 了一下,果然炸了。。。QAQ

等一下,自己 build 這個項目?憑藉着前 Arch Linux 用戶的本能,窩便又隨手去翻了一下 AUR,沒想到居然真的找到了:openra-ra2-git(AUR 信息頁)。既然有了 AUR,那接下來的工作便簡單了。在 chroot 環境下的 Arch Linux 子系統中通過 yaourt 便可以 build 出一個這個項目的軟件包。然後把軟件包裏的內容安裝到外面的系統上便可以正常運行了。

使用 yaourt 來 build 這個包的這一步進行得非常順利,果然還是別人已經寫好了的自動化腳本用起來更舒服。同時 openra-ra2-git 這個 AUR 包裏不僅提供了 build 的腳本,也額外提供了一些方便於在系統中使用的一些額外的配置文件和圖標等,因此 AUR 上的包往往會讓人用起來非常舒適。

然而 build 好了這個包之後,如何把它安裝到外面的系統裏,同時又讓它能正常運行,這便是在得到了這個包之後的一個新問題了。畢竟 openra-ra2-git 的信息頁上明確寫着要運行這個東西有多達 12 項依賴。幸虧 Arch Linux 打的包一般 granularity 都很大,要是像 Ubuntu 那樣非常細地拆分包的話,真是不知道要再裝多少個依賴包了呢。在對這個問題思考了好久之後,窩突然想到一點:AppImage 包含了運行這個程序所需要的全部必須的依賴,而同時它又可以 extract 到一個文件夾中然後再運行的。而 extract 之後得到的是一個完整的目錄樹,可以往其中添加其他的東西並通過這個環境來運行加進去的東西。

誒?窩不是正好有 OpenRA 的 AppImage 嗎?於是,把 OpenRA 的 AppImage 拆包,然後把 openra-ra2-git 這個軟件包中的內容與拆包之後的目錄樹合併在一起,然後改了一下 AppImage 的 AppRun 腳本,將啓動 OpenRA 的命令換成了啓動 OpenRA-RA2 的。果然引擎正常地跑起來了。

OpenRA-RA2 也需要原版 Red Alert 2 中的資源文件
OpenRA-RA2 也需要原版 Red Alert 2 中的資源文件

然後便是 OpenRA-RA2 的 Wiki 上所提到的運行時所必須的 Red Alert 2 的若干 *.mix 文件了。而窩碰巧有遊戲的原版安裝光盤 ISO 鏡像,於是 mount 了這個鏡像,然後根據 Wiki 上的指引把所需的文件放到了指定的資源文件夾中。啓動引擎,發現還是沒有找到資源文件。複查了幾遍之後發現沒有問題,那唯一的一種可能就是官方 Wiki 上給的資源目錄有問題。於是窩在那個 repo 中搜了一下那個目錄,發現有一份配置文件中有提到這個目錄,然而是一個相對於 OpenRA 引擎資源目錄的一個相對目錄。憑藉直覺窩覺得應該是找到問題了,因爲窩拿到的那個 AppImage 中的引擎所指定的資源目錄和 OpenRA-RA2 的 repo 中假設的資源目錄並不一致(窩這邊的話,OpenRA 的資源目錄是在 ~/.config/openra/Content 目錄,而不是 ~/.openra/Content 目錄)。於是把那些資源文件移到了正確的位置之後,遊戲被正常加載了。

進入遊戲主菜單之後,主菜單的佈局還是與 OpenRA 的相同(畢竟是同一個引擎),而不同的是用作背景的動畫地圖變成了一張空白地圖,甚至連邊框都沒有。這不禁讓人很沮喪。但萬幸的是,自帶的地圖編輯器可以修改這張地圖,所以如果有人有閒心的話是可以做一個動畫地圖來替換這張白板的。

OpenRA RA2 的主菜單
OpenRA RA2 的主菜單

進入遊戲之後,相對於 OpenRA 風格就變了很多。UI 的樣式更接近原版(至少作者把原作的素材都用上了),而佈局和快捷鍵還是 OpenRA 的樣式,並沒有遵循原作對於快捷鍵的設置。但在玩過幾盤 OpenRA 熟悉了鍵位的設置之後,建造、放置,一切又都是那種熟悉的感覺……

OpenRA-RA2 中兩種陣營各自的建造選單
OpenRA-RA2 中兩種陣營各自的建造選單

總體來講,這個 mod 目前的完成度並不低,但也並沒有完全做完全部的功能。單從我隨便試玩的兩盤來講,就發現和很多並沒有完成的功能點。比如:Lightning Storm 並沒有支持,建完 Weather Control Device 之後就沒有然後了;Mirage Tank 並不會變成樹,Chrono Legionnaire 只會在地上噠噠噠跑,等等。遊戲中的一些屬性參數的實現也有些許微妙的不一致,玩起來既有那種熟悉的感覺,又總會覺得哪裏怪怪的,更像是一個新遊戲。而遊戲的手感相較於原版的 Red Alert 2 來講更加的棉軟,像是往棉花包裏面打拳那樣的感覺。希望作者能繼續做下去,完成這個項目,甚至發展成一個類似於 OpenRA 這樣的完整改進版本。就目前來看,403 個 star 、91 個 fork 的關注度雖然並不是很高,但作者幾乎每天都有 commit 這件事還是能讓人有所期盼,布吉島後續作者會不會咕~