帶你視覺化理解go記憶體

語言: CN / TW / HK

圖片

導語 | 本文推選自騰訊雲開發者社群-【技思廣益 · 騰訊技術人原創集】專欄。該專欄是騰訊雲開發者社群為騰訊技術人與廣泛開發者打造的分享交流視窗。欄目邀約騰訊技術人分享原創的技術積澱,與廣泛開發者互啟迪共成長。本文作者是騰訊後臺開發工程師邵珠光。

在處理記憶體洩露的時候,想到了一種從記憶體中檢視哪些物件的問題,於是就對實際跑著的程式記憶體進行了解析,通過視覺化的方式有助於理解go的記憶體佈局和管理。

圖片

基礎知識

在本篇文章開始前,希望你可以瞭解go的一些基本的記憶體知識,不需要太深入,簡單總結了如下幾點:

(一)記憶體佈局

記憶體佈局包括記憶體對齊,一個結構體佔用記憶體大小等。另外,對於go語言而言,其記憶體中的堆物件中本身並沒有含有該物件的任何標識資訊,例如型別等。在go語言中屬性的佈局與程式碼順序有關,不會進行自動調整。

(二)常見型別

對於字串型別,實際上它是一個具有兩個屬性的結構:

type StringHeader struct { Data uintptr Len int }

對於陣列而言,它有三個屬性:

type SliceHeader struct { Data uintptr Len int Cap int }

也就是說,如果我們看到一個結構體中含有字串,那麼這個字串佔多少個位元組呢?對於64位系統而言就是16個位元組,8個表示地址,另外8個表示長度。

圖片

測試程式碼

(一)定義兩個結構體

首先我們定義兩個結構體:

``` type User struct { Name string Age uint8 Sex uint8 class *Class }

type Class struct { CName string Index uint } ```

其中一個結構體包含了另外一個結構體,下面我們來看下這兩個結構體的佈局格式(在64位系統中)。

(二)Class的記憶體佈局

Class結構中只有兩個屬性,一個是字串,另外一個是uint,對於後者而言在64位系統中就是uint64,則它的結構包括了24個位元組:

cl := new(Class) fmt.Println(unsafe.Sizeof(*cl)) fmt.Println(unsafe.Alignof(*cl)) // 輸出為: // 24 // 8 // 在記憶體中的結構應該如下: |0 - 7|8 - 15|16 - 23| |0 - 7|:CName的指標 |8 - 15|:CName的長度 |16 - 23|:Index

(三)User的記憶體佈局

User結構比較複雜,對於引用的Class而言,這就是一個指標而已,指標就是8位元組,那麼它的整體結構應該是佔用了32個位元組(特別關注uint8,該值只佔用一個位元組),結構如下:

u := new(User) fmt.Println(unsafe.Sizeof(*u)) fmt.Println(unsafe.Alignof(*u)) // 輸出結果為: // 32 // 8 |0 - 7|8 - 15|16|17|18 - 23|24 - 31| |0 - 7|:Name的指標 |8 - 15|:Name的長度 |16|:Age |17|:Sex |18 - 23|:什麼都沒有,浪費了 |24 - 31|:class,即指標

(四)測試程式碼

我們寫了非常少的一段程式碼,來測試下這兩個物件的記憶體佈局: ``` / Copyright (C) THL A29 Limited, a Tencent company. All rights reserved. SPDX-License-Identifier: Apache-2.0 / package main

import ( "fmt" "math/rand" "os" "os/signal" "strconv" )

var user *User

func main() { idx := rand.Intn(10) user = &User{ Name: "zhangsan", Age: 18, Sex: 1, class: &Class{ CName: "class-" + strconv.Itoa(idx), Index: uint(idx), }, } fmt.Println(user) c := make(chan os.Signal) signal.Notify(c, os.Interrupt, os.Kill) s := <-c fmt.Println("receive signal -> ", s) }

type User struct { Name string Age uint8 Sex uint8 class *Class }

type Class struct { CName string Index uint } ```

其中的字串,我特意使用了兩個不同的方式,一個是獨立的字串,或者說字串常量:"zhangsan",另外一個是字串的拼接。

這兩者是有區別的,對於常量字串而言,通常會在編譯期間將其放在程式段中,而拼接字串是在執行期生成的,那麼我們是可以看到它是明確在堆上生成的。

圖片

解析

(一)執行起來看記憶體

首先把程式跑起來,其PID為14173,我們如何檢視記憶體分配呢?最簡單的方式就是直接通過/proc/14173/maps來檢視: [[email protected] /home/leon]# cat /proc/14173/maps 00400000-00482000 r-xp 00000000 fd:01 672257 /root/chainmaker/my-mem/my-mem 00482000-00510000 r--p 00082000 fd:01 672257 /root/chainmaker/my-mem/my-mem 00510000-0052a000 rw-p 00110000 fd:01 672257 /root/chainmaker/my-mem/my-mem 0052a000-0055e000 rw-p 00000000 00:00 0 c000000000-c000400000 rw-p 00000000 00:00 0 c000400000-c004000000 ---p 00000000 00:00 0 7fc393f86000-7fc3962f7000 rw-p 00000000 00:00 0 7fc3962f7000-7fc3a6477000 ---p 00000000 00:00 0 7fc3a6477000-7fc3a6478000 rw-p 00000000 00:00 0 7fc3a6478000-7fc3b8327000 ---p 00000000 00:00 0 7fc3b8327000-7fc3b8328000 rw-p 00000000 00:00 0 7fc3b8328000-7fc3ba6fd000 ---p 00000000 00:00 0 7fc3ba6fd000-7fc3ba6fe000 rw-p 00000000 00:00 0 7fc3ba6fe000-7fc3bab77000 ---p 00000000 00:00 0 7fc3bab77000-7fc3bab78000 rw-p 00000000 00:00 0 7fc3bab78000-7fc3babf7000 ---p 00000000 00:00 0 7fc3babf7000-7fc3bac57000 rw-p 00000000 00:00 0 7fff7aad6000-7fff7aaf7000 rw-p 00000000 00:00 0 [stack] 7fff7ab9a000-7fff7ab9d000 r--p 00000000 00:00 0 [vvar] 7fff7ab9d000-7fff7ab9e000 r-xp 00000000 00:00 0 [vdso] ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]

上面的資訊中可以看出一部分端倪來,對於前三行,理論上應該是程式段,中間的一些資訊是堆的資料,最後應該是系統呼叫。

(二)把記憶體中的資料dump下來

可以使用gdb命令,直接將記憶體中的資料dump為二進位制檔案,為此編寫了一個指令碼,可以直接執行:

```

!/bin/bash

注意過濾條件,也可以新增其他過濾條件

# 為了全部處理下來,可以將第一行修改為:cat /proc/$1/maps \ grep rw-p /proc/$1/maps \ | sed -n 's/^([0-9a-f])-([0-9a-f]) .*$/\1 \2/p' \ | while read start stop; do \ gdb --batch --pid $1 -ex \ "dump memory $1-$start-$stop.dump 0x$start 0x$stop"; \ done ```

它的入參是程序ID,呼叫後,會將當時記憶體中的資訊dump成檔案:

