百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程网 > 正文

Go 是如何确保内存安全的? go操作内存

yuyutoo 2024-12-15 17:41 6 浏览 0 评论

Illustration created for “A Journey With Go”, made from the original Go Gopher, created by Renee French.

??这篇文章基于 Go 1.13 编写。

Go 的一系列内存管理手段(内存分配,垃圾回收,内存访问检查)使许多开发者的开发工作变得很轻松。编译器通过在代码中引入“边界检查” 来确保安全地访问内存。

生成的指令

Go 引入了一些控制点位,来确保我们的程序访问的内存片段安全且有效的。让我们从一个简单的例子开始:

package main

func main() {
    list := []int{1, 2, 3}

    printList(list)
}

func printList(list []int) {
    println(list[2])
    println(list[3])
}

这段代码跑起来之后会 panic:

3
panic: runtime error: index out of range [3] with length 3

Go 通过添加边界检查来防止不正确的内存访问

如果你想知道没有这些检查会怎么样,你可以使用 -gcflags="-B" 的选项,输出如下

3
824633993168

因为这块内存是无效的,它会读取不属于这个 slice 的下一个 bytes。

利用命令 go tool compile -S main.go 来生成对应的汇编[1]代码,就可以看到这些检查点:

0x0021 00033 (main.go:10)  MOVQ   "".list+48(SP), CX
0x0026 00038 (main.go:10)  CMPQ   CX, $2
0x002a 00042 (main.go:10)  JLS    161
[...] here Go prints the third element
0x0057 00087 (main.go:11)  MOVQ   "".list+48(SP), CX
0x005c 00092 (main.go:11)  CMPQ   CX, $3
0x0060 00096 (main.go:11)  JLS    151
[...]
0x0096 00150 (main.go:12)  RET
0x0097 00151 (main.go:11)  MOVL   $3, AX
0x009c 00156 (main.go:11)  CALL   runtime.panicIndex(SB)
0x00a1 00161 (main.go:10)  MOVL   $2, AX
0x00a6 00166 (main.go:10)  CALL   runtime.panicIndex(SB)

Go 先使用 MOVQ 指令将 list 变量的长度放入寄存器 CX

0x0021 00033 (main.go:10)  MOVQ   "".list+48(SP), CX

友情提醒,slice 类型的变量由三部分组成,指向底层数组的指针、长度,容量(capacity)。list 变量在栈中的位置如下图:

通过将栈指针移动 48 个字节就可以访问长度

下一条指令将 slice 的长度与程序即将访问的偏移量进行比较

CMPQ 指令会将两个值相减,并在下一条指令中与 0 进行比较。如果 slice 的长度(寄存器 CX)减去要访问的偏移量(在这个例子当中是 2)小于或等于 0(JLSJump on lower or the same 的缩写),程序就会跳到 161 处继续执行。

两种边界检查使用的都是相同的指令。除了看生成的汇编代码,Go 提供了一个编译期的通行证去打印出边界检查的点,你可以在 buildrun 的时候使用标志 -gcflags="-d=ssa/check_bce/debug=1" 去开启。输出如下:

./main.go:10:14: Found IsInBounds
./main.go:11:14: Found IsInBounds

我们可以看到输出里生成了两个检查点。不过 Go 编译器足够聪明,在不需要的情况下,它不会生成边界检查的指令。

规则

在每次访问内存的时候都生成检查指令是非常低效的,让我们稍微修改一下前面的例子。

package main

func main() {
    list := []int{1, 2, 3}

    printList(list)
}

func printList(list []int) {
    println(list[3])
    println(list[2])
}

两个 println 指令对调了,用 check_bce 标志再去跑一遍程序,这次只有一处边界检查:

./main.go:11:14: Found IsInBounds

程序先检查了偏移量 3 。如果是有效的,那么 2 很明显也是有效的,没必要再去检查了。可以通过命令 GOSSAFUNC=printList Go run main.go 来生成 SSA 代码看编译过程。这张图就是生成的带边界检查的 SSA 代码:

里面的 prove pass 将边界检查标记为移除,这样后面的 pass 将会收集这些 dead code:

用这条命令 GOSSAFUNC=printList Go run -gcflags="-d=ssa/prove/debug=3" main.go 可以把 pass 背后的逻辑打印出来,它也会生成 SSA 文件来帮助你 debug,接下来看命令的输出:

这个 pass 实际上会采取不同的策略,并建立了 fact 表。这些 fact 决定了矛盾点在哪里。在我们这个例子里,我们可以通过 SSA 的 pass 来解读这些规则:

第一个阶段从代表指令 println(list[3]) 的分析块 b1 开始,这个指令有两种可能:

  • 偏移量 [3] 在边界中,跳到第二个指令 b2。在这个例子中,Go 指定 v7 的限制(slice 的长度)是 [4, max(int)]
  • 偏移量 [3 不在边界中, 程序跳转到 b3 指令并 panic。

接下来,Go 开始处理 b2 块(第二个指令)。这里也有两种可能

  • 偏移量 [2] 在边界中,这意味着 slice 的长度 v7v23(偏移量[2]) 要大。在先前的 b1 块中 Go 已经判断了 v7 > 4, 所以这个已经被确认了。
  • 偏移量 [2] 不在边界中,这意味着它比 slice 的长度 v7 更大,但 v7 的限制是 [4, max(int)] ,所以 Go 会将这个分之标记为矛盾,意味着这种情况永远不会发生,这条指令的边界检查可以被移除。

这个 pass 在随着时间不断地改善,现在可以参考更多的 case[2]。消除边界检查可以略微提升 Go 程序的运行速度,但除非你的程序是微妙级敏感的,不然没有必要去优化它。


via: https://medium.com/a-journey-with-go/go-memory-safety-with-bounds-check-1397bef748b5

作者: Vincent Blanchon[3]译者:yxlimo[4]校对:Alex.Jiang[5]本文由GCTT[6] 原创编译,Go 中文网[7] 荣誉推出

参考资料

[1]

汇编: https://golang.org/doc/asm

[2]

更多的 case: https://github.com/golang/go/blob/master/test/prove.go

[3]

Vincent Blanchon: https://medium.com/@blanchon.vincent

[4]

yxlimo: https://github.com/yxlimo

[5]

Alex.Jiang: https://github.com/JYSDeveloper

[6]

GCTT: https://github.com/studygolang/GCTT

[7]

Go 中文网: https://studygolang.com/


相关推荐

当 Linux 根分区 (/) 已满时如何释放空间?

根分区(/)是Linux文件系统的核心,包含操作系统核心文件、配置文件、日志文件、缓存和用户数据等。当根分区满载时,系统可能出现无法写入新文件、应用程序崩溃甚至无法启动的情况。常见原因包括:...

玩转 Linux 之:磁盘分区、挂载知多少?

今天来聊聊linux下磁盘分区、挂载的问题,篇幅所限,不会聊的太底层,纯当科普!!1、Linux分区简介1.1主分区vs扩展分区硬盘分区表中最多能存储四个分区,但我们实际使用时一般只分为两...

Linux 文件搜索神器 find 实战详解,建议收藏

在Linux系统使用中,作为一个管理员,我希望能查找系统中所有的大小超过200M文件,查看近7天系统中哪些文件被修改过,找出所有子目录中的可执行文件,这些任务需求...

Linux 操作系统磁盘操作(linux 磁盘命令)

一、文档介绍本文档描述Linux操作系统下多种场景下的磁盘操作情况。二、名词解释...

Win10新版19603推送:一键清理磁盘空间、首次集成Linux文件管理器

继上周四的Build19592后,微软今晨面向快速通道的Insider会员推送Windows10新预览版,操作系统版本号Build19603。除了一些常规修复,本次更新还带了不少新功能,一起来了...

Android 16允许Linux终端使用手机全部存储空间

IT之家4月20日消息,谷歌Pixel手机正朝着成为强大便携式计算设备的目标迈进。2025年3月的更新中,Linux终端应用的推出为这一转变奠定了重要基础。该应用允许兼容的安卓设备...

Linux 系统管理大容量磁盘(2TB+)操作指南

对于容量超过2TB的磁盘,传统MBR分区表的32位寻址机制存在限制(最大支持2.2TB)。需采用GPT(GUIDPartitionTable)分区方案,其支持64位寻址,理论上限为9.4ZB(9....

Linux 服务器上查看磁盘类型的方法

方法1:使用lsblk命令lsblk输出说明:TYPE列显示设备类型,如disk(物理磁盘)、part(分区)、rom(只读存储)等。...

ESXI7虚机上的Ubuntu Linux 22.04 LVM空间扩容操作记录

本人在实际的使用中经常遇到Vmware上安装的Linux虚机的LVM扩容情况,最终实现lv的扩容,大多数情况因为虚机都是有备用或者可停机的情况,一般情况下通过添加一块物理盘再加入vg,然后扩容lv来实...

5.4K Star很容易!Windows读取Linux磁盘格式工具

[开源日记],分享10k+Star的优质开源项目...

Linux 文件系统监控:用脚本自动化磁盘空间管理

在Linux系统中,文件系统监控是一项非常重要的任务,它可以帮助我们及时发现磁盘空间不足的问题,避免因磁盘满而导致的系统服务不可用。通过编写脚本自动化磁盘空间管理,我们可以更加高效地处理这一问题。下面...

Linux磁盘管理LVM实战(linux实验磁盘管理)

LVM(逻辑卷管理器,LogicalVolumeManager)是一种在Linux系统中用于灵活管理磁盘空间的技术,通过将物理磁盘抽象为逻辑卷,实现动态调整存储容量、跨磁盘扩展等功能。本章节...

Linux查看文件大小:`ls`和`du`为何结果不同?一文讲透原理!

Linux查看文件大小:ls和du为何结果不同?一文讲透原理!在Linux运维中,查看文件大小是日常高频操作。但你是否遇到过以下困惑?...

使用 df 命令检查服务器磁盘满了,但用 du 命令发现实际小于磁盘容量

在Linux系统中,管理员或开发者经常会遇到一个令人困惑的问题:使用...

Linux磁盘爆满紧急救援指南:5步清理释放50GB+小白也能轻松搞定

“服务器卡死?网站崩溃?当Linux系统弹出‘Nospaceleft’的红色警报,别慌!本文手把手教你从‘删库到跑路’进阶为‘磁盘清理大师’,5个关键步骤+30条救命命令,快速释放磁盘空间,拯救你...

取消回复欢迎 发表评论: