請教,像C/Go這種靜態語言,編譯可執行文件的過程,是否是先生成彙編然後由彙編生成機器碼?


有很多老編譯器是真的會先生成出文本形式的彙編,然後調用彙編器生成機器碼。

但現代編譯器多半都是把代碼生成整合進來,跳過文本形式的彙編,直接生成機器碼。

中間是否有文本形式的彙編,對概念來說是無關緊要的,只是個實現細節而已。跟「靜態語言」與否也沒有任何關係。

題主給問題打上了Go語言的標籤,那麼就看Go的自家編譯器在x86-64上的實現,6g,它在代碼生成時用的彙編器庫就是用Go寫的:

go/asm6.go at master · golang/go · GitHub

編譯器後端直接使用這個彙編器庫生成機器碼,而不是先生成文本形式的彙編後再調用單獨的彙編器去生成機器碼。


最近接觸了一點反彙編的皮毛。

小生不才 弱弱問一句。

按照我的理解,難道可執行文件的二進位保存的不就是有組織有結構的二進位形式的彙編命令集合嗎(這裡假定在windows x86 平台上的話)?

當然你說的彙編可能是彙編的字元形式 例如 mov eax,0

而二進位形式是 0xb8 0x00 0x00 0x00 0x00 (這裡是16進位表示二進位 一共是5個位元組)


是的,但是裡面又會有很多有意思的東西。

比如,java有位元組碼,llvm有IR。Go語言做的比較好的其中一方面特性就是:跨平台編譯。

Go的編譯器在編譯生成可執行的二進位程序的過程中,並不是直接生成我們普通意義上所說的彙編程序,比如arm彙編,x86彙編,plan9彙編程序。而是先生成一種偽彙編程序(pseudo-code)【1】,這種偽彙編代碼是對彙編程序的一種抽象。這是Go 1.5開始Go啟用的新的彙編程序。1.5之前Go面向不同平台的編譯器有很多種,5g,6g,8g等等,而現在只需要一個,實現了共享。

目的是:build a single assembler for all CPUs。

大致工作原理:

Go編譯器編譯時,生成偽彙編程序(可以想像成IR);

在運行時,parse會根據上下文生成翻譯表(這個過程是自動的,由機器去處理,根據某種架構的指令手冊pdf文件生成相應的表,即Go的偽彙編代碼和相應架構的指令彙編代碼之間的對應關係),再根據這些表將偽彙編轉化為相應平台的彙編代碼,最終由鏈接器(很多編譯器和鏈接器是一體的,這裡分開去說,方便理解其中的過程)生成可執行的機器碼。

【1】https://talks.golang.org/2016/asm.slide


彙編和機器碼就是一一對應的,比如jmp=0xEB,je=0x74,jne=0x75。否則的話,做逆向工程的時候如何修改指令?

當然靜態的執行文件和載入到內存里後的進程之間是有區別的,需要做相對地址和絕對地址的轉換,win平台還涉及到iat表中函數字元串和地址的轉換。


是的。

比如gcc編譯可以-S得到彙編文件,再獲得可執行文件。

直接-o獲得可執行文件就是整合了整個編譯的過程,←_←好像大部分編譯器都是這樣了。


簡單來說,是的。


推薦閱讀:

為什麼不能把so文件靜態鏈接到可執行文件中?
關於這段C代碼為什麼會輸出這種結果?
開發一個 C++ 編譯器的難度有多大,難點又在哪裡?
量子計算機的出現能否加快cpp的編譯時間?
為什麼給Mac編寫的軟體不能在Windows上運行?

TAG:彙編語言 | 編譯器 | Go語言 | 編譯 |