<kbd id="5sdj3"></kbd>
<th id="5sdj3"></th>

  • <dd id="5sdj3"><form id="5sdj3"></form></dd>
    <td id="5sdj3"><form id="5sdj3"><big id="5sdj3"></big></form></td><del id="5sdj3"></del>

  • <dd id="5sdj3"></dd>
    <dfn id="5sdj3"></dfn>
  • <th id="5sdj3"></th>
    <tfoot id="5sdj3"><menuitem id="5sdj3"></menuitem></tfoot>

  • <td id="5sdj3"><form id="5sdj3"><menu id="5sdj3"></menu></form></td>
  • <kbd id="5sdj3"><form id="5sdj3"></form></kbd>

    Go 是如何確保內(nèi)存安全的?

    共 3639字,需瀏覽 8分鐘

     ·

    2020-11-07 07:08

    點擊上方藍色“Go語言中文網(wǎng)”關(guān)注,每天一起學 Go

    Illustration created for “A Journey With Go”, made from the original Go Gopher, created by Renee French.

    ??這篇文章基于 Go 1.13 編寫。

    Go 的一系列內(nèi)存管理手段(內(nèi)存分配,垃圾回收,內(nèi)存訪問檢查)使許多開發(fā)者的開發(fā)工作變得很輕松。編譯器通過在代碼中引入“邊界檢查” 來確保安全地訪問內(nèi)存。

    生成的指令

    Go 引入了一些控制點位,來確保我們的程序訪問的內(nèi)存片段安全且有效的。讓我們從一個簡單的例子開始:

    package?main

    func?main()?{
    ????list?:=?[]int{1,?2,?3}

    ????printList(list)
    }

    func?printList(list?[]int)?{
    ????println(list[2])
    ????println(list[3])
    }

    這段代碼跑起來之后會 panic:

    3
    panic:?runtime?error:?index?out?of?range?[3]?with?length?3

    Go 通過添加邊界檢查來防止不正確的內(nèi)存訪問

    如果你想知道沒有這些檢查會怎么樣,你可以使用 -gcflags="-B" 的選項,輸出如下

    3
    824633993168

    因為這塊內(nèi)存是無效的,它會讀取不屬于這個 slice 的下一個 bytes。

    利用命令 go tool compile -S main.go 來生成對應的匯編[1]代碼,就可以看到這些檢查點:

    0x0021?00033?(main.go:10)??MOVQ???"".list+48(SP),?CX
    0x0026?00038?(main.go:10)??CMPQ???CX,?$2
    0x002a?00042?(main.go:10)??JLS????161
    [...]?here?Go?prints?the?third?element
    0x0057?00087?(main.go:11)??MOVQ???"".list+48(SP),?CX
    0x005c?00092?(main.go:11)??CMPQ???CX,?$3
    0x0060?00096?(main.go:11)??JLS????151
    [...]
    0x0096?00150?(main.go:12)??RET
    0x0097?00151?(main.go:11)??MOVL???$3,?AX
    0x009c?00156?(main.go:11)??CALL???runtime.panicIndex(SB)
    0x00a1?00161?(main.go:10)??MOVL???$2,?AX
    0x00a6?00166?(main.go:10)??CALL???runtime.panicIndex(SB)

    Go 先使用 MOVQ 指令將 list 變量的長度放入寄存器 CX

    0x0021?00033?(main.go:10)??MOVQ???"".list+48(SP),?CX

    友情提醒,slice 類型的變量由三部分組成,指向底層數(shù)組的指針、長度,容量(capacity)。list 變量在棧中的位置如下圖:

    通過將棧指針移動 48 個字節(jié)就可以訪問長度

    下一條指令將 slice 的長度與程序即將訪問的偏移量進行比較

    CMPQ 指令會將兩個值相減,并在下一條指令中與 0 進行比較。如果 slice 的長度(寄存器 CX)減去要訪問的偏移量(在這個例子當中是 2)小于或等于 0(JLSJump on lower or the same 的縮寫),程序就會跳到 161 處繼續(xù)執(zhí)行。

    兩種邊界檢查使用的都是相同的指令。除了看生成的匯編代碼,Go 提供了一個編譯期的通行證去打印出邊界檢查的點,你可以在 buildrun 的時候使用標志 -gcflags="-d=ssa/check_bce/debug=1" 去開啟。輸出如下:

    ./main.go:10:14:?Found?IsInBounds
    ./main.go:11:14:?Found?IsInBounds

    我們可以看到輸出里生成了兩個檢查點。不過 Go 編譯器足夠聰明,在不需要的情況下,它不會生成邊界檢查的指令。

    規(guī)則

    在每次訪問內(nèi)存的時候都生成檢查指令是非常低效的,讓我們稍微修改一下前面的例子。

    package?main

    func?main()?{
    ????list?:=?[]int{1,?2,?3}

    ????printList(list)
    }

    func?printList(list?[]int)?{
    ????println(list[3])
    ????println(list[2])
    }

    兩個 println 指令對調(diào)了,用 check_bce 標志再去跑一遍程序,這次只有一處邊界檢查:

    ./main.go:11:14:?Found?IsInBounds

    程序先檢查了偏移量 3 。如果是有效的,那么 2 很明顯也是有效的,沒必要再去檢查了??梢酝ㄟ^命令 GOSSAFUNC=printList Go run main.go 來生成 SSA 代碼看編譯過程。這張圖就是生成的帶邊界檢查的 SSA 代碼:

    里面的 prove pass 將邊界檢查標記為移除,這樣后面的 pass 將會收集這些 dead code:

    用這條命令 GOSSAFUNC=printList Go run -gcflags="-d=ssa/prove/debug=3" main.go 可以把 pass 背后的邏輯打印出來,它也會生成 SSA 文件來幫助你 debug,接下來看命令的輸出:

    這個 pass 實際上會采取不同的策略,并建立了 fact 表。這些 fact 決定了矛盾點在哪里。在我們這個例子里,我們可以通過 SSA 的 pass 來解讀這些規(guī)則:

    第一個階段從代表指令 println(list[3]) 的分析塊 b1 開始,這個指令有兩種可能:

    • 偏移量 [3] 在邊界中,跳到第二個指令 b2。在這個例子中,Go 指定 v7 的限制(slice 的長度)是 [4, max(int)]。
    • 偏移量 [3 不在邊界中, 程序跳轉(zhuǎn)到 b3 指令并 panic。

    接下來,Go 開始處理 b2 塊(第二個指令)。這里也有兩種可能

    • 偏移量 [2] 在邊界中,這意味著 slice 的長度 v7v23(偏移量 [2]) 要大。在先前的 b1 塊中 Go 已經(jīng)判斷了 v7 > 4, 所以這個已經(jīng)被確認了。
    • 偏移量 [2] 不在邊界中,這意味著它比 slice 的長度 v7 更大,但 v7 的限制是 [4, max(int)] ,所以 Go 會將這個分之標記為矛盾,意味著這種情況永遠不會發(fā)生,這條指令的邊界檢查可以被移除。

    這個 pass 在隨著時間不斷地改善,現(xiàn)在可以參考更多的 case[2]。消除邊界檢查可以略微提升 Go 程序的運行速度,但除非你的程序是微妙級敏感的,不然沒有必要去優(yōu)化它。


    via: https://medium.com/a-journey-with-go/go-memory-safety-with-bounds-check-1397bef748b5

    作者: Vincent Blanchon[3]譯者:yxlimo[4]校對:Alex.Jiang[5]本文由 GCTT[6] 原創(chuàng)編譯,Go 中文網(wǎng)[7] 榮譽推出

    參考資料

    [1]

    匯編: https://golang.org/doc/asm

    [2]

    更多的 case: https://github.com/golang/go/blob/master/test/prove.go

    [3]

    Vincent Blanchon: https://medium.com/@blanchon.vincent

    [4]

    yxlimo: https://github.com/yxlimo

    [5]

    Alex.Jiang: https://github.com/JYSDeveloper

    [6]

    GCTT: https://github.com/studygolang/GCTT

    [7]

    Go 中文網(wǎng): https://studygolang.com/



    推薦閱讀


    福利

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


    瀏覽 99
    點贊
    評論
    收藏
    分享

    手機掃一掃分享

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

    手機掃一掃分享

    分享
    舉報

    <kbd id="5sdj3"></kbd>
    <th id="5sdj3"></th>

  • <dd id="5sdj3"><form id="5sdj3"></form></dd>
    <td id="5sdj3"><form id="5sdj3"><big id="5sdj3"></big></form></td><del id="5sdj3"></del>

  • <dd id="5sdj3"></dd>
    <dfn id="5sdj3"></dfn>
  • <th id="5sdj3"></th>
    <tfoot id="5sdj3"><menuitem id="5sdj3"></menuitem></tfoot>

  • <td id="5sdj3"><form id="5sdj3"><menu id="5sdj3"></menu></form></td>
  • <kbd id="5sdj3"><form id="5sdj3"></form></kbd>
    中文字幕第一页AV | 香焦尻屄视频影院 | 三级无码Av | 免费AⅤ在线 | 五月天色色网址 |