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.
Этот коммит содержится в:
родитель
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.
|
// This is a small number of very diverse targets that we want to test.
|
||||||
tests := []sizeTest{
|
tests := []sizeTest{
|
||||||
// microcontrollers
|
// microcontrollers
|
||||||
{"hifive1b", "examples/echo", 4556, 272, 0, 2252},
|
{"hifive1b", "examples/echo", 4612, 276, 0, 2252},
|
||||||
{"microbit", "examples/serial", 2680, 380, 8, 2256},
|
{"microbit", "examples/serial", 2724, 384, 8, 2256},
|
||||||
{"wioterminal", "examples/pininterrupt", 6109, 1471, 116, 6816},
|
{"wioterminal", "examples/pininterrupt", 6159, 1477, 116, 6816},
|
||||||
|
|
||||||
// TODO: also check wasm. Right now this is difficult, because
|
// TODO: also check wasm. Right now this is difficult, because
|
||||||
// wasm binaries are run through wasm-opt and therefore the
|
// wasm binaries are run through wasm-opt and therefore the
|
||||||
|
|
10
src/runtime/arch-has-returnaddr.go
Обычный файл
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
Обычный файл
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 deferExtraRegs = 0
|
||||||
|
|
||||||
|
const callInstSize = 5 // "call someFunction" is 5 bytes
|
||||||
|
|
||||||
// Align on word boundary.
|
// Align on word boundary.
|
||||||
func align(ptr uintptr) uintptr {
|
func align(ptr uintptr) uintptr {
|
||||||
return (ptr + 15) &^ 15
|
return (ptr + 15) &^ 15
|
||||||
|
|
|
@ -7,6 +7,8 @@ const TargetBits = 64
|
||||||
|
|
||||||
const deferExtraRegs = 0
|
const deferExtraRegs = 0
|
||||||
|
|
||||||
|
const callInstSize = 5 // "call someFunction" is 5 bytes
|
||||||
|
|
||||||
// Align a pointer.
|
// Align a pointer.
|
||||||
// Note that some amd64 instructions (like movaps) expect 16-byte aligned
|
// Note that some amd64 instructions (like movaps) expect 16-byte aligned
|
||||||
// memory, thus the result must be 16-byte aligned.
|
// memory, thus the result must be 16-byte aligned.
|
||||||
|
|
|
@ -9,6 +9,8 @@ const TargetBits = 32
|
||||||
|
|
||||||
const deferExtraRegs = 0
|
const deferExtraRegs = 0
|
||||||
|
|
||||||
|
const callInstSize = 4 // "bl someFunction" is 4 bytes
|
||||||
|
|
||||||
// Align on the maximum alignment for this platform (double).
|
// Align on the maximum alignment for this platform (double).
|
||||||
func align(ptr uintptr) uintptr {
|
func align(ptr uintptr) uintptr {
|
||||||
return (ptr + 7) &^ 7
|
return (ptr + 7) &^ 7
|
||||||
|
|
|
@ -7,6 +7,8 @@ const TargetBits = 64
|
||||||
|
|
||||||
const deferExtraRegs = 0
|
const deferExtraRegs = 0
|
||||||
|
|
||||||
|
const callInstSize = 4 // "bl someFunction" is 4 bytes
|
||||||
|
|
||||||
// Align on word boundary.
|
// Align on word boundary.
|
||||||
func align(ptr uintptr) uintptr {
|
func align(ptr uintptr) uintptr {
|
||||||
return (ptr + 15) &^ 15
|
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 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.
|
// Align on a word boundary.
|
||||||
func align(ptr uintptr) uintptr {
|
func align(ptr uintptr) uintptr {
|
||||||
// No alignment necessary on the AVR.
|
// No alignment necessary on the AVR.
|
||||||
|
|
|
@ -13,6 +13,8 @@ const TargetBits = 32
|
||||||
|
|
||||||
const deferExtraRegs = 0
|
const deferExtraRegs = 0
|
||||||
|
|
||||||
|
const callInstSize = 4 // "bl someFunction" is 4 bytes
|
||||||
|
|
||||||
// Align on word boundary.
|
// Align on word boundary.
|
||||||
func align(ptr uintptr) uintptr {
|
func align(ptr uintptr) uintptr {
|
||||||
return (ptr + 7) &^ 7
|
return (ptr + 7) &^ 7
|
||||||
|
|
|
@ -6,6 +6,8 @@ import "device/riscv"
|
||||||
|
|
||||||
const deferExtraRegs = 0
|
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).
|
// 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
|
// Source: https://riscv.org/wp-content/uploads/2015/01/riscv-calling.pdf
|
||||||
func align(ptr uintptr) uintptr {
|
func align(ptr uintptr) uintptr {
|
||||||
|
|
|
@ -13,6 +13,8 @@ const TargetBits = 32
|
||||||
|
|
||||||
const deferExtraRegs = 0
|
const deferExtraRegs = 0
|
||||||
|
|
||||||
|
const callInstSize = 1 // unknown and irrelevant (llvm.returnaddress doesn't work), so make something up
|
||||||
|
|
||||||
//go:extern __heap_base
|
//go:extern __heap_base
|
||||||
var heapStartSymbol [0]byte
|
var heapStartSymbol [0]byte
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,8 @@ const TargetBits = 32
|
||||||
|
|
||||||
const deferExtraRegs = 0
|
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).
|
// The largest alignment according to the Xtensa ABI is 8 (long long, double).
|
||||||
func align(ptr uintptr) uintptr {
|
func align(ptr uintptr) uintptr {
|
||||||
return (ptr + 7) &^ 7
|
return (ptr + 7) &^ 7
|
||||||
|
|
|
@ -278,7 +278,7 @@ func alloc(size uintptr, layout unsafe.Pointer) unsafe.Pointer {
|
||||||
}
|
}
|
||||||
|
|
||||||
if interrupt.In() {
|
if interrupt.In() {
|
||||||
runtimePanic("alloc in interrupt")
|
runtimePanicAt(returnAddress(0), "alloc in interrupt")
|
||||||
}
|
}
|
||||||
|
|
||||||
gcTotalAlloc += uint64(size)
|
gcTotalAlloc += uint64(size)
|
||||||
|
@ -318,7 +318,7 @@ func alloc(size uintptr, layout unsafe.Pointer) unsafe.Pointer {
|
||||||
// Unfortunately the heap could not be increased. This
|
// Unfortunately the heap could not be increased. This
|
||||||
// happens on baremetal systems for example (where all
|
// happens on baremetal systems for example (where all
|
||||||
// available RAM has already been dedicated to the heap).
|
// 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.
|
// Cause a runtime panic, which is (currently) always a string.
|
||||||
func runtimePanic(msg string) {
|
func runtimePanic(msg string) {
|
||||||
printstring("panic: runtime error: ")
|
// 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)
|
println(msg)
|
||||||
abort()
|
abort()
|
||||||
}
|
}
|
||||||
|
@ -119,52 +131,52 @@ func _recover(useParentFrame bool) interface{} {
|
||||||
|
|
||||||
// Panic when trying to dereference a nil pointer.
|
// Panic when trying to dereference a nil pointer.
|
||||||
func nilPanic() {
|
func nilPanic() {
|
||||||
runtimePanic("nil pointer dereference")
|
runtimePanicAt(returnAddress(0), "nil pointer dereference")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Panic when trying to add an entry to a nil map
|
// Panic when trying to add an entry to a nil map
|
||||||
func nilMapPanic() {
|
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.
|
// Panic when trying to acces an array or slice out of bounds.
|
||||||
func lookupPanic() {
|
func lookupPanic() {
|
||||||
runtimePanic("index out of range")
|
runtimePanicAt(returnAddress(0), "index out of range")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Panic when trying to slice a slice out of bounds.
|
// Panic when trying to slice a slice out of bounds.
|
||||||
func slicePanic() {
|
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
|
// Panic when trying to convert a slice to an array pointer (Go 1.17+) and the
|
||||||
// slice is shorter than the array.
|
// slice is shorter than the array.
|
||||||
func sliceToArrayPointerPanic() {
|
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+)
|
// 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
|
// with a len that's too large (which includes if the ptr is nil and len is
|
||||||
// nonzero).
|
// nonzero).
|
||||||
func unsafeSlicePanic() {
|
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.
|
// Panic when trying to create a new channel that is too big.
|
||||||
func chanMakePanic() {
|
func chanMakePanic() {
|
||||||
runtimePanic("new channel is too big")
|
runtimePanicAt(returnAddress(0), "new channel is too big")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Panic when a shift value is negative.
|
// Panic when a shift value is negative.
|
||||||
func negativeShiftPanic() {
|
func negativeShiftPanic() {
|
||||||
runtimePanic("negative shift")
|
runtimePanicAt(returnAddress(0), "negative shift")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Panic when there is a divide by zero.
|
// Panic when there is a divide by zero.
|
||||||
func divideByZeroPanic() {
|
func divideByZeroPanic() {
|
||||||
runtimePanic("divide by zero")
|
runtimePanicAt(returnAddress(0), "divide by zero")
|
||||||
}
|
}
|
||||||
|
|
||||||
func blockingPanic() {
|
func blockingPanic() {
|
||||||
runtimePanic("trying to do blocking operation in exported function")
|
runtimePanicAt(returnAddress(0), "trying to do blocking operation in exported function")
|
||||||
}
|
}
|
||||||
|
|
Загрузка…
Создание таблицы
Сослаться в новой задаче