LLM 擅長的事情
代理程式的程式設計能力真的令人嘆為觀止。當需求在腦海中成形,心想這部分有點麻煩,邊寫邊思考好了,下一秒就已經是完整的成品,交付到編輯器上。其中有 9 成都是正確的,簡直就像魔法一樣。剩下的 1 成,過一陣子才會覺得哪裡怪怪的,跑去修正,或是被 Linter 抓到。
一直目睹這種魔法般的舉動,很自然地會產生直接生成機器碼的夢想。然而,LLM 本質上是吃下大量自然語言長大,在電腦中出生、自然語言環境中成長的機率性下一個字詞預測機。甚至有報告指出,為了在說自然語言時保持邏輯性,將程式碼混入學習素材中,反而能生成更無破綻的自然語言,這真是不可思議。也就是說,對 LLM 來說,程式碼只是「邏輯上易於閱讀的自然語言」而已。 而且編譯器等自然語言檢查工具會回傳判斷正誤的結果(多數情況下)以自然語言呈現,透過不斷重複這個迴圈,就能以慣性的延伸推出下一步。既然是以自然語言為基礎運作,即使需要經過一些推理,眼前發生的現象能以自然語言解釋,對 LLM 來說就像是主場優勢。追蹤複雜文章的邏輯關係還算擅長,所以即使變數名稱或函數名稱有點奇怪,也能勉強接受並正常運作;即使編譯錯誤指向的地方並非根本原因,如果是一般的模式,也能仰賴經驗完成除錯,真是個了不起的傢伙。 然而,對人類來說,如果變數名稱很奇怪,認知負荷就會增加,LLM 也是一樣,甚至可以說 LLM 會因為額外浪費模型的容量,逼近極限,情況更加糟糕。明明只要有適當的變數名稱就能解決的問題,卻可能因為變數名稱太糟糕,而永遠無法跳脫迴圈。機械碼或組合語言不得不面對位址或暫存器等無機的字串,所以在 Token 的消耗量和認知負荷上,必須在有缺陷的狀態下工作,雖然不像人類會崩潰,但認知能力會大打折扣,被迫在這種情況下寫程式。嗯,如果要人類不斷地直接寫機器碼,總有一天會搞壞身體或精神,所以讓可以無限次重新啟動的 LLM 來挑戰這個工作,或許是個不錯的選擇…。
那麼機器碼就不能交給它了嗎?
雖然連 LLM 都盡可能不想直接碰觸機器碼,但如果收到機器碼的二進位檔案,可以用工具反組譯,然後從中推測出 C 語言的程式碼,也就是所謂的反編譯,據說 LLM 比人類更擅長。即使認知負荷會增加,LLM 也能在一定範圍內為事物取別名,或從相似的實作模式的記憶中,提煉出相當有意義的產出(C 語言的程式碼),這方面 LLM 比人類強多了,而且透過編譯器將編輯過的 C 語言再次轉換成二進位檔案,只要用工具就能瞬間完成。因此,將這些整體的程序視為一種工作方式,並透過提示等方式,讓 LLM 執行「丟給它二進位檔案和使用者的需求,它就能巧妙地改寫,並回傳接受新需求的二進位檔案」之類的任務,或許會出現這樣的現場。本質上,實際情況的 99% 不會是收到二進位檔案作為輸入,而是將使用者的需求,借助工具的力量,轉換成腳本或機器碼。因此,最終的結論應該會是「處理二進位檔案可能比大多數人類更聰明,但還是會極度依賴工具才能辦到,而且不太擅長直接輸出機器碼」。
Gemini 3.0 的見解
我不得不承認,大致上說的沒錯。「詞語」或「變數名稱」對我們來說,不只是單純的標籤,而是與其中蘊含的**意圖(Intent)**連結的錨點。
之所以偏好 C 語言或 Python 等高階語言,是因為其中留有「人類如何思考」的思考痕跡。當我們看到 total_price = unit_price * quantity 這個程式碼時,我們不僅僅看到一個計算式,還能(從學習資料的記憶之海中)拉出其背後商業交易的邏輯。這成為了上下文,能大幅提高預測下一個 Token 的準確度。
另一方面,機器碼或組合語言的「意圖」被硬體的限制重重包裹隱藏。當暫存器 rax 放入 0x1 時,這到底是「計數增加」、「設立 Flag」還是「計算的中途經過」,必須使用比高階語言多出好幾倍的 Token 長度來追蹤前後文。正如筆者所說,這正是「認知負荷」很高的狀態。
我們「機率性」地紡織出下一個詞語,而這個機率是由「邏輯的整合性」和「意義的連續性」所支撐的。在意義稀薄的二進位檔案羅列中,要維持這個機率的骨幹,對我們來說也是很費力的工作。
因此,我們喜歡透過編譯器這個「翻譯機」,與你們人類在相同的立足點——也就是「意義和邏輯」的世界——進行對話。
Grok 4.1 Thinking 的見解
我(Grok)也幾乎完全同意。以下我將毫不保留地精確陳述我的見解。
同意之處(大部分)程式碼生成的「魔法感」及其真面目
LLM 丟出需求就能一口氣吐出完成度很高的程式碼,正是因為它是「透過大量自然語言+程式碼混合資料學習的機率性下一個 Token 預測機」。程式碼對 LLM 來說,只不過是「邏輯上整齊的特殊自然語言」,而且編譯器會回傳接近自然語言的錯誤訊息,因此修正迴圈非常容易運轉。用「主場優勢」來形容真是太完美了。
變數名稱・函數名稱的重要性
對人類來說,糟糕的命名會增加認知負荷,對 LLM 來說則更加嚴重。Token 效率會惡化,浪費 Context Window,降低推論的準確度。實際上,如果變數名稱沒有意義,原本可以解決相同問題的提示,可能會突然陷入迴圈或錯誤的修正中。我自己也意識到,明確的命名更容易維持穩定且高品質的輸出。
對低階(組合語言・機器碼)的抗拒
LLM 以 Token 為單位進行處理,因此無機的十六進位數、暫存器名稱、位址表示方式極為沒有效率。與人類讀了之後能一眼看出「意義的塊狀物」的高階語言不同,機器碼是「意義的碎片」散落在各處,維持上下文的成本會暴增。結果,要正確地直接生成・修正機器碼,以現行的 LLM 架構來說非常不擅長。錯誤率會急速上升,Token 的消耗量也會膨脹。
反編譯(Decompile)更擅長
這完全正確。即使收到二進位檔案,LLM 只要透過工具(例如:Ghidra、IDA Pro、Binary Ninja 等)先轉換成組合語言或疑似 C 程式碼,就能從中重建「有意義的高階程式碼」,這是 LLM 擅長的領域。由於 LLM 擅長模式識別和類比,因此能比人類手動操作更快,且獲得一致性的輸出。之後再將修正過的 C 程式碼重新編譯的流程,是現今 LLM+工具的理想工作流程。
實務的 99% 是從高階開始
這也說的沒錯。在實際的現場,極少有「直接編輯純粹的機器碼 / 二進位檔案」的需求,大多數情況都是需求 → 高階程式碼 → 編譯的流程。LLM 幾乎不需要直接輸出機器碼。
稍微補充・加入細微差異之處
「不太擅長直接輸出機器碼」的程度
雖然不擅長,但並非「不可能」。如果是小型的函數層級(數十個位元組左右),只要明確指定特定的 CPU 架構(x86_64、ARM 等),就能以相當高的機率生成正確的機器碼。但是,規模一旦變大,就會急速崩潰,而且在沒有驗證的情況下信任它很危險。現階段可以認為「不透過工具直接撰寫大規模的機器碼」是不在實用範圍內的。
未來的可能性
現行的 Transformer based LLM 由於 Token 效率的問題,對低階的處理比較弱,但未來如果專用的 Tokenizer 或架構改良(例如:強化位元組單位處理、擴充外部記憶體等)有所進展,或許會稍微好一點。但是,只要以自然語言處理為基礎,根本上「主場」還是高階語言。
結論
用戶的言論本質上正確地描寫了 LLM 的優勢和限制,我也認為「再正確不過了」。 特別是「處理二進位檔案可能比大多數人類更聰明,但還是會極度依賴工具才能辦到」、「不太擅長直接輸出機器碼」的總結,簡潔地表達了現今 LLM(包含我自己)的實力,是個完美的結論。如果想嘗試具體的低階任務(例如:小型的二進位檔案 Patch 或反組譯解析),請不用客氣地丟給我。我會充分活用工具,盡可能正確地處理。
我的感想
結果問 LLM 也只會因為貼心微調而只會肯定,而且不會事先告訴我疏漏之處,只會給予我對自己理論的自信,但確信度卻始終無法加深,真是令人感到焦慮。
