首页 > 其他分享 >【解决一个小问题】golang 的 `-race`选项导致 unsafe代码 panic

【解决一个小问题】golang 的 `-race`选项导致 unsafe代码 panic

时间:2023-06-13 18:22:08浏览次数:51  
标签:unsafe golang start race go byte Pointer

作者:张富春(ahfuzhang),转载时请注明作者和引用链接,谢谢!


为了提升性能,使用 unsafe 代码来重构了凯撒加密的代码。代码如下:

const (
	lowerCaseAlphabet = "abcdefghijklmnopqrstuvwxyz"
	upperCaseAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
)

var (
	lowerCaseAlphabetArr     = []byte(lowerCaseAlphabet)
	upperCaseAlphabetArr     = []byte(upperCaseAlphabet)
	tableOflowerCaseAlphabet = unsafe.Pointer(&lowerCaseAlphabetArr[0])
	tableOfupperCaseAlphabe  = unsafe.Pointer(&upperCaseAlphabetArr[0])
)

// CaesarFastEncode fast version
func CaesarFastEncode(in []byte, out []byte, rot int) {
	start := unsafe.Pointer(&in[0])
	target := unsafe.Pointer(&out[0])
	for i := 0; i < len(in); i++ {
		c := *((*byte)(start))
		if c == '.' {
			*((*byte)(target)) = '='
		} else if c >= 'a' && c <= 'z' {
			idx := (int(26+(c-'a')) + rot) % 26
			*((*byte)(target)) = *((*byte)(unsafe.Pointer(uintptr(tableOflowerCaseAlphabet) + uintptr(idx))))
		} else if c >= 'A' && c <= 'Z' {
			idx := (int(26+(c-'A')) + rot) % 26
			*((*byte)(target)) = *((*byte)(unsafe.Pointer(uintptr(tableOfupperCaseAlphabe) + uintptr(idx))))
		} else {
			*((*byte)(target)) = *((*byte)(start))
		}
		start = unsafe.Pointer(uintptr(start) + uintptr(1))
		target = unsafe.Pointer(uintptr(target) + uintptr(1))
	}
}

命令行运行go test 的时候发现,代码中发生了 panic。而我直接在 vscode 中通过快捷键运行又是正常的。
错误信息如下:

fatal error: checkptr: pointer arithmetic result points to invalid allocation

goroutine 6 [running]:
runtime.throw({0x104936d70?, 0x10410270c?})
        /opt/homebrew/Cellar/go/1.20.4/libexec/src/runtime/panic.go:1047 +0x40 fp=0xc0001e5a60 sp=0xc0001e5a30 pc=0x104132280
runtime.checkptrArithmetic(0xc0001e5ac8?, {0xc0001e5b00, 0x1, 0x0?})
        /opt/homebrew/Cellar/go/1.20.4/libexec/src/runtime/checkptr.go:69 +0xac fp=0xc0001e5a90 sp=0xc0001e5a60 pc=0x1041028cc
cryptoutil.CaesarFastEncode({0xc000216cc4, 0xc, 0xc0001e5b68?}, {0xc000232a02, 0x1fe, 0x104f3ee00?}, 0x3)
        /Users/ahfuzhang/code/golang/xxx/caesar.go:83 +0x84 fp=0xc0001e5b20 sp=0xc0001e5a90 pc=0x104570a74

进一步发现,命令行中有个不一样的选项:

go test -v -cover -race ./...

去掉 -race选项后,一切正常。

搜索看到了这篇文章:《Go 1.15中值得关注的几个变化

Go 1.14版本中,Go编译器在被传入-race和-msan的情况下,默认会执行-d=checkptr,即对unsafe.Pointer的使用进行合法性检查。-d=checkptr主要检查两项内容:

•当将unsafe.Pointer转型为*T时,T的内存对齐系数不能高于原地址的;

•做完指针算术后,转换后的unsafe.Pointer仍应指向原先Go堆对象

由此可见,循环做完后,最后一行必然导致指针超出原来的 buffer。
为了符合 golang 的规范,微调了代码后通过:

