天天看點

了解Delve的記憶體檢視功能

作者:閃念基因

01

Delve是什麼

了解Delve的記憶體檢視功能
了解Delve的記憶體檢視功能

Delve是Golang的調試工具,能夠在Go程式運作時,提供斷點調試功能,檢視目前資料,甚至切換到不同協程進行資料檢視,大部分的IDE都已經內建了。

使用上面的功能,其實已經可以很友善的調試Go程式了,但它的功能還不止如此,接下來了解一下其中一個對深入了解Go語言有幫助的功能:記憶體檢視。

02

程式記憶體

了解Delve的記憶體檢視功能
了解Delve的記憶體檢視功能

程式在運作時,被從硬碟加載到記憶體中,大概長這個樣子

了解Delve的記憶體檢視功能

其中,程式的代碼被編譯成了機器碼,存在了程式代碼區,除此以外,最關注的應該就是棧和堆了。

棧的位址空間從高向低增長,堆的記憶體一般向上增長,但是由于Go語言自帶了記憶體管理器,以及由于協程的存在,實際擷取到的記憶體位址可能不完全按照一般情況。

為了接下來的内容,需要了結一下位元組序,位元組序一般為 大端存儲或者小端存儲,大端存儲的資料高位儲存在記憶體低位址位,小端存儲的資料高位儲存在記憶體高位址位。

linux系統可以通過lscpu來檢視Byte Order字段,大部分Intel和amd的cpu應該都是Little Endian(小端)。

這意味着,假設一個指針指向的位址是0xc0123456,該指針變量儲存的值應該從低位址到高位址分别是 0x56 0x34 0x12 0xc0(此處為了簡便,使用了4個位元組,實際上64位系統上指針變量應該占據8個位元組)

03

如何使用

了解Delve的記憶體檢視功能
了解Delve的記憶體檢視功能
了解Delve的記憶體檢視功能

安裝好Delve之後,就可以使用Delve來debug main函數所在的檔案了,如:dlv debug main.go,這樣就進入到了delve的指令行中。

然後就可以用b main.go:9 來在第9行設定一個斷點,如下圖所示

了解Delve的記憶體檢視功能

此時可以看到,dlv提供了一個位址,前面在這裡加了一個斷點,當程式運作到這個程式所在位址時,就會暫停。

可以現在進去看看,這個位址有什麼。因為已經知道,這個地方是程式代碼塊的位址,是以需要在此處使用反編譯方法,将記憶體中的機器碼轉化成彙編語言,而這個也是Delve的功能之一

運作:disass -a 0x495c75 0x495d75,從剛剛這個位址開始,往後0x100個位元組,将其進行反編譯,可以得到類似這樣的結果(未截全)

了解Delve的記憶體檢視功能

這裡就是在main函數調用println的彙編代碼(一部分)

現在繼續輸入c,Delve會運作至下一個斷點處,也就是println之前,這裡可以看到a,b這2個變量的一些資料

使用p指令可以擷取到對應變量的值,如:p a或者p b

了解Delve的記憶體檢視功能

這正是代碼中設定的值,使用p &a和p &b就可以看到對應的位址

了解Delve的記憶體檢視功能

這裡得到了2個變量的位址,這時就可以使用Delve的輸出記憶體原始資料的方法來看看這2個位址存的是什麼

使用x -fmt hex -len 32 0xc00010cef0,這裡x表示輸出原始資料,-fmt hex表示已16進制的方式格式化, -len 32表示輸出指定位址後的32位元組,然後可以得到類似

了解Delve的記憶體檢視功能

這裡每8個位元組一行,從位址低位到高位依次排列,由于int型在64位系統上占8位元組,是以可以到a所在記憶體起始位置後的8位元組,存的其實是一個變量,而它就是1。

後面的3行,對于開發者來說,并不知道它屬于誰或者表示什麼意思,因為記憶體是由Golang在維護。

同理,再看看b所在的位置

了解Delve的記憶體檢視功能

此處第一個8位元組,明顯并不是abc,而後面8位元組正好是3,即字元串的長度,這正好與Golang知識相符:字元串是一串指向連續記憶體的首位位址和長度組成的。

是以第一個8位元組應該是一個位址,是以再次跟蹤過去

了解Delve的記憶體檢視功能

這樣,我就發現該位址上的3位元組,正好是ascii的abc所對應的值,而由于剛剛的代碼是用utf8寫的,而utf8作為變長字元編碼格式,相容ascii,是以abc在utf8下正好就是0x61 0x62 0x63。

而由于上面看到的長度是3,是以隻能讀取前3位元組,後面的位元組還是不知道是什麼意思。

04

總結

了解Delve的記憶體檢視功能
了解Delve的記憶體檢視功能

通過一個簡單的例子了解了Delve的記憶體檢視相關功能,這個功能可以幫助加深對Golang資料結構的了解,并為優化代碼運作速度提供參考。

例如,在上面的例子說明字元串存在連續記憶體中,而在它長度後面的記憶體是未知資料,是以也就是說,如果我想要在這個字元串後面追加字元,不能直接在原來的記憶體後覆寫掉原來的資料。

此時Golang必須開辟一塊足夠大的新記憶體,然後把目前的字元串複制過去,最後再把需要追加的字元寫入進來。開辟記憶體就需要考慮GC,複制也會需要時間。

是以循環中拼接字元串時,就需要考慮是否會産生多次GC影響執行效率。

作者:裘铖

來源-微信公衆号:三七互娛技術團隊

出處:https://mp.weixin.qq.com/s/svedLIwIzbwtEEsO7kJqAQ

繼續閱讀