Created
January 10, 2025 14:47
-
-
Save aktau/357a08a0d1b67f6bc3766feb59136a16 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// $ go build crasher.go | |
// $ ( ulimit -c unlimited ; GOMAXPROCS=2 GOTRACEBACK=crash ./crasher ) | |
// $ viewcore core --exe crasher html --port 3981 | |
package main | |
import ( | |
"fmt" | |
"runtime" | |
"unsafe" | |
) | |
type nonTinyComposite1 struct { | |
a uint64 | |
b uint32 | |
c uint16 | |
d uint16 | |
f complex128 | |
} | |
// Same layout as nonTinyComposite1, but different type. | |
type nonTinyComposite2 struct { | |
a uint64 | |
b uint32 | |
c uint16 | |
d uint16 | |
f complex128 | |
} | |
type Large struct { | |
ptr *uint8 // Object must contain a pointer to trigger code path. | |
arr [32768 - 8]uint8 | |
} | |
func assert(pred bool, msg string) { | |
if !pred { | |
panic(msg) | |
} | |
} | |
func create[T any](v T, isTiny bool) (*T, []T) { | |
backing := make([]T, 3) // 12 bytes, fits in tinyalloc (<16B). | |
objsize := len(backing) * int(unsafe.Sizeof(v)) | |
assert((isTiny && objsize < 16) || (!isTiny && objsize >= 16), "allocation is not tiny/large") | |
backing[1] = v | |
return &backing[1], backing | |
} | |
func escape1[T any](o *T) (rendezvous func()) { | |
ready := make(chan struct{}) | |
block := make(chan struct{}) | |
go func() { | |
fmt.Printf("starting obj len %v\n", unsafe.Sizeof(*o)) | |
ready <- struct{}{} // Ensure the goroutine has scheduled. | |
<-block | |
fmt.Printf("finishing escape obj len %v\n", unsafe.Sizeof(*o)) | |
}() | |
return func() { block <- struct{}{} } | |
} | |
func escape2[T any](o *T, ready chan<- struct{}, block <-chan struct{}) { | |
fmt.Printf("starting obj len %v\n", unsafe.Sizeof(*o)) | |
ready <- struct{}{} // Ensure the goroutine has scheduled. | |
<-block | |
fmt.Printf("finishing escape obj len %v\n", unsafe.Sizeof(*o)) | |
} | |
func main() { | |
elTinyLostBacking, _ := create(uint32(0xDEAD), true) | |
defer runtime.KeepAlive(elTinyLostBacking) | |
elTinyKeepBacking, tinyKeepBacking := create(uint32(0xBEEF), true) | |
defer runtime.KeepAlive(elTinyKeepBacking) | |
defer runtime.KeepAlive(tinyKeepBacking) | |
elBigLostBacking, _ := create(uint64(0xCAFECAFE), false) | |
defer runtime.KeepAlive(elBigLostBacking) | |
elBigKeepBacking, bigKeepBacking := create(uint64(0xBABEBABE), false) | |
defer runtime.KeepAlive(elBigKeepBacking) | |
defer runtime.KeepAlive(bigKeepBacking) | |
plainUint16 := new(uint16) | |
*plainUint16 = 0xFEFE | |
defer runtime.KeepAlive(plainUint16) | |
nonTinyCompositeObj := new(nonTinyComposite1) | |
nonTinyCompositeObj.c = 0xCACA | |
defer runtime.KeepAlive(nonTinyCompositeObj) | |
const ( | |
escapeStyle1 = false | |
escapeStyle2 = false | |
) | |
// The following block is important and load-boarding, even if escapeStyle1/2 | |
// are both false. | |
// | |
// If uncommented, there is we see (truncated): | |
// | |
// Output: pre nonTinyCompositeObj: 0xc0000a6000 | |
// | |
// field | type | value | |
// ----- | ---- | ---- | |
// nonTinyCompositeObj | *main.nonTinyComposite1 | dead | |
// unk | unsafe.Pointer | object c0000a6000 | |
// | |
// If commented, we see: | |
// | |
// Output: pre nonTinyCompositeObj: 0xc0000a6000 | |
// | |
// field | type | value | |
// ----- | ---- | ---- | |
// unk | unsafe.Pointer | object c0000a6000 | |
// | |
// It's unclear to me why c0000a6000 isn't being typed correctly in either | |
// case, and especially not why there is a dead nonTinyCompositeObj if the | |
// channel is created in the block below. | |
if escapeStyle1 { | |
var nonTinyCompositeObj2 nonTinyComposite2 | |
nonTinyCompositeObj2.c = 0xBABA | |
defer escape1(&nonTinyCompositeObj2) | |
} | |
if escapeStyle2 { | |
var nonTinyCompositeObj2 nonTinyComposite2 | |
nonTinyCompositeObj2.c = 0xBABA | |
ready := make(chan struct{}) | |
<-ready | |
} | |
pri := func(prefix string) { | |
fmt.Printf("%s tiny lost : %x\n", prefix, elTinyLostBacking) | |
fmt.Printf("%s tiny keep: %x\n", prefix, elTinyKeepBacking) | |
fmt.Printf("%s big lost : %x\n", prefix, elBigLostBacking) | |
fmt.Printf("%s big keep: %x\n", prefix, elBigKeepBacking) | |
fmt.Printf("%s plain uint16: %x\n", prefix, plainUint16) | |
fmt.Printf("%s nonTinyCompositeObj: %p\n", prefix, nonTinyCompositeObj) | |
} | |
runtime.GC() // Force cleanup, likely unnecessary. | |
pri("pre") | |
_ = *(*int)(nil) | |
pri("post") | |
if escapeStyle2 { | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment