github.com/twelsh-aw/go/src@v0.0.0-20230516233729-a56fe86a7c81/runtime/cgocheck.go (about) 1 // Copyright 2015 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // Code to check that pointer writes follow the cgo rules. 6 // These functions are invoked when GOEXPERIMENT=cgocheck2 is enabled. 7 8 package runtime 9 10 import ( 11 "internal/goarch" 12 "unsafe" 13 ) 14 15 const cgoWriteBarrierFail = "Go pointer stored into non-Go memory" 16 17 // cgoCheckPtrWrite is called whenever a pointer is stored into memory. 18 // It throws if the program is storing a Go pointer into non-Go memory. 19 // 20 // This is called from generated code when GOEXPERIMENT=cgocheck2 is enabled. 21 // 22 //go:nosplit 23 //go:nowritebarrier 24 func cgoCheckPtrWrite(dst *unsafe.Pointer, src unsafe.Pointer) { 25 if !mainStarted { 26 // Something early in startup hates this function. 27 // Don't start doing any actual checking until the 28 // runtime has set itself up. 29 return 30 } 31 if !cgoIsGoPointer(src) { 32 return 33 } 34 if cgoIsGoPointer(unsafe.Pointer(dst)) { 35 return 36 } 37 38 // If we are running on the system stack then dst might be an 39 // address on the stack, which is OK. 40 gp := getg() 41 if gp == gp.m.g0 || gp == gp.m.gsignal { 42 return 43 } 44 45 // Allocating memory can write to various mfixalloc structs 46 // that look like they are non-Go memory. 47 if gp.m.mallocing != 0 { 48 return 49 } 50 51 // It's OK if writing to memory allocated by persistentalloc. 52 // Do this check last because it is more expensive and rarely true. 53 // If it is false the expense doesn't matter since we are crashing. 54 if inPersistentAlloc(uintptr(unsafe.Pointer(dst))) { 55 return 56 } 57 58 systemstack(func() { 59 println("write of Go pointer", hex(uintptr(src)), "to non-Go memory", hex(uintptr(unsafe.Pointer(dst)))) 60 throw(cgoWriteBarrierFail) 61 }) 62 } 63 64 // cgoCheckMemmove is called when moving a block of memory. 65 // It throws if the program is copying a block that contains a Go pointer 66 // into non-Go memory. 67 // 68 // This is called from generated code when GOEXPERIMENT=cgocheck2 is enabled. 69 // 70 //go:nosplit 71 //go:nowritebarrier 72 func cgoCheckMemmove(typ *_type, dst, src unsafe.Pointer) { 73 cgoCheckMemmove2(typ, dst, src, 0, typ.Size_) 74 } 75 76 // cgoCheckMemmove2 is called when moving a block of memory. 77 // dst and src point off bytes into the value to copy. 78 // size is the number of bytes to copy. 79 // It throws if the program is copying a block that contains a Go pointer 80 // into non-Go memory. 81 // 82 //go:nosplit 83 //go:nowritebarrier 84 func cgoCheckMemmove2(typ *_type, dst, src unsafe.Pointer, off, size uintptr) { 85 if typ.PtrBytes == 0 { 86 return 87 } 88 if !cgoIsGoPointer(src) { 89 return 90 } 91 if cgoIsGoPointer(dst) { 92 return 93 } 94 cgoCheckTypedBlock(typ, src, off, size) 95 } 96 97 // cgoCheckSliceCopy is called when copying n elements of a slice. 98 // src and dst are pointers to the first element of the slice. 99 // typ is the element type of the slice. 100 // It throws if the program is copying slice elements that contain Go pointers 101 // into non-Go memory. 102 // 103 //go:nosplit 104 //go:nowritebarrier 105 func cgoCheckSliceCopy(typ *_type, dst, src unsafe.Pointer, n int) { 106 if typ.PtrBytes == 0 { 107 return 108 } 109 if !cgoIsGoPointer(src) { 110 return 111 } 112 if cgoIsGoPointer(dst) { 113 return 114 } 115 p := src 116 for i := 0; i < n; i++ { 117 cgoCheckTypedBlock(typ, p, 0, typ.Size_) 118 p = add(p, typ.Size_) 119 } 120 } 121 122 // cgoCheckTypedBlock checks the block of memory at src, for up to size bytes, 123 // and throws if it finds a Go pointer. The type of the memory is typ, 124 // and src is off bytes into that type. 125 // 126 //go:nosplit 127 //go:nowritebarrier 128 func cgoCheckTypedBlock(typ *_type, src unsafe.Pointer, off, size uintptr) { 129 // Anything past typ.PtrBytes is not a pointer. 130 if typ.PtrBytes <= off { 131 return 132 } 133 if ptrdataSize := typ.PtrBytes - off; size > ptrdataSize { 134 size = ptrdataSize 135 } 136 137 if typ.Kind_&kindGCProg == 0 { 138 cgoCheckBits(src, typ.GCData, off, size) 139 return 140 } 141 142 // The type has a GC program. Try to find GC bits somewhere else. 143 for _, datap := range activeModules() { 144 if cgoInRange(src, datap.data, datap.edata) { 145 doff := uintptr(src) - datap.data 146 cgoCheckBits(add(src, -doff), datap.gcdatamask.bytedata, off+doff, size) 147 return 148 } 149 if cgoInRange(src, datap.bss, datap.ebss) { 150 boff := uintptr(src) - datap.bss 151 cgoCheckBits(add(src, -boff), datap.gcbssmask.bytedata, off+boff, size) 152 return 153 } 154 } 155 156 s := spanOfUnchecked(uintptr(src)) 157 if s.state.get() == mSpanManual { 158 // There are no heap bits for value stored on the stack. 159 // For a channel receive src might be on the stack of some 160 // other goroutine, so we can't unwind the stack even if 161 // we wanted to. 162 // We can't expand the GC program without extra storage 163 // space we can't easily get. 164 // Fortunately we have the type information. 165 systemstack(func() { 166 cgoCheckUsingType(typ, src, off, size) 167 }) 168 return 169 } 170 171 // src must be in the regular heap. 172 173 hbits := heapBitsForAddr(uintptr(src), size) 174 for { 175 var addr uintptr 176 if hbits, addr = hbits.next(); addr == 0 { 177 break 178 } 179 v := *(*unsafe.Pointer)(unsafe.Pointer(addr)) 180 if cgoIsGoPointer(v) { 181 throw(cgoWriteBarrierFail) 182 } 183 } 184 } 185 186 // cgoCheckBits checks the block of memory at src, for up to size 187 // bytes, and throws if it finds a Go pointer. The gcbits mark each 188 // pointer value. The src pointer is off bytes into the gcbits. 189 // 190 //go:nosplit 191 //go:nowritebarrier 192 func cgoCheckBits(src unsafe.Pointer, gcbits *byte, off, size uintptr) { 193 skipMask := off / goarch.PtrSize / 8 194 skipBytes := skipMask * goarch.PtrSize * 8 195 ptrmask := addb(gcbits, skipMask) 196 src = add(src, skipBytes) 197 off -= skipBytes 198 size += off 199 var bits uint32 200 for i := uintptr(0); i < size; i += goarch.PtrSize { 201 if i&(goarch.PtrSize*8-1) == 0 { 202 bits = uint32(*ptrmask) 203 ptrmask = addb(ptrmask, 1) 204 } else { 205 bits >>= 1 206 } 207 if off > 0 { 208 off -= goarch.PtrSize 209 } else { 210 if bits&1 != 0 { 211 v := *(*unsafe.Pointer)(add(src, i)) 212 if cgoIsGoPointer(v) { 213 throw(cgoWriteBarrierFail) 214 } 215 } 216 } 217 } 218 } 219 220 // cgoCheckUsingType is like cgoCheckTypedBlock, but is a last ditch 221 // fall back to look for pointers in src using the type information. 222 // We only use this when looking at a value on the stack when the type 223 // uses a GC program, because otherwise it's more efficient to use the 224 // GC bits. This is called on the system stack. 225 // 226 //go:nowritebarrier 227 //go:systemstack 228 func cgoCheckUsingType(typ *_type, src unsafe.Pointer, off, size uintptr) { 229 if typ.PtrBytes == 0 { 230 return 231 } 232 233 // Anything past typ.PtrBytes is not a pointer. 234 if typ.PtrBytes <= off { 235 return 236 } 237 if ptrdataSize := typ.PtrBytes - off; size > ptrdataSize { 238 size = ptrdataSize 239 } 240 241 if typ.Kind_&kindGCProg == 0 { 242 cgoCheckBits(src, typ.GCData, off, size) 243 return 244 } 245 switch typ.Kind_ & kindMask { 246 default: 247 throw("can't happen") 248 case kindArray: 249 at := (*arraytype)(unsafe.Pointer(typ)) 250 for i := uintptr(0); i < at.Len; i++ { 251 if off < at.Elem.Size_ { 252 cgoCheckUsingType(at.Elem, src, off, size) 253 } 254 src = add(src, at.Elem.Size_) 255 skipped := off 256 if skipped > at.Elem.Size_ { 257 skipped = at.Elem.Size_ 258 } 259 checked := at.Elem.Size_ - skipped 260 off -= skipped 261 if size <= checked { 262 return 263 } 264 size -= checked 265 } 266 case kindStruct: 267 st := (*structtype)(unsafe.Pointer(typ)) 268 for _, f := range st.Fields { 269 if off < f.Typ.Size_ { 270 cgoCheckUsingType(f.Typ, src, off, size) 271 } 272 src = add(src, f.Typ.Size_) 273 skipped := off 274 if skipped > f.Typ.Size_ { 275 skipped = f.Typ.Size_ 276 } 277 checked := f.Typ.Size_ - skipped 278 off -= skipped 279 if size <= checked { 280 return 281 } 282 size -= checked 283 } 284 } 285 }