runtime: print the address where a panic happened

This is not very useful in itself, but makes it possible to detect this
address in the output. See the next commit.

This adds around 50 bytes to each binary (except for AVR and wasm). This
is unfortunate, but I think this feature is quite useful still.
A future enhancement might be to create a build tag for extended panic
information that's not set by default.
Этот коммит содержится в:
Ayke van Laethem 2023-04-25 01:22:55 +02:00 коммит произвёл Ron Evans
родитель 59838338ba
коммит 3392827c3e
14 изменённых файлов: 67 добавлений и 16 удалений

Просмотреть файл

@ -41,9 +41,9 @@ func TestBinarySize(t *testing.T) {
// This is a small number of very diverse targets that we want to test.
tests := []sizeTest{
// microcontrollers
{"hifive1b", "examples/echo", 4556, 272, 0, 2252},
{"microbit", "examples/serial", 2680, 380, 8, 2256},
{"wioterminal", "examples/pininterrupt", 6109, 1471, 116, 6816},
{"hifive1b", "examples/echo", 4612, 276, 0, 2252},
{"microbit", "examples/serial", 2724, 384, 8, 2256},
{"wioterminal", "examples/pininterrupt", 6159, 1477, 116, 6816},
// TODO: also check wasm. Right now this is difficult, because
// wasm binaries are run through wasm-opt and therefore the

10
src/runtime/arch-has-returnaddr.go Обычный файл
Просмотреть файл

@ -0,0 +1,10 @@
//go:build !(avr || tinygo.wasm)
package runtime
import "unsafe"
const hasReturnAddr = true
//export llvm.returnaddress
func returnAddress(level uint32) unsafe.Pointer

11
src/runtime/arch-no-returnaddr.go Обычный файл
Просмотреть файл

@ -0,0 +1,11 @@
//go:build avr || tinygo.wasm
package runtime
import "unsafe"
const hasReturnAddr = false
func returnAddress(level uint32) unsafe.Pointer {
return nil
}

Просмотреть файл

@ -7,6 +7,8 @@ const TargetBits = 32
const deferExtraRegs = 0
const callInstSize = 5 // "call someFunction" is 5 bytes
// Align on word boundary.
func align(ptr uintptr) uintptr {
return (ptr + 15) &^ 15

Просмотреть файл

@ -7,6 +7,8 @@ const TargetBits = 64
const deferExtraRegs = 0
const callInstSize = 5 // "call someFunction" is 5 bytes
// Align a pointer.
// Note that some amd64 instructions (like movaps) expect 16-byte aligned
// memory, thus the result must be 16-byte aligned.

Просмотреть файл

@ -9,6 +9,8 @@ const TargetBits = 32
const deferExtraRegs = 0
const callInstSize = 4 // "bl someFunction" is 4 bytes
// Align on the maximum alignment for this platform (double).
func align(ptr uintptr) uintptr {
return (ptr + 7) &^ 7

Просмотреть файл

@ -7,6 +7,8 @@ const TargetBits = 64
const deferExtraRegs = 0
const callInstSize = 4 // "bl someFunction" is 4 bytes
// Align on word boundary.
func align(ptr uintptr) uintptr {
return (ptr + 15) &^ 15

Просмотреть файл

@ -11,6 +11,8 @@ const TargetBits = 8
const deferExtraRegs = 1 // the frame pointer (Y register) also needs to be stored
const callInstSize = 2 // "call" is 4 bytes, "rcall" is 2 bytes
// Align on a word boundary.
func align(ptr uintptr) uintptr {
// No alignment necessary on the AVR.

Просмотреть файл

@ -13,6 +13,8 @@ const TargetBits = 32
const deferExtraRegs = 0
const callInstSize = 4 // "bl someFunction" is 4 bytes
// Align on word boundary.
func align(ptr uintptr) uintptr {
return (ptr + 7) &^ 7

Просмотреть файл

@ -6,6 +6,8 @@ import "device/riscv"
const deferExtraRegs = 0
const callInstSize = 4 // 8 without relaxation, maybe 4 with relaxation
// RISC-V has a maximum alignment of 16 bytes (both for RV32 and for RV64).
// Source: https://riscv.org/wp-content/uploads/2015/01/riscv-calling.pdf
func align(ptr uintptr) uintptr {

Просмотреть файл

@ -13,6 +13,8 @@ const TargetBits = 32
const deferExtraRegs = 0
const callInstSize = 1 // unknown and irrelevant (llvm.returnaddress doesn't work), so make something up
//go:extern __heap_base
var heapStartSymbol [0]byte

Просмотреть файл

@ -9,6 +9,8 @@ const TargetBits = 32
const deferExtraRegs = 0
const callInstSize = 3 // "callx0 someFunction" (and similar) is 3 bytes
// The largest alignment according to the Xtensa ABI is 8 (long long, double).
func align(ptr uintptr) uintptr {
return (ptr + 7) &^ 7

Просмотреть файл

@ -278,7 +278,7 @@ func alloc(size uintptr, layout unsafe.Pointer) unsafe.Pointer {
}
if interrupt.In() {
runtimePanic("alloc in interrupt")
runtimePanicAt(returnAddress(0), "alloc in interrupt")
}
gcTotalAlloc += uint64(size)
@ -318,7 +318,7 @@ func alloc(size uintptr, layout unsafe.Pointer) unsafe.Pointer {
// Unfortunately the heap could not be increased. This
// happens on baremetal systems for example (where all
// available RAM has already been dedicated to the heap).
runtimePanic("out of memory")
runtimePanicAt(returnAddress(0), "out of memory")
}
}
}

Просмотреть файл

@ -54,7 +54,19 @@ func _panic(message interface{}) {
// Cause a runtime panic, which is (currently) always a string.
func runtimePanic(msg string) {
// As long as this function is inined, llvm.returnaddress(0) will return
// something sensible.
runtimePanicAt(returnAddress(0), msg)
}
func runtimePanicAt(addr unsafe.Pointer, msg string) {
if hasReturnAddr {
printstring("panic: runtime error at ")
printptr(uintptr(addr) - callInstSize)
printstring(": ")
} else {
printstring("panic: runtime error: ")
}
println(msg)
abort()
}
@ -119,52 +131,52 @@ func _recover(useParentFrame bool) interface{} {
// Panic when trying to dereference a nil pointer.
func nilPanic() {
runtimePanic("nil pointer dereference")
runtimePanicAt(returnAddress(0), "nil pointer dereference")
}
// Panic when trying to add an entry to a nil map
func nilMapPanic() {
runtimePanic("assignment to entry in nil map")
runtimePanicAt(returnAddress(0), "assignment to entry in nil map")
}
// Panic when trying to acces an array or slice out of bounds.
func lookupPanic() {
runtimePanic("index out of range")
runtimePanicAt(returnAddress(0), "index out of range")
}
// Panic when trying to slice a slice out of bounds.
func slicePanic() {
runtimePanic("slice out of range")
runtimePanicAt(returnAddress(0), "slice out of range")
}
// Panic when trying to convert a slice to an array pointer (Go 1.17+) and the
// slice is shorter than the array.
func sliceToArrayPointerPanic() {
runtimePanic("slice smaller than array")
runtimePanicAt(returnAddress(0), "slice smaller than array")
}
// Panic when calling unsafe.Slice() (Go 1.17+) or unsafe.String() (Go 1.20+)
// with a len that's too large (which includes if the ptr is nil and len is
// nonzero).
func unsafeSlicePanic() {
runtimePanic("unsafe.Slice/String: len out of range")
runtimePanicAt(returnAddress(0), "unsafe.Slice/String: len out of range")
}
// Panic when trying to create a new channel that is too big.
func chanMakePanic() {
runtimePanic("new channel is too big")
runtimePanicAt(returnAddress(0), "new channel is too big")
}
// Panic when a shift value is negative.
func negativeShiftPanic() {
runtimePanic("negative shift")
runtimePanicAt(returnAddress(0), "negative shift")
}
// Panic when there is a divide by zero.
func divideByZeroPanic() {
runtimePanic("divide by zero")
runtimePanicAt(returnAddress(0), "divide by zero")
}
func blockingPanic() {
runtimePanic("trying to do blocking operation in exported function")
runtimePanicAt(returnAddress(0), "trying to do blocking operation in exported function")
}