使用`recover`函数来处理`SIGSEGV`信号吗?

huangapple go评论89阅读模式
英文:

Handling SIGSEGV with recover?

问题

信号包中提到:

同步信号是由程序执行中的错误触发的信号:SIGBUS、SIGFPE和SIGSEGV。只有在由程序执行引起时,才被认为是同步的,而不是通过os.Process.Kill或kill程序或某些类似机制发送的信号。一般来说,除非另有讨论,Go程序会将同步信号转换为运行时恐慌。

然而,似乎recover()无法捕获到这个信号。

程序:

package main

import (
	"fmt"
	"unsafe"

	"log"
)

func seeAnotherDay() {
	defer func() {
		if p := recover(); p != nil {
			err := fmt.Errorf("recover panic: panic call")
			log.Println(err)
			return
		}
	}()
	panic("oops")
}

func notSoMuch() {
	defer func() {
		if p := recover(); p != nil {
			err := fmt.Errorf("recover panic: sigseg")
			log.Println(err)
			return
		}
	}()
	b := make([]byte, 1)
	log.Println("access some memory")
	foo := (*int)(unsafe.Pointer(uintptr(unsafe.Pointer(&b[0])) + uintptr(9999999999999999)))
	fmt.Print(*foo + 1)
}

func main() {
	seeAnotherDay()
	notSoMuch()
}

输出:

2017/04/04 12:13:16 recover panic: panic call
2017/04/04 12:13:16 access some memory
unexpected fault address 0xb01dfacedebac1e
fatal error: fault
[signal SIGSEGV: segmentation violation code=0x1 addr=0xb01dfacedebac1e pc=0x108aa8a]

goroutine 1 [running]:
runtime.throw(0x10b5807, 0x5)
           /usr/local/go/src/runtime/panic.go:596 +0x95 fp=0xc420043ea8 sp=0xc420043e88
runtime.sigpanic()
           /usr/local/go/src/runtime/signal_unix.go:297 +0x28c fp=0xc420043ef8 sp=0xc420043ea8
main.notSoMuch()
           /Users/kbrandt/src/sigseg/main.go:32 +0xca fp=0xc420043f78 sp=0xc420043ef8
main.main()
           /Users/kbrandt/src/sigseg/main.go:37 +0x25 fp=0xc420043f88 sp=0xc420043f78
runtime.main()
           /usr/local/go/src/runtime/proc.go:185 +0x20a fp=0xc420043fe0 sp=0xc420043f88
runtime.goexit()
           /usr/local/go/src/runtime/asm_amd64.s:2197 +0x1 fp=0xc420043fe8 sp=0xc420043fe0
exit status 2

有没有办法在代码的特定部分处理SIGSEGV信号?

英文:

The signal package states:

> Synchronous signals are signals triggered by errors in program
> execution: SIGBUS, SIGFPE, and SIGSEGV. These are only considered
> synchronous when caused by program execution, not when sent using
> os.Process.Kill or the kill program or some similar mechanism. In
> general, except as discussed below, Go programs will convert a
> synchronous signal into a run-time panic.

However, it seems recover() is not catching this.

Program:

package main

import (
	"fmt"
	"unsafe"

	"log"
)

func seeAnotherDay() {
	defer func() {
		if p := recover(); p != nil {
			err := fmt.Errorf("recover panic: panic call")
			log.Println(err)
			return
		}
	}()
	panic("oops")
}

func notSoMuch() {
	defer func() {
		if p := recover(); p != nil {
			err := fmt.Errorf("recover panic: sigseg")
			log.Println(err)
			return
		}
	}()
	b := make([]byte, 1)
	log.Println("access some memory")
	foo := (*int)(unsafe.Pointer(uintptr(unsafe.Pointer(&b[0])) + uintptr(9999999999999999)))
	fmt.Print(*foo + 1)
}

func main() {
	seeAnotherDay()
	notSoMuch()
}

Output:

2017/04/04 12:13:16 recover panic: panic call
2017/04/04 12:13:16 access some memory
unexpected fault address 0xb01dfacedebac1e
fatal error: fault
[signal SIGSEGV: segmentation violation code=0x1 addr=0xb01dfacedebac1e pc=0x108aa8a]

goroutine 1 [running]:
runtime.throw(0x10b5807, 0x5)
           /usr/local/go/src/runtime/panic.go:596 +0x95 fp=0xc420043ea8 sp=0xc420043e88
