8

验证 golang 中 unsafe 包不安全

 3 years ago
source link: https://www.purewhite.io/2019/04/02/golang-validate-unsafe-is-unsafe/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

验证 golang 中 unsafe 包不安全

2019-04-02go 502
2.9k 5 分钟

在 go 中,uintptr 不能持有对象,unsafe 包不安全,但是我之前一直没有时间验证,今天写了段代码验证了一下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package main

import (
"fmt"
"unsafe"
)

func main() {
a := f()
b := f2()
fmt.Println(a)
fmt.Println(b)
}

//go:noinline
func f() unsafe.Pointer {
d := 1
p := unsafe.Pointer(&d)
return p
}

//go:noinline
func f2() uintptr {
d := 1
p := uintptr(unsafe.Pointer(&d))
return p
}

根据逃逸分析可以看出来 f 和 f2 这两个函数中的 d 变量分别分配在哪里:

编译参数方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
$ go build -gcflags '-m -m' unsafe.go
# command-line-arguments
./unsafe.go:16:6: cannot inline f: marked go:noinline
./unsafe.go:23:6: cannot inline f2: marked go:noinline
./unsafe.go:8:6: cannot inline main: function too complex: cost 260 exceeds budget 80
./unsafe.go:11:13: inlining call to fmt.Println func(...interface {}) (int, error) { return fmt.Fprintln(io.Writer(os.Stdout), fmt.a...) }
./unsafe.go:12:13: inlining call to fmt.Println func(...interface {}) (int, error) { return fmt.Fprintln(io.Writer(os.Stdout), fmt.a...) }
./unsafe.go:18:22: &d escapes to heap
./unsafe.go:18:22: from p (assigned) at ./unsafe.go:18:4
./unsafe.go:18:22: from ~r0 (return) at ./unsafe.go:19:2
./unsafe.go:17:2: moved to heap: d
./unsafe.go:25:30: f2 &d does not escape
./unsafe.go:11:13: a escapes to heap
./unsafe.go:11:13: from ~arg0 (assign-pair) at ./unsafe.go:11:13
./unsafe.go:11:13: io.Writer(os.Stdout) escapes to heap
./unsafe.go:11:13: from io.Writer(os.Stdout) (passed to call[argument escapes]) at ./unsafe.go:11:13
./unsafe.go:12:13: io.Writer(os.Stdout) escapes to heap
./unsafe.go:12:13: from io.Writer(os.Stdout) (passed to call[argument escapes]) at ./unsafe.go:12:13
./unsafe.go:12:13: b escapes to heap
./unsafe.go:12:13: from ~arg0 (assign-pair) at ./unsafe.go:12:13
./unsafe.go:12:13: from []interface {} literal (slice-literal-element) at ./unsafe.go:12:13
./unsafe.go:12:13: from fmt.a (assigned) at ./unsafe.go:12:13
./unsafe.go:12:13: from *fmt.a (indirection) at ./unsafe.go:12:13
./unsafe.go:12:13: from fmt.a (passed to call[argument content escapes]) at ./unsafe.go:12:13
./unsafe.go:11:13: main []interface {} literal does not escape
./unsafe.go:12:13: main []interface {} literal does not escape
<autogenerated>:1: os.(*File).close .this does not escape

可以看出来在函数 f 中,d 逃逸到堆上;但是在函数 f2 中,d 没有发生逃逸,uintptr 没有持有对象。

再来看看汇编的结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ go tool compile -S unsafe.go | grep unsafe.go:24
0x000e 00014 (unsafe.go:24) PCDATA $2, $0
0x000e 00014 (unsafe.go:24) PCDATA $0, $0
0x000e 00014 (unsafe.go:24) MOVQ $1, "".d(SP)
$ go tool compile -S unsafe.go | grep unsafe.go:17
0x001d 00029 (unsafe.go:17) PCDATA $2, $1
0x001d 00029 (unsafe.go:17) PCDATA $0, $0
0x001d 00029 (unsafe.go:17) LEAQ type.int(SB), AX
0x0024 00036 (unsafe.go:17) PCDATA $2, $0
0x0024 00036 (unsafe.go:17) MOVQ AX, (SP)
0x0028 00040 (unsafe.go:17) CALL runtime.newobject(SB)
0x002d 00045 (unsafe.go:17) PCDATA $2, $1
0x002d 00045 (unsafe.go:17) MOVQ 8(SP), AX
0x0032 00050 (unsafe.go:17) MOVQ $1, (AX)

可以看出来,结果也是一样的,f 中的 d 调用了 newobject,但是 f2 中没有。

所以为什么说 unsafe 包不安全呢,原因之一就是因为 go 不保证地址一定是有效的,当然还有其它的原因,有时间再验证分享。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK