這是我3月2日視頻分享的文字稿,從匯編語言的本質,學習匯編語言的意義講到學習方法。
關鍵詞
分享 知識系統 匯編語言 軟件 系統軟件 芯片 中間件 內核 系統調用 編程模型 匯編 應用層 反匯編 編譯器 邏輯運算 操作系統 軟硬件接口 體系結構
以下是文字記錄
〇、作者簡介
在正文之前給先大家做一個簡單的自我介紹,自我介紹和后面的內容也是有一定聯系的。我大學學的是微電子專業,也就是芯片專業。從2005年讀研開始做單片機的開發,后來又去做Linux的系統移植。從那之后基本上一直都是做軟件,芯片的東西也有關注,但是沒有具體做過,最后一次具體做項目,應該是讀研的時候實驗室做過一些芯片相關的東西。所以我其實是沒有接受過一個特別完整的計算機專業的受訓經歷的,很多東西都是用到了或者遇到問題的時候再去學習。我在學習的時候特別注重兩點,一點是注重知識的系統,另一點是重視回到原始的文檔。一、匯編語言的本質
今天的分享里面也會涉及到知識的系統和原始文檔這兩點。首先我們為什么要聊要不要學習匯編語言,因為我們不管在學習任何知識的時候,一定都要有兩個視角。一個是在這個知識里面,搞清楚這個知識本身是怎么回事。另外一個就是跳出這個知識或者這個技能來看看,從外面往里面看,看看這是什么東西。所以今天的分享,我們先從外往里面先看一下。首先匯編語言是個什么東西,按照我的理解,匯編語言其實就是計算機軟件和硬件的接口。所以這個接口意味著什么呢,意味著我們往下走,可以去了解計算機體系結構,我們往上走,可以去了解操作系統軟件。二、學習匯編語言的意義
我自己一直是在做系統軟件的開發,我的工作經驗中其實有很多都是操作系統相關的軟件。這里面有幾個項目都是直接或者間接的用到了匯編語言的技能,給大家先舉幾個例子。第一個例子是,大概十年前我在中星微工作的時候,當時負責芯片平臺的CPU和環境軟件的支持。其中有一項任務是機臺測試,機臺測試是什么呢,就是我們要去支持在芯片的生產線上去篩選合格和不合格的芯片,這里面需要提供測試用例,有一部分代碼必須要使用匯編去寫,這個是我第一次手寫匯編語言。不僅僅是我在這個經歷里面手寫了匯編語言,同時為了手寫匯編語言還要去了解函數之間是怎么調用的。之前我也知道 ABI 這個概念,ABI 就是 Application Binary lnterface 應用程序的二進制接口。我知道這個概念,但是并不確切知道函數調用之間要符合什么樣的規范。我也是在那次的機臺測試的時候具體的看了ARM的AAPCS手冊,也就是ARM Architecture Procedure Call Standard 的ABI手冊。我了解到這個手冊之后,我才對這個函數調用之間關系有一個系統的了解。這個也對應到前面我們說的點,就是要注重知識的系統和原始的文檔。第二個例子是,我在SUSE工作的時候遇到的,大家都知道SUSE是第二大的Linux發行版公司,第一大的是RedHat。我當時是做虛擬化,主要負責的是中間件的東西,主要是libvirt和QEMU,也有一點點Xen的工作。這個工作本身它是比較靠上層的,那里面主要的libvirt和QEMU都是用C語言寫的,這可以看成是一個C語言的應用開發,這個工作本身幾乎是不涉及到匯編語言的。但是那時候發生個事情,就是我們在某一個場景下面,發現那個系統啟動有問題。熟悉虛擬化的同學都知道,QEMU里面的系統啟動方式跟我們物理機可以一樣,也可以不一樣。我當時就發現這個問題很奇怪,因為我之前一直做ARM的東西,對x86平臺其實是不了解的。此時我有兩個選擇一個選擇,一個是去看x86相關的資料,看一看BIOS這個流程怎么樣,啟動流程怎么樣。但那時候我看了之后發現Google直接得到資料一般都不太具體,它比較抽象,講的都是大概的流程,沒有具體的東西。這個沒法支持到我怎么去解決問題,于是那個時候我就嘗試著通過匯編語言去看一下BIOS那個代碼。我發現,它原來是在某個地方蹦到某個地址,再做跳轉,從而完成了從BIOS到下一級BootLoader的啟動。第三個例子就是,我在華為工作的時候做一個跟 ABI相關的事情,我之前也提到過, 就是AARCH64的ILP32的工作,相當于是我在64位的系統里面去用匯編,但是我要支持32位的ELF。這個里面基本上來說因為編譯器當時已經差不多做好了,雖然有些bug,但是基本上是沒問題的。因為我當時解決的主要是要完善glibc跟內核的這個工作本身幾乎不涉及到匯編語言,除了 ld.so和系統調用,例如系統調用中我要考慮怎么去給應用程序給系統將傳參。但是在這個項目里面,我發現了解匯編語言還是很有幫助的,為什么呢,因為你去解一些bug的時候,有些地方如果對匯編語言不了解,會很不方便。比如說那時候我們是大端系統,我們首先就要考慮兩個問題,就是大小端的轉換。我們既要保證這個代碼大端是正確的,要保證小端是正確的。第二個事我們要考慮32位和64位的轉換,因為我用戶端是32位的ELF,進到內核里面是64位的,這兩個地方它經常會出一些問題。這個時候除了做代碼級的分析之外,另外一個方式就是看反匯編,看看它這個地方32位和64位的關系到底是怎么處理的。從這三個例子大家可以看到匯編語言其實不僅僅是我們做底層開發,比如開發操作系統,或者說調內核調驅動的時候需要,它其實也是我們去理解CPU的編程模型,去理解系統的一個很好的切入點。所以回到我們最原始的問題,要不要學匯編語言。我自己的體會是,如果工作是涉及到系統軟件和中間件,這個是CPU相關的,這個匯編語言是要學習的。如果不是這樣,這種工作可能比如說是JAVA 或者別的語言,我覺得相關的像ByteCode的這種相應字節碼的一些相關的知識可能也是需要的。三、如何學習匯編語言
我們前面主要聊了一下要不要學匯編語言,我們下一段主要是分享如何學習匯編語言。那聯想一下,前面咱們做內核知識的分享,當時我們分享的時候提到一個點就是軟件的分層。大家應該對分層這點都比較熟悉了,我們在學習匯編語言的時候,也可以借助這樣一個思想,我先學習什么樣的匯編語言呢,先學習哪個層次跟相關的匯編語言呢,顯然應用層可能會比較容易一點。剛開始去看匯編的時候,可以先看一看這個應用層匯編是怎么回事,應用層匯編包括什么內容,我們就可以回到基本的程序設計語言,它基本結構是怎么樣的。就是這個順序判斷循環對吧,它這里面就包括了算術運算和邏輯運算,還包括判斷和跳轉這些基本的指令,有了這些指令之后,我們可以在比如說我們涉及匯編語言和C語言的調用,匯編語言和C++的調用,比如說涉及到的一些 ABI 相關的事情,我們可以有一個匯編的切入點可以去進行查看。同時,如果說crash了或者coredump了或者內核 crash 了,我們借助匯編語言,可以去看一下具體出錯那個地方,因為反匯編得到的C語言的行號可能沒有那么準確。那除此之外,我們在了解了算數運算邏輯運算這些基本的匯編之后,還可以再去深入的學習。在這個地方我們先停留一下,我們怎么去學呢,比較直接的辦法當然是我們找一些資料去學習,這肯定是沒問題的。還有什么辦法呢,比如我去看體系架構的手冊,比如看x86、ARM、RISC-V的架構手冊,我們去看這些手冊里面具體是怎么寫的。我覺得這是一個最終的目標,如果最終我們能夠基于這些手冊去解決我們匯編語言相關的問題,那么這個匯編語言的學習就比較到位了,可以說是已經到了一個可以自己進行自我迭代的一個程度了。問題在于我們怎么能夠進入到這樣一個狀態,直接去看這些架構手冊的話還是非常有挑戰性的。那么我們學習匯編語言的方式應該是什么呢,這個方式就可以是,我們自己寫段代碼,不要開啟編譯器的-O3等各種優化選項,然后看看反匯編是什么樣子的。這樣我們就能對基本的匯編語言有一個基本的了解。當然在實際工作中,我們可能會開啟 -O3 -Os這些編譯選項,這些都沒關系的。我們再去對比它開與不開有什么區別,匯編指令有哪些不同,這樣我們就有了第一手的素材,對匯編語言就有了一個基本的了解。同時在這個層次上可能還會出現一個什么情況,我們可能有時候會看到一些內嵌匯編,那看到內嵌匯編,比如說是GCC的內嵌匯編,根據我們前面說的我的學習方法,我們還要回到原始的文檔。那么GCC的匯編語言,就要去看GCC的手冊,GCC關于內嵌匯編有很詳細的說明,詳細的說明里面我們能不能根據詳細的說明去理解,我該怎么去寫這些GCC的內嵌匯編的。同時這個地方涉及到一些ABI的知識,就是什么情況下我需要用那些extra的一些flag(參見參考資料1)。那么這個地方你可以看到它其實又是一個軟硬件的一個接口,我們其實是在學習匯編語言,其實看的是我們對于系統軟件的了解,對于 CPU 架構的了解。那么我們對于簡單的匯編如算術邏輯運算判斷跳轉有了一個了解之后,我們再往下走是什么呢。我們會看到一些比如說操作內核,它用的匯編跟應用的匯編有什么區別。區別就是操作系統內核是一個privilege,特權級別,這個特權級別它就會對應到一些特權相關的指令。比如說如何進入和退出一個特權級別,比如說如何去操作一些 CPU 的控制寄存器。這些控制寄存器可能涉及到操作系統的一些能力,比如說中斷使能、系統調用的路由、中斷路由這些事情。在這個地方其實我們有可以看到,匯編語言和CPU架構其實是高度相關的。也就是說我們可以在學習某個CPU架構的時候,我們去對照匯編去看一些操作系統比如Linux、RTT,它是怎么去寫這個匯編語言的。同時我們也可以基于某一次對操作系統比如Linux的一個調試,我們基于調試中我們看到一些我們之前沒有見過的匯編的一些語言,再切入回來,再去看一看它跟CPU架構有時么關系,哪些是跟CPU架構相關的。除了我們剛剛說的系統分層這塊之外,系統分層里面還有什么內容呢。比如說Memory模型,像ARM和RISC-V,它都是統一尋址的,如果是x86,它其實還有傳統的方式,還包括一個IO尋址的方式。這種東西它在匯編語言里面也有會有所區別,同時在Memory模型里面還有一塊頁表處理。頁表處理里面頁表基址在什么地方,有幾個頁表基址,這些又都涉及到匯編語言去訪問特殊功能的寄存器。那通過這樣一個學習呢,我們基本來說對于匯編語言就有了一個整體的了解了,不管是應用掛了還是操作系統內核掛了我們都可以去調試。那這個是不是就學完了呢,還沒有,為什么呢,因為還有一個很重要的概念就是pseudo assembly,就是偽匯編。偽匯編這個東西,我們平時用得非常廣泛。大家平時不知道有沒有遇到過這樣的問題,我看到一段代碼,但是我去搜手冊就是沒搜到這個匯編在哪,但是它明明是匯編器能編譯過的,還是能用的一個商業/開源軟件。它為什么就不知道在哪了,那這個就是偽匯編,對吧。其實它并不單獨對應某一條機器指令,它其實是某個匯編指令的一個變化,或者是多條指令的集合。那偽匯編其實是跟架構相關的,不同的CPU架構它會不一樣。比如說RISC-V,它其實在RISC-V spec里面是有寫到的。它也有專門的ASM手冊,里面也會單獨寫都有哪些偽匯編指令。了解偽匯編指令,其實是很重要的,就是看我們的匯編語言學習到底是不是實操,到底我只是知道紙面上知道這些匯編語言,還是說我真正在工程里面調過匯編語言。因為工程里面它有很多都是偽匯編了,如果我沒有看位匯編的話,那么可能在實際工程中看別人寫的代碼就會比較費勁。OK, 剛剛說到基本的算術邏輯運算判斷跳轉,還有特權指令,Memory模型、偽匯編,除此之外匯編語言里面還有什么內容呢?其實還有一些內容,比如說像x86的3DNow ?MMX這種多媒體指令。還有ARM的NEON指令,VFP的這種浮點指令。這些指令它也會對應的匯編。這些匯編和前面的邏輯差不多,我們也是要看基本的應用是怎么用的,然后再看它還要考慮一點,整數跟這種浮點之間它相互調用是怎么操作的,這個地方我就不展開講了。四、推薦的書籍與文檔
所以可以看到,其實我們在整個學習過程中,我覺得比較重要的一點就是注重回到原始文檔。原始文檔有什么,比如說 CPU的架構手冊,比如操作系統的代碼,比如操作系統的一些重要的書籍。我是建議大家日常至少應該有兩本,一本是講 CPU講計算機組成原理的書,比如說《計算機組成原理與設計軟硬件接口》那本書,然后還有一塊應該就是操作系統的書了,比如說我最近正在看陳海波老師寫的《現代操作系統》。我覺得經典的書都可以,也不用說去糾結到底看哪一本。我的建議是我們對照CPU和操作系統的兩本書去看,我們會發現有些內容CPU的書里面講的比較多,有些內容操作系統的書里面講得比較多一點。我們要不斷的注重回到原始的文檔,除了計算機體系結構和操作系統的書之外呢,平時可能還需要看的就是這個ABI相關的這些手冊。大家可以趁這個機會去看一看,大家自己平時用的常用的CPU就是 x86、ARM、RISC-V,那這里面哪些資料你可以再去看一看。大家可以先把自己手頭的資料給它補齊,補齊之后呢我們基于問題再去對應相應的資料,最終目標應該是能夠看到原始的文檔去解決問題。五、總結
這個就是我今天的分享。總結一下,今天我是從自我介紹開始的。因為我是轉專業的,所以我會比較重視知識的系統和原始的文檔。今天我們是從匯編語言是軟硬件接口開始分享。我首先舉了三個我自己的項目經歷,有些是明顯要寫匯編的,有些是看起來跟匯編語言無關,但是由于為了解決問題去看了匯編語言。還有一些,是跟體系結構操作系統有點關系,會比較多地涉及到通過匯編語言做調試。這也就是說,系統軟件包括中間件這些軟件可能我們在調試的時候了解匯編語言,對我們的工作可能會或多或少的有一些幫助。第二部分我分享的是如何學習匯編語言。我們站在分層的角度先去學習應用層次的匯編語言。然后再往深了一層去看特權級別的匯編語言有什么不一樣,這涉及到一些特權指令,涉及到Memory模型的一些內容,最后我們說偽匯編和浮點指令這些內容。我是張健,前華為架構師,前創業公司技術合伙人,目前自由職業。同時我是兩個孩子的爸爸。在職業發展,工作和生活如何平衡方面有很多第一手經驗。歡迎大家與我探討。- https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html
張健老師的技術小班課:ARM架構與調測調優打通ARM架構和操作系統,想繼續深化匯編語言,異常處理和內存管理的童鞋可以關注下。點擊閱讀原文直達課程介紹。