使用lldb看看Rust的HashMap

发布于:2025-05-13 ⋅ 阅读:(16) ⋅ 点赞:(0)

目录

前言

正文

读取桶的状态

获取键值对

键值对的指针地址

此时,读取数据

读取索引4的键值对

多添加几个键值对

 使用i32作为键,&str作为值

使用i32作为键,String作为值


前言

前面使用ldb看了看不同的类型,这篇再使用lldb就来看看HashMap

使用lldb查看Rust不同类型的结构-CSDN博客https://blog.csdn.net/qq_63401240/article/details/147839957?spm=1001.2014.3001.5501

正文

use std::collections::HashMap;

fn main() {
    let mut map = HashMap::new();
    // 插入键值对
    map.insert("go", "tt");
    map.insert("66", "123");
    map.insert("ok", "no");


    println!("最终的 HashMap: {:?}", map);
}

这段简单地的代码,笔者在println哪里打个断点,然后用lldb查看

首先

p map

效果和expr 的等效的  

(lldb) p map
(std::collections::hash::map::HashMap<ref$<str$>,ref$<str$>,std::hash::random::RandomState>) map = {
  base = {
    hash_builder = (k0 = 4722932796647620636, k1 = 13347288390664849048)
    table = {
      table = {
        bucket_mask = 3
        ctrl = {
          pointer = 0x000001bc42010e00
        }
        growth_left = 0
        items = 3
      }
      alloc = {}
      marker = {}
    }
  }
}

 这个就看出HashMap在Rust的结构吧,笔者也不知道用什么词表示,就用结构这个词表示。

这里最关键的东西,显然易见是pointer,里面是一个地址。

结合HashMap有关的知识
【数据结构】哈希表-CSDN博客https://blog.csdn.net/jmlangel_/article/details/147423680

 7. HashMap的底层数据结构 - 知乎https://zhuanlan.zhihu.com/p/636054806

关注

bucket_mask = 3
ctrl = {
          pointer = 0x000001bc42010e00
    }
        growth_left = 0
        items = 3

这几个的意思

bucket_mask = 3:哈希表有 3+1 个桶。
ctrl.pointer = 0x000001bc42010e00:控制字节数组的起始地址。
growth_left = 0:哈希表已经满了。
items = 3:哈希表中有 3 个键值对 

这里面只有一个地址,就从这里开始

读取桶的状态

memory read -c 4 0x000001bc42010e00

memory read 是lldb命令,-c 4 表示读取4个字节的内容

为什么是4 ,因为有4个桶。

关于桶的状态如下

状态 描述 控制字节值 备注
占用 (Occupied) 桶中存储了有效的键值对 < 0x80 (高位为 0) 通常为哈希值的高 7 位 (hash >> 57),用于快速比较哈希
空 (Empty) 桶未被使用,当前没有键值对 0x80 (二进制 10000000) 表示桶从未存储数据
已删除 (Tombstone) 桶曾存储键值对,但数据已被删除 0xff (二进制 11111111) 用于标记移除的条目,避免破坏哈希表的探查链 (probe chain)

运行结果如下

(lldb) memory read -c 4 0x000001bc42010e00
0x1bc42010e00: 70 ff 06 7f   

70 是小于80,因此是占有,索引为1 。

ff  表示曾经占有 ,

06 是小于80,因此是占有 ,索引为3。

7f 是小于80,因此是占有,索引为4 。

获取键值对

怎么获取键值对,这确实是一个的问题?

笔者添加一个循环,打上断点

    for (k,v) in &map{
        println!("{:?},{:?}",k,v);
    }

看看k,v

此时,地址发生变换,重新执行上面的命令

(lldb) memory read -c 4 0x000002a0336c0e00
0x2a0336c0e00: ff 7a 75 4c

发现了变换,还是三个,但是索引是2,3,4了 

ctrl指针的地址是0x2a0336c0e00,笔者复制出来,没有前面的0,不影响

k的地址是          0x2a0336c0dc0

二者之差,等于0x40,变成十进制就是64

有这样一个公式

桶的起始地址 = 桶数组的起始地址 + (桶索引 × 桶的大小)

现在有两个地址,有索引2,笔者的电脑是64位的

&str有两个部分组成,一个指向数据的地址,占8个字节,还有一个是长度u64,也是8个字节,因此,一个&str,占16个字节

同时考虑到键值对都是&str,因此桶的大小为32字节

实际上可以使用std::mem

use std::mem;
println!("(&str, &str) {}", mem::size_of::<(&str, &str)>());

结果如下

(&str, &str) 32

没问题 

同时考虑到索引为2 ,因此2乘以32等于64,两个地址之差正是64

没问题。

经过多次尝试。可以发现

通过ctrl的pointer的地址,减去 (桶索引 × 桶的大小),就可以算出一个地址。这个地址就是键值对的指针地址

键值对的指针地址

再次运行命令,获取索引

现在ctrl pointer的地址变成了0x00000277412c0e00

(lldb) memory read -c 4 0x00000277412c0e00
0x277412c0e00: 17 ff 5d 04

索引为1,3,4

因此0x00000277412c0e00-32=0x00000277412c0de0

同时考虑到笔者是64位的电脑,读取一个指针需要8个字节

因此

(lldb) memory read -c 8 0x00000277412c0de0
0x277412c0de0: 9c 1b e2 61 f6 7f 00 00

这就是指针的地址

笔者的操作系统储存数据的方式是小端序,因此

指针地址为

0x00007ff661e21b9c

此时,读取数据

(lldb) memory read -c 5 0x00007ff661e21b9c
0x7ff661e21b9c: 36 36 31 32 33                                   66123 

为什么读取5个,这是笔者主动选择的,

看到66123,

结合代码,66是键,123是值,读取到键值对

读取索引4的键值对

0x00000277412c0e00 -4*32=0x00000277412c0d80

指针为

(lldb) memory read -c 8 0x00000277412c0d80
0x277412c0d80: a1 1b e2 61 f6 7f 00 00   

地址为0x00007ff661e21ba1

键值对

(lldb) memory read -c 4 0x00007ff661e21ba1
0x7ff661e21ba1: 6f 6b 6e 6f                                      okno

 没问题

看看是否可以修改

(lldb) memory region 0x00007ff661e21ba1
[0x00007ff661e00000-0x00007ff661e2c000) r--

发现不可以修改。没有写的权限

多添加几个键值对

    map.insert("go", "tt");
    map.insert("66", "123");
    map.insert("ok", "no");
    map.insert("hello1","world1");
    map.insert("hello2","world2");
    map.insert("hello3","world3");
    map.insert("hello4","world4");
    map.insert("hello5","world5");

p map的结果如下

(lldb) p map
(std::collections::hash::map::HashMap<ref$<str$>,ref$<str$>,std::hash::random::RandomState>) map = {
  base = {
    hash_builder = (k0 = 12591853735628107626, k1 = 11397258469864990867)
    table = {
      table = {
        bucket_mask = 15
        ctrl = {
          pointer = 0x000002ea70e2c090
        }
        growth_left = 6
        items = 8
      }
      alloc = {}
      marker = {}
    }
  }
}

可以发现bucket_mask 变成了15,就有16个桶

因此,第一个内存的读取,桶的状态

(lldb) memory read -c 16 0x000002ea70e2c090
0x2ea70e2c090: 4a ff ff 0a 48 15 ff 60 ff 3c 2a ff ff 4d ff ff

索引分别是1、4、5、6、8、10、11、14

桶的大小依然是32,不妨读取索引8

地址为0x000002ea70e2c090 - 0x100 = 0x000002ea70e2bf90

指针地址为

(lldb) memory read -c 8 0x000002ea70e2bf90
0x2ea70e2bf90: c9 1b 48 e9 f6 7f 00 00 

即 0x00007ff6e9481bc9

(lldb) memory read -c 12 0x00007ff6e9481bc9
0x7ff6e9481bc9: 68 65 6c 6c 6f 34 77 6f 72 6c 64 34              hello4world4

看来是hello4这个键。其他同理

 使用i32作为键,&str作为值

看看桶的大小

println!("(i32,&str) {}", mem::size_of::<(i32, &str)>());

(i32,&str) 24

 i32是4字节,Rust 默认会按 8字节对齐,笔者是64位

因此8+16=24

插入键值对

    map.insert(1, "tt");

就一个

关键输出

 ctrl = {
          pointer = 0x0000013c22361450
        }

获取桶的状态

(lldb) memory read -c 4 0x0000013c22361450
0x13c22361450: ff ff ff 39       

索引为4

因此,key地址为

0x0000013c22361450 - 4*24

0x0000013c22361450- 0x60 = 0x000001d9c0da13f0