[[email protected] /home/leon]# ./dump-all-mem.sh 14173 runtime.futex () at /usr/local/go/src/runtime/sys_linux_amd64.s:553 553 MOVL AX, ret+40(FP) ...... warning: File "/usr/local/go/src/runtime/runtime-gdb.py" auto-loading has been declined by your `auto-load [Inferior 1 (process 14173) detached] [[email protected] /home/leon]# ll -h -rw-r--r-- 1 root root 532480 Aug 1 10:44 14173-00400000-00482000.dump -rw-r--r-- 1 root root 581632 Aug 1 10:44 14173-00482000-00510000.dump -rw-r--r-- 1 root root 106496 Aug 1 10:44 14173-00510000-0052a000.dump -rw-r--r-- 1 root root 212992 Aug 1 10:44 14173-0052a000-0055e000.dump -rw-r--r-- 1 root root 37163008 Aug 1 10:44 14173-7fc393f86000-7fc3962f7000.dump -rw-r--r-- 1 root root 270008320 Aug 1 10:44 14173-7fc3962f7000-7fc3a6477000.dump -rw-r--r-- 1 root root 4096 Aug 1 10:44 14173-7fc3a6477000-7fc3a6478000.dump -rw-r--r-- 1 root root 300609536 Aug 1 10:44 14173-7fc3a6478000-7fc3b8327000.dump -rw-r--r-- 1 root root 4096 Aug 1 10:44 14173-7fc3b8327000-7fc3b8328000.dump -rw-r--r-- 1 root root 37572608 Aug 1 10:44 14173-7fc3b8328000-7fc3ba6fd000.dump -rw-r--r-- 1 root root 4096 Aug 1 10:44 14173-7fc3ba6fd000-7fc3ba6fe000.dump -rw-r--r-- 1 root root 4689920 Aug 1 10:44 14173-7fc3ba6fe000-7fc3bab77000.dump -rw-r--r-- 1 root root 4096 Aug 1 10:44 14173-7fc3bab77000-7fc3bab78000.dump -rw-r--r-- 1 root root 520192 Aug 1 10:44 14173-7fc3bab78000-7fc3babf7000.dump -rw-r--r-- 1 root root 393216 Aug 1 10:44 14173-7fc3babf7000-7fc3bac57000.dump -rw-r--r-- 1 root root 135168 Aug 1 10:44 14173-7fff7aad6000-7fff7aaf7000.dump -rw-r--r-- 1 root root 4096 Aug 1 10:44 14173-7fff7ab9d000-7fff7ab9e000.dump -rw-r--r-- 1 root root 4194304 Aug 1 10:44 14173-c000000000-c000400000.dump -rw-r--r-- 1 root root 62914560 Aug 1 10:44 14173-c000400000-c004000000.dump

其中以程序ID開頭的dump檔案就是我們dump下來的記憶體資料。

(三)從dump檔案中查詢class-

我們定義了一個特別的字串用於過濾,那就是class-,因為後面的值是隨機的,我們不清楚是什麼,但通過這個字串足夠。

此時使用strings命令來查詢,該命令會將能轉為字串的轉為字串檢視,我們的目的是找到在具體哪個檔案中:

[[email protected] /home/leon]# strings 14173-c000000000-c000400000.dump | grep class- class-1

最終我們找到了這個dump檔案:14173-c000000000-c000400000.dump

(四)仔細看看這個dump檔案

下面我們仔細看看這個dump檔案裡面是什麼,因為其中的內容是二進位制,所以我們採用十六進位制的方式來檢視,使用的命令是hexdump,下面是內容:

``` [[email protected] /home/leon]# hexdump -c 14173-c000000000-c000400000.dump

解釋下下面的內容,否則可能無法理解,以第一行為例

每一行分為兩部分,前面第一段表示的是偏移量,後面是內容,內容是十六個位元組,詳細說明如下:

0000000:偏移量,是基於初始記憶體地址的,此處是c000000000,這個非常重要,是我們找地址的本質所在,真實的地址是將這個偏移量與初始地址相加,例如00000f0對應的地址為:c0000000f0

剩下的就是十六個位元組的內容,對於裡面的顯示需要進行一些說明:

\0:表示0

312:這種以3個數字描述的是其實是8進位制,其中最高位佔2bit,剩下兩個各佔3bit,312=11001010(二進位制)=0xca(十六進位制)

2:這個數字2不是數字2,如果是2會以002的方式描述,這個數字2是一個ascii碼,它實際代表的值是:50(十進位制)或0x32(十六進位制)

\a:還有類似的,如\t \b等和上面的2是一樣的,都是ascii碼

另外需要說明的是,如果該行出現了*,表示不是一段連續的記憶體地址,類似於分割符

0000000 027 221 I \0 \0 \0 \0 \0 003 \0 \0 \0 \0 \0 \0 \0 0000010 001 216 U \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 0000020 032 221 I \0 \0 \0 \0 \0 003 \0 \0 \0 \0 \0 \0 \0 0000030 \0 216 U \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 0000040 035 221 I \0 \0 \0 \0 \0 003 \0 \0 \0 \0 \0 \0 \0 0000050 002 216 U \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 0000060 312 221 I \0 \0 \0 \0 \0 004 \0 \0 \0 \0 \0 \0 \0 0000070 003 216 U \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 0000080 322 221 I \0 \0 \0 \0 \0 004 \0 \0 \0 \0 \0 \0 \0 0000090 004 216 U \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 00000a0 326 221 I \0 \0 \0 \0 \0 004 \0 \0 \0 \0 \0 \0 \0 00000b0 005 216 U \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 00000c0 002 222 I \0 \0 \0 \0 \0 004 \0 \0 \0 \0 \0 \0 \0 00000d0 006 216 U \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 00000e0 & 221 I \0 \0 \0 \0 \0 003 \0 \0 \0 \0 \0 \0 \0 00000f0 \a 216 U \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 0000100 235 233 I \0 \0 \0 \0 \0 \t \0 \0 \0 \0 \0 \0 \0 0000110 \t 216 U \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 0000120 330 224 I \0 \0 \0 \0 \0 006 \0 \0 \0 \0 \0 \0 \0 0000130 \n 216 U \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 0000140 336 224 I \0 \0 \0 \0 \0 006 \0 \0 \0 \0 \0 \0 \0 0000150 \v 216 U \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 0000160 2 222 I \0 \0 \0 \0 \0 004 \0 \0 \0 \0 \0 \0 \0 0000170 \f 216 U \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 0000180 = 223 I \0 \0 \0 \0 \0 005 \0 \0 \0 \0 \0 \0 \0 0000190 016 216 U \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 00001a0 B 223 I \0 \0 \0 \0 \0 005 \0 \0 \0 \0 \0 \0 \0 00001b0 017 216 U \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 00001c0 G 223 I \0 \0 \0 \0 \0 005 \0 \0 \0 \0 \0 \0 \0 00001d0 \r 216 U \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 00001e0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 * // 將上面和下面的記憶體分割開了 0002000 \0 @ \0 \0 300 \0 \0 \0 \0 300 \0 \0 300 \0 \0 \0 0002010 240 C \0 \0 300 \0 \0 \0 240 C \0 \0 300 \0 \0 \0 ```

我們的目的是找到class-,所以我們只需要看它前後的一部分內容即可:

[[email protected] /home/leon]# hexdump -c 14173-c000000000-c000400000.dump | grep -A 3 -B 3 "c l" 00ab300 i 254 321 332 6 Y 315 246 \0 \0 \0 \0 \0 \0 \0 \0 00ab310 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 * 00ae010 c l a s s - 1 \0 & { \0 \0 \0 \0 \0 \0 00ae020 & { z h a n g s a n 1 8 1 00ae030 022 001 \0 \0 004 002 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 00ae040 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0

可以看出,class-1這個字串所在的地址是:00ae010+c000000000=c0000ae010,根據理論,下面就是要找到哪引用了這個地址。

(五)找到引用地址的位置

在找到引用地址c0000ae010之前,我們首先需要做一個計算,因為這是一個十六進位制,我們知道實際上通過hexdump看的時候裡面大部分都是八進位制,計算很簡單,將地址以兩兩進行分開即可:c0 00 0a e0 10,經過計算可以得出如下結果:

300 0 12 340 20,我們需要將該結果返回來,容易進行查詢,那麼需要查詢的結果可能是:20 340 012 \0 300。(實際上不是)。為什麼不是?需要做一個說明:

標準的ascii碼錶示的是從0~127,這個值如果以八進位制表示的話就是從:0~177;對於這個範圍的值都會以ascii碼的形式展示,對於12而言,其ascii碼的顯示是換行符,也就是\n;對於20而言,它的ascii碼顯示是:資料鏈路轉義,這個沒有可顯示的字元對應,通常會使用原始的020來描述。

