github.com/goplus/igop@v0.25.0/runtime.go (about) 1 /* 2 * Copyright (c) 2022 The GoPlus Authors (goplus.org). All rights reserved. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package igop 18 19 import ( 20 "bytes" 21 "fmt" 22 "go/token" 23 "go/types" 24 "os" 25 "path/filepath" 26 "reflect" 27 "regexp" 28 "runtime" 29 "strings" 30 "sync/atomic" 31 "unsafe" 32 33 "github.com/visualfc/funcval" 34 "golang.org/x/tools/go/ssa" 35 ) 36 37 func init() { 38 RegisterExternal("os.Exit", func(fr *frame, code int) { 39 interp := fr.interp 40 if atomic.LoadInt32(&interp.goexited) == 1 { 41 //os.Exit(code) 42 interp.chexit <- code 43 } else { 44 panic(exitPanic(code)) 45 } 46 }) 47 RegisterExternal("runtime.Goexit", func(fr *frame) { 48 interp := fr.interp 49 // main goroutine use panic 50 if goroutineID() == interp.mainid { 51 atomic.StoreInt32(&interp.goexited, 1) 52 panic(goexitPanic(0)) 53 } else { 54 runtime.Goexit() 55 } 56 }) 57 RegisterExternal("runtime.Caller", runtimeCaller) 58 RegisterExternal("runtime.FuncForPC", runtimeFuncForPC) 59 RegisterExternal("runtime.Callers", runtimeCallers) 60 RegisterExternal("(*runtime.Frames).Next", runtimeFramesNext) 61 RegisterExternal("(*runtime.Func).FileLine", runtimeFuncFileLine) 62 RegisterExternal("runtime.Stack", runtimeStack) 63 RegisterExternal("runtime/debug.Stack", debugStack) 64 RegisterExternal("runtime/debug.PrintStack", debugPrintStack) 65 66 if funcval.IsSupport { 67 RegisterExternal("(reflect.Value).Pointer", func(v reflect.Value) uintptr { 68 if v.Kind() == reflect.Func { 69 if fv, n := funcval.Get(v.Interface()); n == 1 { 70 pc := (*makeFuncVal)(unsafe.Pointer(fv)).pfn.base 71 return uintptr(pc) 72 } 73 } 74 return v.Pointer() 75 }) 76 } 77 } 78 79 func runtimeFuncFileLine(fr *frame, f *runtime.Func, pc uintptr) (file string, line int) { 80 entry := f.Entry() 81 if isInlineFunc(f) && pc > entry { 82 interp := fr.interp 83 if pfn := findFuncByEntry(interp, int(entry)); pfn != nil { 84 // pc-1 : fn.instr.pos 85 pos := pfn.PosForPC(int(pc - entry - 1)) 86 if !pos.IsValid() { 87 return "?", 0 88 } 89 fpos := interp.ctx.FileSet.Position(pos) 90 if fpos.Filename == "" { 91 return "??", fpos.Line 92 } 93 file, line = filepath.ToSlash(fpos.Filename), fpos.Line 94 return 95 } 96 } 97 return f.FileLine(pc) 98 } 99 100 func runtimeCaller(fr *frame, skip int) (pc uintptr, file string, line int, ok bool) { 101 if skip < 0 { 102 return runtime.Caller(skip) 103 } 104 rpc := make([]uintptr, 1) 105 n := runtimeCallers(fr, skip+1, rpc[:]) 106 if n < 1 { 107 return 108 } 109 frame, _ := runtimeFramesNext(fr, runtime.CallersFrames(rpc)) 110 return frame.PC, frame.File, frame.Line, frame.PC != 0 111 } 112 113 //go:linkname runtimePanic runtime.gopanic 114 func runtimePanic(e interface{}) 115 116 // runtime.Callers => runtime.CallersFrames 117 // 0 = runtime.Caller 118 // 1 = frame 119 // 2 = frame.caller 120 // ... 121 func runtimeCallers(fr *frame, skip int, pc []uintptr) int { 122 if len(pc) == 0 { 123 return 0 124 } 125 pcs := make([]uintptr, 1) 126 127 // runtime.Caller itself 128 runtime.Callers(0, pcs) 129 pcs[0] -= 1 130 131 caller := fr 132 for caller.valid() { 133 link := caller._panic 134 for link != nil { 135 pcs = append(pcs, uintptr(reflect.ValueOf(runtimePanic).Pointer())) 136 pcs = append(pcs, link.pcs...) 137 link = link.link 138 } 139 pcs = append(pcs, caller.pc()) 140 caller = caller.caller 141 } 142 var rpc []uintptr 143 for _, pc := range pcs { 144 // skip wrapper method func 145 if fn := findFuncByPC(fr.interp, int(pc)); fn != nil && isWrapperFuncName(fn.Fn.String()) { 146 continue 147 } 148 rpc = append(rpc, pc) 149 } 150 if skip < 0 { 151 skip = 0 152 } else if skip > len(rpc)-1 { 153 return 0 154 } 155 return copy(pc, rpc[skip:]) 156 } 157 158 func runtimeFuncForPC(fr *frame, pc uintptr) *runtime.Func { 159 if pfn := findFuncByPC(fr.interp, int(pc)); pfn != nil { 160 return runtimeFunc(pfn) 161 } 162 return runtime.FuncForPC(pc) 163 } 164 165 func findFuncByPC(interp *Interp, pc int) *function { 166 if pc == 0 { 167 return nil 168 } 169 for _, pfn := range interp.funcs { 170 if pc >= pfn.base && pc <= pfn.base+len(pfn.ssaInstrs) { 171 return pfn 172 } 173 } 174 return nil 175 } 176 177 func findFuncByEntry(interp *Interp, entry int) *function { 178 for _, pfn := range interp.funcs { 179 if entry == pfn.base { 180 return pfn 181 } 182 } 183 return nil 184 } 185 186 func isWrapperFuncName(name string) bool { 187 return strings.HasSuffix(name, "$bound") || strings.HasSuffix(name, "$thunk") 188 } 189 190 func runtimeFunc(pfn *function) *runtime.Func { 191 fn := pfn.Fn 192 f := inlineFunc(uintptr(pfn.base)) 193 var autogen bool 194 f.name, autogen = fixedFuncName(pfn.Fn) 195 if autogen { 196 f.file = "<autogenerated>" 197 f.line = 1 198 } else { 199 if pos := fn.Pos(); pos != token.NoPos { 200 fpos := pfn.Interp.ctx.FileSet.Position(pos) 201 f.file = filepath.ToSlash(fpos.Filename) 202 f.line = fpos.Line 203 } 204 } 205 return (*runtime.Func)(unsafe.Pointer(f)) 206 } 207 208 /* 209 type Frames struct { 210 // callers is a slice of PCs that have not yet been expanded to frames. 211 callers []uintptr 212 213 // frames is a slice of Frames that have yet to be returned. 214 frames []Frame 215 frameStore [2]Frame 216 } 217 */ 218 type runtimeFrames struct { 219 callers []uintptr 220 frames []runtime.Frame 221 frameStore [2]runtime.Frame 222 } 223 224 func runtimeFramesNext(fr *frame, frames *runtime.Frames) (frame runtime.Frame, more bool) { 225 ci := (*runtimeFrames)(unsafe.Pointer(frames)) 226 for len(ci.frames) < 2 { 227 // Find the next frame. 228 // We need to look for 2 frames so we know what 229 // to return for the "more" result. 230 if len(ci.callers) == 0 { 231 break 232 } 233 pc := ci.callers[0] 234 ci.callers = ci.callers[1:] 235 f := runtimeFuncForPC(fr, pc) 236 if f == nil { 237 continue 238 } 239 ci.frames = append(ci.frames, runtime.Frame{ 240 PC: pc, 241 Func: f, 242 Function: f.Name(), 243 Entry: f.Entry(), 244 // Note: File,Line set below 245 }) 246 } 247 248 // Pop one frame from the frame list. Keep the rest. 249 // Avoid allocation in the common case, which is 1 or 2 frames. 250 switch len(ci.frames) { 251 case 0: // In the rare case when there are no frames at all, we return Frame{}. 252 return 253 case 1: 254 frame = ci.frames[0] 255 ci.frames = ci.frameStore[:0] 256 case 2: 257 frame = ci.frames[0] 258 ci.frameStore[0] = ci.frames[1] 259 ci.frames = ci.frameStore[:1] 260 default: 261 frame = ci.frames[0] 262 ci.frames = ci.frames[1:] 263 } 264 more = len(ci.frames) > 0 265 if frame.Func != nil { 266 frame.File, frame.Line = runtimeFuncFileLine(fr, frame.Func, frame.PC) 267 } 268 return 269 } 270 271 func extractGoroutine() (string, bool) { 272 buf := make([]byte, 1024) 273 n := runtime.Stack(buf, false) 274 s := string(buf[:n]) 275 if strings.HasPrefix(s, "goroutine") { 276 if pos := strings.Index(s, "\n"); pos != -1 { 277 return s[:pos+1], true 278 } 279 } 280 return "", false 281 } 282 283 func runtimeStack(fr *frame, buf []byte, all bool) int { 284 if len(buf) == 0 { 285 return 0 286 } 287 var w bytes.Buffer 288 if s, ok := extractGoroutine(); ok { 289 w.WriteString(s) 290 } else { 291 w.WriteString("goroutine 1 [running]:\n") 292 } 293 rpc := make([]uintptr, 64) 294 n := runtimeCallers(fr, 1, rpc) 295 fs := runtime.CallersFrames(rpc[:n]) 296 for { 297 f, more := runtimeFramesNext(fr, fs) 298 if f.Function == "runtime.gopanic" { 299 w.WriteString("panic()") 300 } else { 301 w.WriteString(f.Function + "()") 302 } 303 w.WriteByte('\n') 304 w.WriteByte('\t') 305 w.WriteString(fmt.Sprintf("%v:%v", f.File, f.Line)) 306 if f.PC != f.Entry { 307 w.WriteString(fmt.Sprintf(" +0x%x", f.PC-f.Entry)) 308 } 309 w.WriteByte('\n') 310 if !more { 311 break 312 } 313 } 314 return copy(buf, w.Bytes()) 315 } 316 317 // PrintStack prints to standard error the stack trace returned by runtime.Stack. 318 func debugPrintStack(fr *frame) { 319 os.Stderr.Write(debugStack(fr)) 320 } 321 322 // Stack returns a formatted stack trace of the goroutine that calls it. 323 // It calls runtime.Stack with a large enough buffer to capture the entire trace. 324 func debugStack(fr *frame) []byte { 325 buf := make([]byte, 1024) 326 for { 327 n := runtimeStack(fr, buf, false) 328 if n < len(buf) { 329 return buf[:n] 330 } 331 buf = make([]byte, 2*len(buf)) 332 } 333 } 334 335 var ( 336 reFuncName = regexp.MustCompile("\\$(\\d+)") 337 ) 338 339 func fixedFuncName(fn *ssa.Function) (name string, autogen bool) { 340 name = fn.String() 341 name = reFuncName.ReplaceAllString(name, ".func$1") 342 if strings.HasPrefix(name, "(") { 343 if pos := strings.LastIndex(name, ")"); pos != -1 { 344 line := name[1:pos] 345 if strings.HasPrefix(line, "*") { 346 if dot := strings.LastIndex(line, "."); dot != -1 { 347 line = line[1:dot+1] + "(*" + line[dot+1:] + ")" 348 } 349 } 350 name = line + name[pos+1:] 351 } 352 } 353 if strings.HasSuffix(name, "$bound") { 354 return name[:len(name)-6] + "-fm", bound_is_autogen 355 } else if strings.HasSuffix(name, "$thunk") { 356 name = name[:len(name)-6] 357 if strings.HasPrefix(name, "struct{") { 358 return name, true 359 } 360 if sig, ok := fn.Type().(*types.Signature); ok { 361 if types.IsInterface(sig.Params().At(0).Type()) { 362 return name, true 363 } 364 } 365 } 366 return name, false 367 } 368 369 func runtimeGC(fr *frame) { 370 for fr.valid() { 371 fr.gc() 372 fr = fr.caller 373 } 374 runtime.GC() 375 }