因为key是一个i32

此时这个地址——0x000001d9c0da13f0,就是key的地址

(lldb) p *(0x000001d9c0da13f0 as *mut u8)
(u8) * = 1

value的指针地址就是0x000001d9c0da13f8,不必细说

笔者重新运行。结果如下

(lldb) memory read -c 4 0x000001a895741880
0x1a895741880: ff ff ff 74   
(lldb)  p *(0x000001a895741820 as *mut i32)
(i32) * = 1
(lldb) memory read -c 8 0x000001a895741828
0x1a895741828: b8 1b 80 a2 f7 7f 00 00 
(lldb) memory read -c 2 0x00007ff7a2801bb8
0x7ff7a2801bb8: 74 74                                            tt

没问题

使用i32作为键,String作为值

首先,看看桶的大小

    let size= mem::size_of::<(i32, String)>();
    println!("size of (i32, String): {}", size);
size of (i32, String): 32

可以得到是32

 插入数据

    // 插入键值对
    map.insert(1, "tt".to_string());

先打印看看

    for (key, value) in &map {
        println!("key: {}, value: {}", key, value);
    }

结果如下 

key是*mut i32

value 是*mut alloc::string::String

key与value差了0x8

直接开始

(lldb) p map
(std::collections::hash::map::HashMap<i32,alloc::string::String,std::hash::random::RandomState>) map = {
  base = {
    hash_builder = (k0 = 14409132009700107725, k1 = 6608230683712741401)
    table = {
      table = {
        bucket_mask = 3
        ctrl = {
          pointer = 0x000001f8d3781470
        }
        growth_left = 2
        items = 1
      }
      alloc = {}
      marker = {}
    }
  }
}

ctrl的地址0x000001f8d3781470

看看桶的状态

(lldb) memory read -c 4 0x000001f8d3781470

0x1f8d3781470: 5d ff ff ff   

索引为1

因此 key 的地址0x000001f8d3781470-32=0x000001f8d3781450

(lldb) p *(0x000001f8d3781450 as *mut i32)

(i32) * = 1

没问题

key是i32,占8个字节,因此value的地址0x000001f8d3781458

同时考虑到value是一个字符串,在前面输出中是*mut alloc::string::String,

因此,笔者尝试

(lldb) p (0x000001f8d3781458 as *mut alloc::string::String)
(*mut alloc::string::String)  = 0x000001f8d3781458

没想到居然成功了

(lldb) p *(0x000001f8d3781458 as *mut alloc::string::String)
(alloc::string::String) * = {
  vec = {
    buf = {
      inner = {
        ptr = {
          pointer = {
            pointer = 0x000001f8d37861f0
          }
          _marker = {}
        }
        cap = (__0 = 2)
        alloc = {}
      }
      _marker = {}
    }
    len = 2
  }
}

那后面的事情就不必细说了。都有地址了。

(lldb) memory read -c 2 0x000001f8d37861f0
0x1f8d37861f0: 74 74                                            tt 

没问题

笔者感到很奇怪,为什么&str不行

如果改成&str,输出是这样的

但是

(lldb) p *(0x0000015a3ef96190 as *mut &str)

error: could not find type "str"

笔者不能理解。

但是对于i32为键, &str为值,获取到了key的地址

    map.insert(1,"ggggg");

比如

(lldb) p *(0x000001f296071820 as *mut i32)
(i32) * = 1

这个0x000001f296071820 就看key的地址

0x000001f296071820+0x8=0x000001f296071828 

0x000001f296071828 就是value的地址

而value是&str,包括两个部分,一个数据指针,一个是长度

总共是16个字节,因此,如下

(lldb) memory read -c 16 0x000001f296071828
0x1f296071828: b8 1b d2 34 f6 7f 00 00 05 00 00 00 00 00 00 00

前8个字节b8 1b d2 34 f6 7f 00 00 ,数据指针,05 就是长度

数据

(lldb) memory read -c 5 0x00007ff634d21bb8
0x7ff634d21bb8: 67 67 67 67 67                                   ggggg

长度,value的地址+8个字节,即

0x000001f296071828 +0x8=0x000001f296071830

(lldb) memory read -c 1 0x000001f296071830
0x1f296071830: 05                                               
(lldb) p *(0x000001f296071830 as *mut u8)
(u8) * = 5


网站公告

今日签到

点亮在社区的每一天
去签到