那麼我們要查詢的結果應該就是:020 340 \n \0 300

通過我們的搜尋還真的找到了該引用:

```

為了查詢方便,首先我把所有的dump檔案全部轉為hex輸出到了指定的檔案(字尾為.hex的),列表:

[[email protected] /home/leon]# ll -rw-r--r-- 1 root root 532480 Aug 1 10:44 14173-00400000-00482000.dump -rw-r--r-- 1 root root 2379546 Aug 1 10:58 14173-00400000-00482000.dump.hex -rw-r--r-- 1 root root 581632 Aug 1 10:44 14173-00482000-00510000.dump -rw-r--r-- 1 root root 2595452 Aug 1 10:58 14173-00482000-00510000.dump.hex -rw-r--r-- 1 root root 106496 Aug 1 10:44 14173-00510000-0052a000.dump -rw-r--r-- 1 root root 452260 Aug 1 10:59 14173-00510000-0052a000.dump.hex -rw-r--r-- 1 root root 212992 Aug 1 10:44 14173-0052a000-0055e000.dump -rw-r--r-- 1 root root 42992 Aug 1 10:59 14173-0052a000-0055e000.dump.hex -rw-r--r-- 1 root root 37163008 Aug 1 10:44 14173-7fc393f86000-7fc3962f7000.dump -rw-r--r-- 1 root root 34282 Aug 1 10:59 14173-7fc393f86000-7fc3962f7000.dump.hex -rw-r--r-- 1 root root 270008320 Aug 1 10:44 14173-7fc3962f7000-7fc3a6477000.dump -rw-r--r-- 1 root root 83 Aug 1 10:59 14173-7fc3962f7000-7fc3a6477000.dump.hex -rw-r--r-- 1 root root 4096 Aug 1 10:44 14173-7fc3a6477000-7fc3a6478000.dump -rw-r--r-- 1 root root 154 Aug 1 11:00 14173-7fc3a6477000-7fc3a6478000.dump.hex -rw-r--r-- 1 root root 300609536 Aug 1 10:44 14173-7fc3a6478000-7fc3b8327000.dump -rw-r--r-- 1 root root 83 Aug 1 11:00 14173-7fc3a6478000-7fc3b8327000.dump.hex -rw-r--r-- 1 root root 4096 Aug 1 10:44 14173-7fc3b8327000-7fc3b8328000.dump -rw-r--r-- 1 root root 154 Aug 1 11:00 14173-7fc3b8327000-7fc3b8328000.dump.hex -rw-r--r-- 1 root root 37572608 Aug 1 10:44 14173-7fc3b8328000-7fc3ba6fd000.dump -rw-r--r-- 1 root root 82 Aug 1 11:01 14173-7fc3b8328000-7fc3ba6fd000.dump.hex -rw-r--r-- 1 root root 4096 Aug 1 10:44 14173-7fc3ba6fd000-7fc3ba6fe000.dump -rw-r--r-- 1 root root 154 Aug 1 11:01 14173-7fc3ba6fd000-7fc3ba6fe000.dump.hex -rw-r--r-- 1 root root 4689920 Aug 1 10:44 14173-7fc3ba6fe000-7fc3bab77000.dump -rw-r--r-- 1 root root 82 Aug 1 11:01 14173-7fc3ba6fe000-7fc3bab77000.dump.hex -rw-r--r-- 1 root root 4096 Aug 1 10:44 14173-7fc3bab77000-7fc3bab78000.dump -rw-r--r-- 1 root root 228 Aug 1 11:02 14173-7fc3bab77000-7fc3bab78000.dump.hex -rw-r--r-- 1 root root 520192 Aug 1 10:44 14173-7fc3bab78000-7fc3babf7000.dump -rw-r--r-- 1 root root 82 Aug 1 11:02 14173-7fc3bab78000-7fc3babf7000.dump.hex -rw-r--r-- 1 root root 393216 Aug 1 10:44 14173-7fc3babf7000-7fc3bac57000.dump -rw-r--r-- 1 root root 37582 Aug 1 11:03 14173-7fc3babf7000-7fc3bac57000.dump.hex -rw-r--r-- 1 root root 135168 Aug 1 10:44 14173-7fff7aad6000-7fff7aaf7000.dump -rw-r--r-- 1 root root 24072 Aug 1 11:04 14173-7fff7aad6000-7fff7aaf7000.dump.hex -rw-r--r-- 1 root root 4096 Aug 1 10:44 14173-7fff7ab9d000-7fff7ab9e000.dump -rw-r--r-- 1 root root 17510 Aug 1 11:02 14173-7fff7ab9d000-7fff7ab9e000.dump.hex -rw-r--r-- 1 root root 4194304 Aug 1 10:44 14173-c000000000-c000400000.dump -rw-r--r-- 1 root root 195662 Aug 1 11:02 14173-c000000000-c000400000.dump.hex -rw-r--r-- 1 root root 62914560 Aug 1 10:44 14173-c000400000-c004000000.dump -rw-r--r-- 1 root root 82 Aug 1 11:03 14173-c000400000-c004000000.dump.hex -rw-r--r-- 1 root root 4096 Aug 1 10:44 14173-ffffffffff600000-ffffffffff601000.dump -rw-r--r-- 1 root root 446 Aug 1 11:03 14173-ffffffffff600000-ffffffffff601000.dump.hex

然後通過grep命令可以找到對應行:

[[email protected] /home/leon]# grep "020 340 \\n \\0 300" 14173-*.hex 14173-c000000000-c000400000.dump.hex:009c010 p 374 \t \0 300 \0 \0 \0 020 340 \n \0 300 \0 \0 \0 ```

(六)Class物件分析

我們將查詢範圍稍微擴大幾行(上下均擴大了幾行):

```

grep命令中-A表示向後(after),-B表示向前(before)

[[email protected] /home/leon]# grep "020 340 \\n \\0 300" -A 3 -B 3 14173-.hex 14173-c000000000-c000400000.dump.hex-009af30 300 @ R \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 14173-c000000000-c000400000.dump.hex-009af40 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 14173-c000000000-c000400000.dump.hex- 14173-c000000000-c000400000.dump.hex:009c010 p 374 \t \0 300 \0 \0 \0 020 340 \n \0 300 \0 \0 \0 14173-c000000000-c000400000.dump.hex-009c020 \a \0 \0 \0 \0 \0 \0 \0 001 \0 \0 \0 \0 \0 \0 \0 14173-c000000000-c000400000.dump.hex-009c030 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 14173-c000000000-c000400000.dump.hex-* ```

我們根據程式碼來分析一下這段記憶體資訊,首先將之前class的記憶體佈局拿過來:

// 在記憶體中的結構應該如下: |0 - 7|8 - 15|16 - 23| |0 - 7|:CName的指標 |8 - 15|:CName的長度 |16 - 23|:Index

下面是根據記憶體佈局的分析結果:

```

# |-- 此處為其他內容,不用關注 ------|----------CName指標-----------|

009c010 p 374 \t \0 300 \0 \0 \0 020 340 \n \0 300 \0 \0 \0

# |CName長度為\a,一個轉義符,表示7, |---Index的值為001,也就是1,符合|

009c020 \a \0 \0 \0 \0 \0 \0 \0 001 \0 \0 \0 \0 \0 \0 \0 ```

其中的009c018即為該地址的偏移地址(注意最後一位是8,因為沒有正好在009c010開頭),下面我們要找的就是User這個物件。

(七)User物件查詢

還是按照相同的方式將地址c00009c018(009c018 + c000000000)找出來。

先換算,過程不再寫,換算後的結果為:030 300 \t \0 300

然後進行查詢,發現了4個:

```

注意grep查詢\的時候需要\\來轉義

[[email protected] /home/leon]# grep "030 300 \\t \\0 300" 14173-*.dump.hex 14173-c000000000-c000400000.dump.hex:0088f00 030 300 \t \0 300 \0 \0 \0 \0 \0 \v \0 300 \0 \0 \0 14173-c000000000-c000400000.dump.hex:00b0010 022 001 \0 \0 \0 \0 \0 \0 030 300 \t \0 300 \0 \0 \0 14173-c000000000-c000400000.dump.hex:00b0030 022 001 \0 \0 \0 \0 \0 \0 030 300 \t \0 300 \0 \0 \0 14173-c000000000-c000400000.dump.hex:00bbf00 030 300 \t \0 300 \0 \0 \0 \0 \0 \v \0 300 \0 \0 \0 ```

我們對這四個挨著進行分析,他們都在檔案14173-c000000000-c000400000.dump.hex中。

[[email protected] /home/leon]# grep "030 300 \\\\t \\\\0 300" -A 3 -B 3 14173-c000000000-c000400000.dump.hex 0088ed0 001 \0 \0 \0 \0 \0 \0 \0 001 \0 \0 \0 \0 \0 \0 \0 * 0088ef0 v 356 I \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 0088f00 030 300 \t \0 300 \0 \0 \0 \0 \0 \v \0 300 \0 \0 \0 0088f10 300 h H \0 \0 \0 \0 \0 \0 \0 \v \0 300 \0 \0 \0 0088f20 \0 \0 \0 \0 \0 \0 \0 \0 X P @ \0 \0 \0 \0 \0 0088f30 p 217 \b \0 300 \0 \0 \0 231 O @ \0 \0 \0 \0 \0 -- 00ae040 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 * 00b0000 ^ 231 I \0 \0 \0 \0 \0 \b \0 \0 \0 \0 \0 \0 \0 00b0010 022 001 \0 \0 \0 \0 \0 \0 030 300 \t \0 300 \0 \0 \0 00b0020 ^ 231 I \0 \0 \0 \0 \0 \b \0 \0 \0 \0 \0 \0 \0 00b0030 022 001 \0 \0 \0 \0 \0 \0 030 300 \t \0 300 \0 \0 \0 00b0040 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 * 00b2080 340 _ I \0 \0 \0 \0 \0 0 \f \t \0 300 \0 \0 \0 -- 00bbed0 001 \0 \0 \0 \0 \0 \0 \0 001 \0 \0 \0 \0 \0 \0 \0 * 00bbef0 v 356 I \0 \0 \0 \0 \0 300 \b \0 300 \0 \0 \0 00bbf00 030 300 \t \0 300 \0 \0 \0 \0 \0 \v \0 300 \0 \0 \0 00bbf10 300 h H \0 \0 \0 \0 \0 \0 \0 \v \0 300 \0 \0 \0 00bbf20 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 00bbf30 p 217 \b \0 300 \0 \0 \0 231 O @ \0 \0 \0 \0 \0

結合User的記憶體佈局(如下),在class前面應該有24個位元組,表示的是開頭:

```

|0 - 7|8 - 15|16|17|18 - 23|24 - 31|

|0 - 7|:Name的指標

|8 - 15|:Name的長度

|16|:Age

|17|:Sex

|18 - 23|:什麼都沒有,浪費了

|24 - 31|:class,即指標

```

首先分析第一個:

`` 0056010 + \0 \0 300 \0 \0 \0 \0 - \0 \0 300 \0 \0 \0

|----- | 此處應該是Age+Sex,為022 001,不合法|

0088ef0 v 356 I \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0

|----- class指標 -----|

0088f00 030 300 \t \0 300 \0 \0 \0 \0 \0 \v \0 300 \0 \0 \0 ```

可以看出來,該地址並不是User的地址資訊,因為對映Age和Sex的位置不合法。

通過分析我們知道了,Age和Sex的位置應該是022和001,那麼可以直接進行過濾,就剩下了兩個地址:

```

#| ----- "zhangsan"所在地址 ----- |-------- 字串長度為:8 -------|

00b0000 ^ 231 I \0 \0 \0 \0 \0 \b \0 \0 \0 \0 \0 \0 \0

#|Age|Sex| |------- class ----------|

00b0010 022 001 \0 \0 \0 \0 \0 \0 030 300 \t \0 300 \0 \0 \0

#| ----- "zhangsan"所在地址 ----- |-------- 字串長度為:8 -------|

00b0020 ^ 231 I \0 \0 \0 \0 \0 \b \0 \0 \0 \0 \0 \0 \0

#|Age|Sex| |------- class ----------|

00b0030 022 001 \0 \0 \0 \0 \0 \0 030 300 \t \0 300 \0 \0 \0 00b0040 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 ```

如上面標註的一樣,這兩個地址都是合法的。簡單說明一下:

  • 在程式中,我們設定了Age=18,此處的022(八進位制)=2 * 8 + 2 = 18,是符合預期的;

  • 在程式中設定的Sex=1與記憶體中的001也是對應成功的;

  • 在程式中,我們設定的Name為"zhangsan",也就是8個位元組,\b在ascii碼中表示退格鍵,正好是十進位制的8,八進位制的10,也是符合預期的。

最後看一下Name指標,這是一個比較特殊的部分,它的地址內容是:^ 231 I,換算成十六進位制為5e 99 49,對應的絕對地址是:49995e。

(八)查詢User中的zhangsan

可以看到,絕對地址為49995e的記憶體在:14173-00482000-00510000.dump中,進行偏移量的計算:

偏移量=49995e-482000=1795e,但需要考慮的是,該偏移量並不是一個可以被十六進位制整除的值,也就是它不會出現在檔案的最開始一列,它對應的開頭的地址應該是17950。

下面我們可以所有17950,可以看到如下的資訊:

[[email protected] /home/leon]# grep "17950" -A 1 14173-00482000-00510000.dump.hex 0017950 a c e B u f u n k n o w n ( z h 0017960 a n g s a n ( f o r c e d

可以很明確的看到,zhangsan的地址確實是1795e地址。這個字串具體在哪呢?

(九)zhangsan在哪

我們重新迴歸到目錄:/proc/14173/maps,它的前三行:

[[email protected] /home/leon]# cat /proc/14173/maps 00400000-00482000 r-xp 00000000 fd:01 672257 /root/chainmaker/my-mem/my-mem 00482000-00510000 r--p 00082000 fd:01 672257 /root/chainmaker/my-mem/my-mem 00510000-0052a000 rw-p 00110000 fd:01 672257 /root/chainmaker/my-mem/my-mem

zhangsan的內容恰恰位於第2行,注意第二行的許可權標識:r--p,該許可權標識它是一個只讀的,不可以執行,什麼資料是隻讀的,不可執行的,一般來講就是放入的常量池。另外,需要看到的是最開始的三行都是描述的當前程序的資訊。簡單的說明如下:

``` [[email protected] /home/leon]# cat /proc/14173/maps

可讀可執行,但不可寫,通常是程式碼段的位置

00400000-00482000 r-xp 00000000 fd:01 672257 /root/chainmaker/my-mem/my-mem

只讀,不可執行不可寫,一般放的是常量池,就是那些不會修改的常量,在go語言中常見的一般是字串

00482000-00510000 r--p 00082000 fd:01 672257 /root/chainmaker/my-mem/my-mem

可讀,可寫,但不可執行,一般會放全域性變數,該類值是可以被修改的

00510000-0052a000 rw-p 00110000 fd:01 672257 /root/chainmaker/my-mem/my-mem ```

參考資料:

1.程序記憶體sysfs解讀:

https://www.cnblogs.com/arnoldlu/p/8568330.html

2./proc//maps簡要分析:

https://www.cnblogs.com/arnoldlu/p/10272466.html

如果你是騰訊技術內容創作者,騰訊雲開發者社群誠邀您加入【騰訊雲原創分享計劃】,領取禮品,助力職級晉升。

閱讀原文