乐趣区

关于go:带你可视化理解go内存

导语 | 本文推选自腾讯云开发者社区 -【技思广益 · 腾讯技术人原创集】专栏。该专栏是腾讯云开发者社区为腾讯技术人与宽泛开发者打造的分享交换窗口。栏目邀约腾讯技术人分享原创的技术积淀,与宽泛开发者互启迪共成长。本文作者是腾讯后盾开发工程师邵珠光。

在解决内存泄露的时候,想到了一种从内存中查看哪些对象的问题,于是就对理论跑着的程序内存进行了解析,通过可视化的形式有助于了解 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/arnol…

2./proc/<pid>/maps 简要剖析:

https://www.cnblogs.com/arnol…

如果你是腾讯技术内容创作者,腾讯云开发者社区诚邀您退出【腾讯云原创分享打算】,支付礼品,助力职级降职。

退出移动版