runtime.sigpanic()
           /usr/local/go/src/runtime/signal_unix.go:297 +0x28c fp=0xc420043ef8 sp=0xc420043ea8
main.notSoMuch()
           /Users/kbrandt/src/sigseg/main.go:32 +0xca fp=0xc420043f78 sp=0xc420043ef8
main.main()
           /Users/kbrandt/src/sigseg/main.go:37 +0x25 fp=0xc420043f88 sp=0xc420043f78
runtime.main()
           /usr/local/go/src/runtime/proc.go:185 +0x20a fp=0xc420043fe0 sp=0xc420043f88
runtime.goexit()
           /usr/local/go/src/runtime/asm_amd64.s:2197 +0x1 fp=0xc420043fe8 sp=0xc420043fe0
exit status 2

Is there any way I could handle SIGSEGV in a way localized to certain parts of the code?

答案1

得分: 9

是的,你需要使用debug.SetPanicOnFault将在意外(非nil)地址处发生的故障转换为可以恢复的恐慌。根据文档:

SetPanicOnFault控制程序在意外(非nil)地址处发生故障时的运行时行为。这种故障通常是由于运行时内存损坏等错误引起的,因此默认的响应是使程序崩溃。在处理内存映射文件或对内存进行不安全操作的程序中,可能会在非nil地址处发生故障,但情况不会像崩溃那样严重;SetPanicOnFault允许这类程序请求运行时仅触发恐慌,而不是崩溃。SetPanicOnFault仅适用于当前的goroutine。它返回先前的设置。

对于影响的本地化,请注意SetPanicOnFault是在goroutine级别上设置的,因此单个goroutine可以处理已知的不安全访问。

英文:

Yes, you will want to use debug.SetPanicOnFault to convert faults at an unexpected (non-nil) address into panics from which you can recover. From the docs:

> SetPanicOnFault controls the runtime's behavior when a program faults at an unexpected (non-nil) address. Such faults are typically caused by bugs such as runtime memory corruption, so the default response is to crash the program. Programs working with memory-mapped files or unsafe manipulation of memory may cause faults at non-nil addresses in less dramatic situations; SetPanicOnFault allows such programs to request that the runtime trigger only a panic, not a crash. SetPanicOnFault applies only to the current goroutine. It returns the previous setting.

For the localization of the impact, note that SetPanicOnFault is set at the goroutine level, so a single goroutine can deal with known unsafe access.

答案2

得分: 2

当你遇到sigsegv时,关于程序状态,你真的处于一个无法预料的情况。唯一通常安全的做法是停止一切,并可能让系统将内存转储到文件以进行调试,这就是Go语言所做的。在这种情况下,实际上没有办法“保护主运行时”。

如果你的运行时正在运行不受信任或不安全的代码,你真的应该将其隔离到一个单独的进程中。如果是你自己运行来自用户的代码(而不是用户自己运行代码),那么这个进程绝对应该被沙箱化。

所以我的建议是,采取以下任一措施:

  1. 让程序崩溃,然后让用户处理。在Go语言中,用户编写导致sigsegv的代码通常需要更多或更少主动尝试去踩自己的脚,所以这种情况应该很少见,并且可以归类为他们自己承担风险的事情。
  2. 将其分为一个监管进程和一个“不受信任/不安全”的子进程,监管进程从子进程中捕获不当的退出条件,并适当地报告它们。
英文:

When you encounter a sigsegv, you're really in an all-bets-are-off situation with regards to the program state. The only generally safe thing to do is to stop everything, and possibly have the system dump your memory to file for debugging, which is what Go does. There isn't really any way to "protect the main runtime" in this situation.

If you have a runtime that is running code that is untrusted or unsafe, you really should isolate it into a separate process instead. And if you are the one running the code received from the users (rather than the users themselves), this process should most definitely be sandboxed.

So my advice is, do either of the following:

  1. Let it crash and let the user handle it from there. The user writing code causing a sigsegv in Go normally requires more or less active attempts of shooting in the direction of one's foot, so it should be rare and arguably filed under things they are doing at their own risk anyway.
  2. Separate it into a supervisor process and an "untrusted/unsafe" child process, where the supervisor picks up improper exit conditions from the child process and reports them appropriately.

huangapple
  • 本文由 发表于 2017年4月5日 00:17:56
  • 转载请务必保留本文链接:https://go.coder-hub.com/43212593.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定