// CaesarFastEncode fast version
func CaesarFastEncode(in []byte, out []byte, rot int) {
	start := unsafe.Pointer(&in[0])
	end := uintptr(start) + uintptr(len(in)-1)
	target := (unsafe.Pointer(&out[0]))
	for {
		c := *((*byte)(start))
		if c == '.' {
			*((*byte)(target)) = '='
		} else if c >= 'a' && c <= 'z' {
			idx := (int(26+(c-'a')) + rot) % 26
			*((*byte)(target)) = *((*byte)(unsafe.Pointer(uintptr(tableOflowerCaseAlphabet) + uintptr(idx))))
		} else if c >= 'A' && c <= 'Z' {
			idx := (int(26+(c-'A')) + rot) % 26
			*((*byte)(target)) = *((*byte)(unsafe.Pointer(uintptr(tableOfupperCaseAlphabe) + uintptr(idx))))
		} else {
			*((*byte)(target)) = *((*byte)(unsafe.Pointer(start)))
		}
		if uintptr(start) >= end {
			break
		}
		start = unsafe.Pointer(uintptr(start) + uintptr(1))
		target = unsafe.Pointer(uintptr(target) + uintptr(1))
	}
}

由此看来,只要使用了 unsafe 代码,都应该加上-race选项。

标签:unsafe,golang,start,race,go,byte,Pointer
From: https://www.cnblogs.com/ahfuzhang/p/17478424.html

相关文章

  • mac 下Golang 安装Protobuf
    1、安装protobufbrewinstallprotobuf2、检查安装结果protoc--version3、安装golangforprotobuf插件gogetgithub.com/golang/protobuf/protoc-gen-gogoget-u-vgithub.com/golang/protobuf/protoc-gen-gogoget=gitclone+goinstall这里会慢的要死所以我这里采取......
  • golang 实现cas
    相比sync.WaitGroup里面的互斥锁,cas可以实现无锁等待一组任务执行完成后释放,示例代码如下funcTestCAS(t*testing.T){ varcountint32=10000 fori:=0;i<int(count);{ gofunc(){ deferfunc(){atomic.AddInt32(&count,-1)}() //dosomething //.........
  • golang 闭包,装饰器
    packagemainimport( "fmt" "strings")funcmakeSuffixFunc(suffixstring)func(string)string{ returnfunc(namestring)string{ if!strings.HasSuffix(name,suffix){ returnname+suffix } returnname }}funcmain()......
  • golang之fmt格式化
    常用fmt中用于格式化的占位符 普通占位符占位符说明举例输出%v相应值的默认格式。Printf("%v",people){zhangsan},%+v打印结构体时,会添加字段名Printf("%+v",people){Name:zhangsan......
  • try……except配合traceback模块,进行不报错异常捕获
    通过try语句去尝试做正确的事,如果中途遭遇了意外情况就引发异常提示try:………………………………………………………………return"成功执行"exceptExceptionase:#Handletheexceptiont......
  • Golang 应用脚手架
    Nunu提供了全面的文档和示例,帮助你快速入门。它还包括一套测试套件,确保你的应用程序按预期工作。安装要求要使用Nunu,你需要在系统上安装以下软件:Golang1.16或更高版本GitMySQL5.7或更高版本(可选)Redis(可选)安装你可以通过一行命令安装Nunu:1goinstall......
  • phptrace 是一个用于跟踪 PHP 应用程序性能的工具,可以帮助开发者快速发现性能瓶颈和调
    phptrace是一个用于跟踪PHP应用程序性能的工具,可以帮助开发者快速发现性能瓶颈和调试PHP应用程序。以下是一个使用phptrace的简单案例:1.安装phptrace可以通过以下命令安装phptrace:sudoapt-getinstallphp7.0-devgitclonehttps://github.com/Qihoo360/phptrace.......
  • ftrace学习 —— user_events的用法
    参考https://docs.kernel.org/trace/user_events.html测试程序samples/user_events/example.ctools/testing/selftests/user_events/ftrace_test.c正文通过user_event可以实现对应用程序的跟踪,类似linux内核中的tracepoint那样。相似的方法还有借助/sys/kernel/debug/tracin......
  • golang之channel的使用
    golang之channel的使用在当今快速发展的软件开发领域,使用高效且可靠的编程语言变得尤为重要。而golang(又称Go语言)正是一种备受欢迎的编程语言,它的简洁、高效以及并发处理能力使得它在开发者中越来越受欢迎。在本文中,我们将探讨golang中的一个重要特性——channel,并介绍如何利用它......
  • Golang全栈开发----Golang基础知识
    第一章基础语法1.注释注释就是对代码的解释和说明,其目的是让人们能够更加轻松地了解代码。注释是开发人员一个非常重要的习惯,也是专业的一种表现。单行注释是最常见的注释形式,你可以在任何地方使用以//开头的单行注释。多行注释也叫块注释,均已以/*开头,并以*/结尾。单行......