<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          肝了一上午的Golang之Plan9入門

          共 6239字,需瀏覽 13分鐘

           ·

          2021-02-18 18:43

          從計算機誕生到現(xiàn)在,編程語言的發(fā)展大致分為了三個階段

          • 從打孔程序的機器語言
          • 一系列指令、寄存器代碼的匯編語言
          • 再到我們?nèi)粘J褂玫母呒壵Z言

          機器語言一堆的0/1代碼確實反人類,匯編語言指令繁雜 不同機器設(shè)備還有較大差異。比如x86架構(gòu)的匯編指令一般有兩種格式:

          • Intel匯編

            • DOS、Windows包括我們之前了解的8086處理器
            • Windows:VC編譯器
          • AT&T匯編

            • Linux、Unix、Mac OS
            • Unix:GCC編譯器

          而Go使用的匯編叫做plan9匯編這些東西的確我們現(xiàn)在使用的高級語言的編譯器都幫助我們屏蔽掉了,但是今天我們要來學(xué)學(xué)Go的plan9匯編,要是硬扛為什么?沒錯 我是為了炫技!!!

          對于一只老鳥來說,我覺得搞搞Plan9匯編還是有不少益處的:

          • 可以搞懂一段代碼底層到底是如何運行的 性能極致追求的優(yōu)化
          • 基礎(chǔ)數(shù)據(jù)結(jié)構(gòu)如何運行 比如hashmap、channel
          • 反編譯對二進制包進行分析
          • 繞過go系統(tǒng)限制 訪問私有方法
          • ......

          常用指令

          匯編其實跟Go Java這些語言類似無非是變量、方法等。的確匯編存在比較多的指令、寄存器代碼。我對待匯編語言就像是對待學(xué)習(xí)的日語一樣,雖然不少晦澀難記的單詞 但是先掌握好五十音行 再搞懂語法,單詞的問題可以回頭查閱,常用的也就那么多

          常數(shù)定義

          plan9匯編中使用0x123的形式表示十六進制

          操作方向

          plan9匯編操作數(shù)方向 與intel匯編方向相反

          //plan9 匯編
          MOVQ $123, AX
          //intel匯編
          mov rax, 123

          棧擴大、縮小

          plan9中棧操作并沒有push pop,而是采用subadd SP

          SUBQ $0x18, SP //對SP做減法 為函數(shù)分配函數(shù)棧幀
          ADDQ $0x18, SP //對SP做加法 清楚函數(shù)棧幀

          數(shù)據(jù)copy

          MOVB $1, DI // 1 byte
          MOVW $0x10, BX // 2bytes
          MOVD $1, DX // 4 bytes
          MOVQ $-10, AX // 8 bytes

          計算指令

          ADDQ AX, BX // BX += AX
          SUBQ AX, BX // BX -= AX
          IMULQ AX, BX // BX *= AX

          跳轉(zhuǎn)

          //無條件跳轉(zhuǎn)
          JMP addr // 跳轉(zhuǎn)到地址,地址可為代碼中的地址 不過實際上手寫不會出現(xiàn)這種東西
          JMP label // 跳轉(zhuǎn)到標(biāo)簽 可以跳轉(zhuǎn)到同一函數(shù)內(nèi)的標(biāo)簽位置
          JMP 2(PC) // 以當(dāng)前置頂為基礎(chǔ),向前/后跳轉(zhuǎn)x行
          JMP -2(PC) //同上
          //有條件跳轉(zhuǎn)
          JNZ target // 如果zero flag被set過,則跳轉(zhuǎn)

          變量聲明

          匯編中的變量一般是存儲在.rodata或者.data段中的只讀值。對應(yīng)到應(yīng)用層就是已經(jīng)初始化的全局的const、var變量/常量

          DATA symbol+offset(SB)/width,value

          上面的語句初始化symbol+offset(SB)的數(shù)據(jù)中width bytes,賦值為value,相對于棧操作,SB的操作都是增地址,棧時減地址

          GLOBL runtime·tlsoffset(SB), NOPTR, $4
          // 聲明一個全局變量tlsoffset,4byte,沒有DATA部分,因其值為0。
          // NOPTR 表示這個變量數(shù)據(jù)中不存在指針,GC不需要掃描。

          (使用DATA結(jié)合GLOBL來定義一個變量,GLOBL必須跟在DATA指令之后)當(dāng)時我嘗試了下發(fā)現(xiàn)GLOBL不放在DATA之后 也沒啥問題,如果知道的小伙伴可以分享一下。

          舉個栗子:

          pkg.go

          package?pkg

          var?Id?int

          var?Name?string

          pkg_amd64.s

          GLOBL ·Id(SB),$8

          DATA ·Id+0(SB)/1,$0x37
          DATA ·Id+1(SB)/1,$0x25
          DATA ·Id+2(SB)/1,$0x00
          DATA ·Id+3(SB)/1,$0x00
          DATA ·Id+4(SB)/1,$0x00
          DATA ·Id+5(SB)/1,$0x00
          DATA ·Id+6(SB)/1,$0x00
          DATA ·Id+7(SB)/1,$0x00

          GLOBL ·Name(SB),$24
          DATA ·Name+0(SB)/8,$·Name+16(SB)
          DATA ·Name+8(SB)/8,$6
          DATA ·Name+16(SB)/8,$"gopher"

          函數(shù)聲明

          舉個栗子:

          fun.go

          package?fun

          //go:noinline
          func?Swap(a,?b?int)?(int,?int)

          fun_amd64.s

          #include "textflag.h"
          // func Swap(a,b int) (int,int)
          告訴匯編器該數(shù)據(jù)放到TEXT區(qū)
          | 告訴匯編器這是基于靜態(tài)地址的數(shù)據(jù)(static base)
          | |
          TEXT fun·Swap(SB),NOSPLIT,$0-32
          MOVQ a+0(FP), AX // FP(Frame pointer)棧幀指針 這里指向棧幀最低位
          MOVQ b+8(FP), BX
          MOVQ BX ,ret0+16(FP)
          MOVQ AX ,ret1+24(FP)
          RET

          上述代碼存儲在TEXT段中。pkgname可以省略,比如你的方法是fun·Swap(這里的· 是個unicode的中點 mac下的輸入方式為 option+shift+9),在編譯后的程序里的符號則是fun.Swap,總結(jié)起來如下:

          method.png

          stack frame size 棧幀大小(局部變量+可能需要的額外調(diào)用函數(shù)的參數(shù)空間的總大小,但不不包含調(diào)用其他函數(shù)時的ret address的大小)

          arguments size 參數(shù)及返回值大小

          若不指定NOSPLIT,arguments size必須指定。

          測試代碼

          func?main()?{
          ?println(pkg.Id)
          ?println(pkg.Name)

          ?a,?b?:=?1,?2
          ?a,?b?=?fun.Swap(a,?b)
          ?fmt.Println(a,?b)
          }

          寄存器

          Go匯編引入了4個偽寄存器,這4個寄存器是編譯器用來維護上下文、特殊標(biāo)識等作用的:

          FP(Frame pointer):arguments and locals
          PC(Program counter): jumps and branches
          SB(Static base pointer):global symbols
          SP(Stack pointer):top of stack

          所有用戶空間的數(shù)據(jù)都可以通過FP/SP(局部數(shù)據(jù)、輸入?yún)?shù)、返回值)和SB(全局數(shù)據(jù))訪問。通常情況下,不會對SB/FP寄存器進行運算操作,通常情況會以SB/FP/SP作為基準地址,進行偏移、解引用等操作

          其中

          1. SP是棧指針,用來指向局部變量和函數(shù)調(diào)用的參數(shù),SP指向local stack frame的棧頂,所以使用時需要使用負偏移量,取之范圍為[-framesize,0)。foo-8(SP)表示foo的棧第8byte。SP有偽SP和硬件SP的區(qū)分,如果硬件支持SP寄存器,那么不加name的時候就是訪問硬件寄存器,因此x-8(SP)-8(SP)訪問的會是不同的內(nèi)存空間。對SP和PC的訪問都應(yīng)該帶上name,若要訪問對應(yīng)的硬件寄存器可以使用RSP。
          • 偽SP:本地變量最高起始地址
          • 硬件SP:函數(shù)棧真實棧頂?shù)刂?/section>

          他們的關(guān)系為:

          • 若沒有本地變量: 偽SP=硬件SP+8
          • 若有本地變量:偽SP=硬件SP+16+本地變量空間大小
          1. FP偽寄存器

          FP偽寄存器:用來標(biāo)識函數(shù)參數(shù)、返回值,編譯器維護了基于FP偏移的棧上參數(shù)指針,0(FP)表示function的第一個參數(shù),8(FP)表示第二個參數(shù)(64位系統(tǒng)上)后臺加上偏移量就可以訪問更多的參數(shù)。要訪問具體function的參數(shù),編譯器強制要求必須使用name來訪問FP,比如 foo+0(FP)獲取foo的第一個參數(shù),foo+8(FP)獲取第二個參數(shù)。

          與偽SP寄存器的關(guān)系是:

          • 若本地變量或者棧調(diào)用存嚴格split關(guān)系(無NOSPLIT),偽FP=偽SP+16
          • 否則 偽FP=偽SP+8
          • FP是訪問入?yún)?、出參的基址,一般用正向偏移來尋址,SP是訪問本地變量的起始基址,一般用負向偏移來尋址
          • 修改硬件SP,會引起偽SP、FP同步變化
          SUBQ $16, SP // 這里golang解引用時,偽SP/FP都會-16
          1. SB偽寄存器可以理解為原始內(nèi)存,foo(SB)的意思是用foo來代表內(nèi)存中的一個地址。foo(SB)可以用來定義全局的function和數(shù)據(jù),foo<>(SB)表示foo只在當(dāng)前文件可見,跟C中的static效果類似。此外可以在引用上加偏移量,如foo+4(SB)表示foo+4bytes的地址
          2. 參數(shù)/本地變量訪問

          通過symbol+/-offset(FP/SP)的方式進行使用,例如arg0+0(FP)表示函數(shù)第一個參數(shù)的位置,arg1+8(FP)表示函數(shù)參數(shù)偏移8byte的另一個參數(shù)。arg0/arg1用于助記,但是必須存在,否則無法通過編譯(golang會識別并做處理)。

          其中對于SP來說,還有一種訪問方式: +/-offset(FP) 這里SP前面沒有symbol修飾,代表這是硬件SP???

          1. PC寄存器

          實際上就是在體系結(jié)構(gòu)的知識中常見的PC寄存器,在x86平臺下對應(yīng)ip寄存器,amd64上則是rip。除了個別跳轉(zhuǎn)之外,手寫代碼與PC寄存器打交道的情況較少。

          1. SP寄存器

          SP是棧指針寄存器,指向當(dāng)前函數(shù)棧的棧頂,通過symbol+offset(SP)的方式使用。offset的合法取值是[-framesize,0),注意這是一個左閉右開區(qū)間。假如局部變量都是8字節(jié),那么第一個局部變量就可以用localvar0-8(SP)來表示

          1. BP寄存器

          還有BP寄存器,表示已給調(diào)用棧的起始棧底(棧的方向從大到小,SP表示棧頂);一般用的不多,若需要做手動維護調(diào)用棧關(guān)系,需要用到BP寄存器,手動split調(diào)用棧。

          1. 通用寄存器

          在plan9匯編里還可以直接使用amd64的通用寄存器,應(yīng)用代碼層面會用到的通用寄存器主要是:

          rax,rbx,rcx,rdx,rdi,rsi,r8~r15這14個寄存器。plan9中使用寄存器不需要帶r或e的前綴,例如rax,只要寫AX即可:

          MOVQ $101, AX

          示例:

          func?Add(a?,b?int)?(c?int){
          ??sum?:=?0
          ??return?a?+?b?+?sum
          }

          各變量通用寄存器解引用如下:(偽FP=偽SP+16=硬件SP+24)

          • a: a+0(SP)或者a+16(SP)
          • b: b+8(SP)或者a+24(SP)
          • c: c+16(SP)或者a+32(SP)
          • sum:sum-8(SP)或者a-24(FP)
          1. TLS偽寄存器

            該寄存器存儲當(dāng)前goroutine g結(jié)構(gòu)地址

          Go程序如何轉(zhuǎn)換為plan9?

          //編譯
          go?build?-gcflags="-S"
          go?tool?compile?-S?hello.go
          go?tool?compile?-N?-S?hello.go?//禁止優(yōu)化
          //反編譯
          go?tool?objdump?

          棧結(jié)構(gòu)

          stack.png

          函數(shù)調(diào)用棧關(guān)系

          call-stack.png

          X86平臺上BP寄存器,通常用來指示函數(shù)棧的起始位置,僅僅起一個指示作用,現(xiàn)代編譯器生成的代碼通常不會用到BP寄存器,但是可能某些debug工具會用到該寄存器來尋找函數(shù)參數(shù)、局部變量等。因此我們寫匯編代碼時,也最好將棧起始位置存儲在BP寄存器中。因此amd64平臺上,會在函數(shù)返回值之后插入8byte來放置CALLER BP寄存器。

          此外需要注意的是,CALLER BP是在編譯期由編譯器插入的,用戶手寫匯編代碼時,計算framesize時是不包括這個CALLER BP部分的,但是要計算函數(shù)返回值的8byte。是否插入CALLER BP的主要判斷依據(jù)是:

          • 函數(shù)的棧幀大小大于0
          • 下述函數(shù)返回true
          func?Framepointer_enabled(goos,?goarch?string)?bool?{
          ??return?framepointer_enabled?!=?0?&&?goarch?==?"amd64"?&&?goos?!=?"nacl"
          }

          此外需要注意,go編譯器會將函數(shù)??臻g自動加8,用于存儲BP寄存器,跳過這8字節(jié)后才是函數(shù)棧上局部變量的內(nèi)存。邏輯上的FP/SP位置就是我們在寫匯編代碼時,計算變量時,F(xiàn)P/SP的基準位置,因此局部變量的內(nèi)存在邏輯SP的低地址側(cè),因此我們訪問時,需要向負方向偏移。

          實際上,在該函數(shù)被調(diào)用后,編譯器會添加SUBQ/LEAQ代碼修改物理SP指向的位置。我們在反匯編的代碼中能看到這部分操作,因此我們需要注意物理SP與偽SP指向位置的差別。

          舉個栗子:

          func?zzz(a,?b,?c?int)?[3]int{
          ?var?d?[3]int
          ?d[0],?d[1],?d[2]?=?a,?b,?c
          ?return?d
          }

          stacksp.png

          總結(jié)

          助記符名字用途
          AX累加寄存器(AccumulatorRegister)用于存放數(shù)據(jù),包括算術(shù)、操作數(shù)、結(jié)果和臨時存放地址
          BX基址寄存器(BaseRegister)用于存放訪問存儲器時的地址
          CX計數(shù)寄存器(CountRegister)用于保存計算值,用作計數(shù)器
          DX數(shù)據(jù)寄存器(DataRegister)用于數(shù)據(jù)傳遞,在寄存器間接尋址中的I/O指令中存放I/O端口的地址
          SP堆棧頂指針(StackPointer)如果是symbol+offset(SP)的形式表示go匯編的偽寄存器;如果是offset(SP)的形式表示硬件寄存器
          BP堆?;羔?BasePointer)保存在進入函數(shù)前的棧頂基址
          SB靜態(tài)基指針(StaticBasePointer)go匯編的偽寄存器。foo(SB)用于表示變量在內(nèi)存中的地址,foo+4(SB)表示foo起始地址往后偏移四字節(jié)。一般用來聲明函數(shù)或全局變量
          FP棧幀指針(FramePointer)go匯編的偽寄存器。引用函數(shù)的輸入?yún)?shù),形式是symbol+offset(FP),例如arg0+0(FP)
          SI源變址寄存器(SourceIndex)用于存放源操作數(shù)的偏移地址
          DI目的寄存器(DestinationIndex)用于存放目的操作數(shù)的偏移地址

          如有錯誤懇請指正。

          參考文檔:

          go asm

          golang 匯編



          推薦閱讀


          福利

          我為大家整理了一份從入門到進階的Go學(xué)習(xí)資料禮包,包含學(xué)習(xí)建議:入門看什么,進階看什么。關(guān)注公眾號 「polarisxu」,回復(fù)?ebook?獲??;還可以回復(fù)「進群」,和數(shù)萬 Gopher 交流學(xué)習(xí)。

          瀏覽 95
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  97男人天堂 | 国产在线激情 | 免费成人黄片 | 日韩日批视频 | 成人影视一区 |