带你可视化理解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个表明长度。

带你可视化理解go内存

测验代码

(一)定义两个结构体

首要咱们定义两个结构体:

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”,别的一个是字符串的拼接。

这两者是有区别的,关于常量字符串而言,通常会在编译期间将其放在程序段中,而拼接字符串是在运转期生成的,那么咱们是能够看到它是清晰在堆上生成的。

带你可视化理解go内存

解析

(一)运转起来看内存

首要把程序跑起来,其PID为14173,咱们怎么检查内存分配呢?最简略的方法便是直接通过/proc/14173/maps来检查:

[root@VM-0-16-centos /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成文件:

[root@VM-0-16-centos /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]
[root@VM-0-16-centos /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指令来查找,该指令会将能转为字符串的转为字符串检查,咱们的目的是找到在详细哪个文件中:

[root@VM-0-16-centos /home/leon]# strings 14173-c000000000-c000400000.dump | grep class-
class-1

最终咱们找到了这个dump文件:14173-c000000000-c000400000.dump

(四)细心看看这个dump文件

下面咱们细心看看这个dump文件里边是什么,由于其间的内容是二进制,所以咱们选用十六进制的方法来检查,运用的指令是hexdump,下面是内容:

[root@VM-0-16-centos /home/leon]# hexdump -c 14173-c000000000-c000400000.dump
### 解说下下面的内容,否则或许无法了解,以榜首行为例
### 每一行分为两部分,前面榜首段表明的是偏移量,后边是内容,内容是十六个字节,详细阐明如下:
## 0000000:偏移量,是依据初始内存地址的,此处是c000000000,这个非常重要,是咱们找地址的实质地点,实在的地址是将这个偏移量与初始地址相加,例如00000f0对应的地址为:c0000000f0
## 剩下的便是十六个字节的内容,关于里边的显现需求进行一些阐明:
# \0:表明0
# 312:这种以3个数字描绘的是其实是8进制,其间最高位占2bit,剩下两个各占3bit312=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-,所以咱们只需求看它前后的一部分内容即可:

[root@VM-0-16-centos /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码表明的是从0127,这个值假如以八进制表明的话便是从:0177;关于这个规模的值都会以ascii码的方法展现,关于12而言,其ascii码的显现是换行符,也便是\n;关于20而言,它的ascii码显现是:数据链路转义,这个没有可显现的字符对应,通常会运用原始的020来描绘。

那么咱们要查询的成果应该便是:020 340 \n \0 300

通过咱们的搜索还真的找到了该引证:

### 为了查找便利,首要我把一切的dump文件全部转为hex输出到了指定的文件(后缀为.hex的),列表:
[root@VM-0-16-centos /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指令能够找到对应行:
[root@VM-0-16-centos /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)
[root@VM-0-16-centos /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查找\的时分需求\\\\来转义
[root@VM-0-16-centos /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中。

[root@VM-0-16-centos /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,能够看到如下的信息:

[root@VM-0-16-centos /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,它的前三行:

[root@VM-0-16-centos /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,该权限标识它是一个只读的,不能够履行,什么数据是只读的,不可履行的,一般来讲便是放入的常量池。别的,需求看到的是最开端的三行都是描绘的当前进程的信息。简略的阐明如下:

[root@VM-0-16-centos /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解读:

www.cnblogs.com/arnoldlu/p/…

2./proc//maps扼要剖析:

www.cnblogs.com/arnoldlu/p/…

带你可视化理解go内存

假如你是腾讯技能内容创作者,腾讯云开发者社区诚邀您参加【腾讯云原创共享方案】,领取礼品,助力职级提升。

阅览原文