├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── cmd └── viewcore │ ├── html.go │ └── main.go ├── codereview.cfg ├── dwtest ├── dwloc_test.go ├── dwtest.go └── testdata │ ├── dwdumploc.go │ ├── go.mod.txt │ └── go.sum.txt ├── go.mod ├── go.sum ├── internal ├── core │ ├── address.go │ ├── core_test.go │ ├── mapping.go │ ├── mapping_test.go │ ├── process.go │ ├── process_unix.go │ ├── read.go │ ├── testdata │ │ ├── README │ │ ├── core │ │ └── tmp │ │ │ └── test │ └── thread.go ├── gocore │ ├── dominator.go │ ├── dominator_test.go │ ├── dwarf.go │ ├── gocore_test.go │ ├── goroutine.go │ ├── module.go │ ├── object.go │ ├── process.go │ ├── region.go │ ├── reverse.go │ ├── root.go │ ├── testdata │ │ └── testprogs │ │ │ ├── bigslice.go │ │ │ ├── large.go │ │ │ └── trees.go │ └── type.go └── testenv │ ├── crash.go │ └── testenv.go └── third_party └── delve ├── LICENSE ├── README.md └── dwarf ├── godwarf └── addr.go ├── leb128 ├── decode.go ├── decode_test.go ├── doc.go ├── encode.go └── encode_test.go ├── loclist ├── dwarf2_loclist.go ├── dwarf5_loclist.go └── dwarf5_loclist_additions.go ├── op ├── op.go ├── op_test.go ├── opcodes.go ├── opcodes.table └── regs.go ├── parseutil.go └── regnum ├── amd64.go ├── arm64.go ├── i386.go ├── ppc64le.go └── riscv64.go /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Go 2 | 3 | Go is an open source project. 4 | 5 | It is the work of hundreds of contributors. We appreciate your help! 6 | 7 | ## Filing issues 8 | 9 | When [filing an issue](https://golang.org/issue/new), make sure to answer these five questions: 10 | 11 | 1. What version of Go are you using (`go version`)? 12 | 2. What operating system and processor architecture are you using? 13 | 3. What did you do? 14 | 4. What did you expect to see? 15 | 5. What did you see instead? 16 | 17 | General questions should go to the [golang-nuts mailing list](https://groups.google.com/group/golang-nuts) instead of the issue tracker. 18 | The gophers there will answer or ask you to file an issue if you've tripped over a bug. 19 | 20 | ## Contributing code 21 | 22 | Please read the [Contribution Guidelines](https://golang.org/doc/contribute.html) 23 | before sending patches. 24 | 25 | Unless otherwise noted, the Go source files are distributed under 26 | the BSD-style license found in the LICENSE file. 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2014 The Go Authors. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Google LLC nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Go Debug 2 | 3 | [![Go Reference](https://pkg.go.dev/badge/golang.org/x/debug.svg)](https://pkg.go.dev/golang.org/x/debug) 4 | 5 | This repository holds utilities and libraries for debugging Go programs. 6 | 7 | **WARNING!** 8 | Please expect breaking changes and unstable APIs. 9 | Most of them are currently are at an early, *experimental* stage. 10 | 11 | ## Report Issues / Send Patches 12 | 13 | This repository uses Gerrit for code changes. To learn how to submit changes to 14 | this repository, see https://go.dev/doc/contribute. 15 | 16 | The git repository is https://go.googlesource.com/debug. 17 | 18 | The main issue tracker for the debug repository is located at 19 | https://go.dev/issues. Prefix your issue with "x/debug:" in the 20 | subject line, so it is easy to find. 21 | -------------------------------------------------------------------------------- /cmd/viewcore/html.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 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 | //go:build !aix && !plan9 && !wasm 6 | 7 | // (go.dev/issue/32839) 8 | 9 | package main 10 | 11 | import ( 12 | "fmt" 13 | "html" 14 | "math" 15 | "net/http" 16 | "strconv" 17 | 18 | "golang.org/x/debug/internal/core" 19 | "golang.org/x/debug/internal/gocore" 20 | ) 21 | 22 | // serveHTML starts and serves a webserver on the port. 23 | // If async is true, it returns immediately after starting the server. 24 | func serveHTML(c *gocore.Process, port int, async bool) { 25 | http.HandleFunc("/object", func(w http.ResponseWriter, r *http.Request) { 26 | objs, ok := r.URL.Query()["o"] 27 | if !ok || len(objs) != 1 { 28 | fmt.Fprintf(w, "wrong or missing o= object specification") 29 | return 30 | } 31 | obj, err := strconv.ParseInt(objs[0], 16, 64) 32 | if err != nil { 33 | fmt.Fprintf(w, "unparseable o= object specification: %s", err) 34 | return 35 | } 36 | a := core.Address(obj) 37 | x, _ := c.FindObject(a) 38 | if x == 0 { 39 | fmt.Fprintf(w, "can't find object at %x", a) 40 | return 41 | } 42 | addr := c.Addr(x) 43 | size := c.Size(x) 44 | typ, repeat := c.Type(x) 45 | 46 | tableStyle(w) 47 | fmt.Fprintf(w, "

object %x

\n", a) 48 | fmt.Fprintf(w, "

%s

\n", html.EscapeString(typeName(c, x))) 49 | fmt.Fprintf(w, "

%d bytes

\n", size) 50 | 51 | if typ != nil && repeat == 1 && typ.String() == "runtime.g" { 52 | found := false 53 | for _, g := range c.Goroutines() { 54 | if g.Addr() == addr { 55 | found = true 56 | break 57 | } 58 | } 59 | if found { 60 | fmt.Fprintf(w, "

goroutine stack

\n", addr) 61 | } 62 | } 63 | 64 | fmt.Fprintf(w, "\n") 65 | fmt.Fprintf(w, "\n") 66 | var end int64 67 | if typ != nil { 68 | n := size / typ.Size 69 | if n > 1 { 70 | for i := int64(0); i < n; i++ { 71 | htmlObject(w, c, fmt.Sprintf("[%d]", i), addr.Add(i*typ.Size), typ, nil) 72 | } 73 | } else { 74 | htmlObject(w, c, "", addr, typ, nil) 75 | } 76 | end = n * typ.Size 77 | } 78 | for i := end; i < size; i += c.Process().PtrSize() { 79 | fmt.Fprintf(w, "", i) 80 | if c.IsPtr(addr.Add(i)) { 81 | fmt.Fprintf(w, "", htmlPointer(c, c.Process().ReadPtr(addr.Add(i)))) 82 | } else { 83 | fmt.Fprintf(w, "") 97 | } 98 | fmt.Fprintf(w, "\n") 99 | } 100 | fmt.Fprintf(w, "
fieldtypevalue
f%d?%s
")
 84 | 				for j := int64(0); j < c.Process().PtrSize(); j++ {
 85 | 					fmt.Fprintf(w, "%02x ", c.Process().ReadUint8(addr.Add(i+j)))
 86 | 				}
 87 | 				fmt.Fprintf(w, "
")
 88 | 				for j := int64(0); j < c.Process().PtrSize(); j++ {
 89 | 					r := c.Process().ReadUint8(addr.Add(i + j))
 90 | 					if r >= 32 && r <= 126 {
 91 | 						fmt.Fprintf(w, "%s", html.EscapeString(string(rune(r))))
 92 | 					} else {
 93 | 						fmt.Fprintf(w, ".")
 94 | 					}
 95 | 				}
 96 | 				fmt.Fprintf(w, "
\n") 101 | fmt.Fprintf(w, "

references to this object

\n") 102 | nrev := 0 103 | c.ForEachReversePtr(x, func(z gocore.Object, r *gocore.Root, i, j int64) bool { 104 | if nrev == 10 { 105 | fmt.Fprintf(w, "...additional references elided...
\n") 106 | return false 107 | } 108 | if r != nil { 109 | fmt.Fprintf(w, "%s%s", r.Name, typeFieldName(r.Type, i)) 110 | } else { 111 | t, r := c.Type(z) 112 | if t == nil { 113 | fmt.Fprintf(w, "%s", htmlPointer(c, c.Addr(z).Add(i))) 114 | } else { 115 | idx := "" 116 | if r > 1 { 117 | idx = fmt.Sprintf("[%d]", i/t.Size) 118 | i %= t.Size 119 | } 120 | fmt.Fprintf(w, "%s%s%s", htmlPointer(c, c.Addr(z)), idx, typeFieldName(t, i)) 121 | } 122 | } 123 | fmt.Fprintf(w, " → %s
\n", htmlPointer(c, a.Add(j))) 124 | nrev++ 125 | return true 126 | }) 127 | }) 128 | http.HandleFunc("/goroutines", func(w http.ResponseWriter, r *http.Request) { 129 | fmt.Fprintf(w, "

goroutines

\n") 130 | tableStyle(w) 131 | fmt.Fprintf(w, "\n") 132 | fmt.Fprintf(w, "\n") 133 | for _, g := range c.Goroutines() { 134 | fmt.Fprintf(w, "\n", g.Addr(), g.Addr(), g.Frames()[0].Func().Name()) 135 | } 136 | fmt.Fprintf(w, "
goroutinetop of stack
%x%s
\n") 137 | // TODO: export goroutine state (runnable, running, syscall, ...) and print it here. 138 | }) 139 | http.HandleFunc("/goroutine", func(w http.ResponseWriter, r *http.Request) { 140 | gs, ok := r.URL.Query()["g"] 141 | if !ok || len(gs) != 1 { 142 | fmt.Fprintf(w, "wrong or missing g= goroutine specification") 143 | return 144 | } 145 | addr, err := strconv.ParseInt(gs[0], 16, 64) 146 | if err != nil { 147 | fmt.Fprintf(w, "unparseable g= goroutine specification: %s\n", err) 148 | return 149 | } 150 | a := core.Address(addr) 151 | var g *gocore.Goroutine 152 | for _, x := range c.Goroutines() { 153 | if x.Addr() == a { 154 | g = x 155 | break 156 | } 157 | } 158 | if g == nil { 159 | fmt.Fprintf(w, "goroutine %x not found\n", a) 160 | return 161 | } 162 | 163 | tableStyle(w) 164 | fmt.Fprintf(w, "

goroutine %x

\n", g.Addr()) 165 | fmt.Fprintf(w, "

%s

\n", htmlPointer(c, g.Addr())) 166 | fmt.Fprintf(w, "

%d bytes of stack

\n", g.Stack()) 167 | for _, f := range g.Frames() { 168 | fmt.Fprintf(w, "

%s+%d

\n", f.Func().Name(), f.PC().Sub(f.Func().Entry())) 169 | // TODO: convert fn+off to file+lineno. 170 | fmt.Fprintf(w, "\n") 171 | fmt.Fprintf(w, "\n") 172 | for _, r := range f.Roots() { 173 | if r.HasAddress() { 174 | htmlObject(w, c, r.Name, r.Addr(), r.Type, f.Live) 175 | } 176 | } 177 | fmt.Fprintf(w, "
fieldtypevalue
\n") 178 | } 179 | }) 180 | http.HandleFunc("/globals", func(w http.ResponseWriter, r *http.Request) { 181 | fmt.Fprintf(w, "

globals

\n") 182 | tableStyle(w) 183 | fmt.Fprintf(w, "\n") 184 | fmt.Fprintf(w, "\n") 185 | for _, r := range c.Globals() { 186 | if r.HasAddress() { 187 | htmlObject(w, c, r.Name, r.Addr(), r.Type, nil) 188 | } 189 | } 190 | fmt.Fprintf(w, "
fieldtypevalue
\n") 191 | }) 192 | http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 193 | fmt.Fprintf(w, "

core dump viewer

\n") 194 | fmt.Fprintf(w, "%s
\n", c.Process().Arch()) 195 | fmt.Fprintf(w, "%s
\n", c.BuildVersion()) 196 | fmt.Fprintf(w, "goroutines
\n") 197 | fmt.Fprintf(w, "globals
\n") 198 | tableStyle(w) 199 | fmt.Fprintf(w, "\n") 200 | fmt.Fprintf(w, "\n") 201 | all := c.Stats().Value 202 | var p func(*gocore.Statistic, string) 203 | p = func(s *gocore.Statistic, prefix string) { 204 | fmt.Fprintf(w, "\n", prefix, s.Name, s.Value, float64(s.Value)/float64(all)*100) 205 | for c := range s.Children() { 206 | p(c, prefix+"..") 207 | } 208 | } 209 | p(c.Stats(), "") 210 | fmt.Fprintf(w, "
categorybytespercent
%s%s%d%.2f
\n") 211 | }) 212 | 213 | if port <= 0 { 214 | port = 8080 215 | } 216 | fmt.Printf("start serving on http://localhost:%d\n", port) 217 | 218 | httpAddr := fmt.Sprintf(":%d", port) 219 | if async { 220 | go http.ListenAndServe(httpAddr, nil) 221 | return 222 | } 223 | http.ListenAndServe(httpAddr, nil) 224 | } 225 | 226 | func htmlObject(w http.ResponseWriter, c *gocore.Process, name string, a core.Address, t *gocore.Type, live map[core.Address]bool) { 227 | switch t.Kind { 228 | case gocore.KindBool: 229 | v := c.Process().ReadUint8(a) != 0 230 | fmt.Fprintf(w, "%s%s%t\n", name, html.EscapeString(t.String()), v) 231 | case gocore.KindInt: 232 | var v int64 233 | switch t.Size { 234 | case 1: 235 | v = int64(c.Process().ReadInt8(a)) 236 | case 2: 237 | v = int64(c.Process().ReadInt16(a)) 238 | case 4: 239 | v = int64(c.Process().ReadInt32(a)) 240 | case 8: 241 | v = c.Process().ReadInt64(a) 242 | } 243 | fmt.Fprintf(w, "%s%s%d\n", name, html.EscapeString(t.String()), v) 244 | case gocore.KindUint: 245 | var v uint64 246 | switch t.Size { 247 | case 1: 248 | v = uint64(c.Process().ReadUint8(a)) 249 | case 2: 250 | v = uint64(c.Process().ReadUint16(a)) 251 | case 4: 252 | v = uint64(c.Process().ReadUint32(a)) 253 | case 8: 254 | v = c.Process().ReadUint64(a) 255 | } 256 | fmt.Fprintf(w, "%s%s%d\n", name, html.EscapeString(t.String()), v) 257 | case gocore.KindFloat: 258 | var v float64 259 | switch t.Size { 260 | case 4: 261 | v = float64(math.Float32frombits(c.Process().ReadUint32(a))) 262 | case 8: 263 | v = math.Float64frombits(c.Process().ReadUint64(a)) 264 | } 265 | fmt.Fprintf(w, "%s%s%f\n", name, html.EscapeString(t.String()), v) 266 | case gocore.KindComplex: 267 | var v complex128 268 | switch t.Size { 269 | case 8: 270 | v = complex128(complex( 271 | math.Float32frombits(c.Process().ReadUint32(a)), 272 | math.Float32frombits(c.Process().ReadUint32(a.Add(4))))) 273 | 274 | case 16: 275 | v = complex( 276 | math.Float64frombits(c.Process().ReadUint64(a)), 277 | math.Float64frombits(c.Process().ReadUint64(a.Add(8)))) 278 | } 279 | fmt.Fprintf(w, "%s%s%f\n", name, html.EscapeString(t.String()), v) 280 | case gocore.KindEface: 281 | fmt.Fprintf(w, "%sinterface{}*runtime._type%s", name, htmlPointerAt(c, a, live)) 282 | if live == nil || live[a] { 283 | dt := c.DynamicType(t, a) 284 | if dt != nil { 285 | fmt.Fprintf(w, "%s", dt.Name) 286 | } 287 | } 288 | fmt.Fprintf(w, "\n") 289 | fmt.Fprintf(w, "unsafe.Pointer%s\n", htmlPointerAt(c, a.Add(c.Process().PtrSize()), live)) 290 | case gocore.KindIface: 291 | fmt.Fprintf(w, "%sinterface{...}*runtime.itab%s", name, htmlPointerAt(c, a, live)) 292 | if live == nil || live[a] { 293 | dt := c.DynamicType(t, a) 294 | if dt != nil { 295 | fmt.Fprintf(w, "%s", dt.Name) 296 | } 297 | } 298 | fmt.Fprintf(w, "\n") 299 | fmt.Fprintf(w, "unsafe.Pointer%s\n", htmlPointerAt(c, a.Add(c.Process().PtrSize()), live)) 300 | case gocore.KindPtr: 301 | fmt.Fprintf(w, "%s%s%s\n", name, html.EscapeString(t.String()), htmlPointerAt(c, a, live)) 302 | case gocore.KindFunc: 303 | fmt.Fprintf(w, "%s%s%s", name, html.EscapeString(t.String()), htmlPointerAt(c, a, live)) 304 | if fn := c.Process().ReadPtr(a); fn != 0 { 305 | pc := c.Process().ReadPtr(fn) 306 | if f := c.FindFunc(pc); f != nil && f.Entry() == pc { 307 | fmt.Fprintf(w, "%s", f.Name()) 308 | } 309 | } 310 | fmt.Fprintf(w, "\n") 311 | case gocore.KindString: 312 | n := c.Process().ReadInt(a.Add(c.Process().PtrSize())) 313 | fmt.Fprintf(w, "%sstring*uint8%s", name, htmlPointerAt(c, a, live)) 314 | if live == nil || live[a] { 315 | if n > 0 { 316 | n2 := n 317 | ddd := "" 318 | if n > 100 { 319 | n2 = 100 320 | ddd = "..." 321 | } 322 | b := make([]byte, n2) 323 | c.Process().ReadAt(b, c.Process().ReadPtr(a)) 324 | fmt.Fprintf(w, "\"%s\"%s", html.EscapeString(string(b)), ddd) 325 | } else { 326 | fmt.Fprintf(w, "\"\"") 327 | } 328 | } 329 | fmt.Fprintf(w, "\n") 330 | fmt.Fprintf(w, "int%d\n", n) 331 | case gocore.KindSlice: 332 | fmt.Fprintf(w, "%s%s*%s%s\n", name, t, t.Elem, htmlPointerAt(c, a, live)) 333 | fmt.Fprintf(w, "int%d\n", c.Process().ReadInt(a.Add(c.Process().PtrSize()))) 334 | fmt.Fprintf(w, "int%d\n", c.Process().ReadInt(a.Add(c.Process().PtrSize()*2))) 335 | case gocore.KindArray: 336 | s := t.Elem.Size 337 | n := t.Count 338 | if n*s > 16384 { 339 | n = (16384 + s - 1) / s 340 | } 341 | for i := int64(0); i < n; i++ { 342 | htmlObject(w, c, fmt.Sprintf("%s[%d]", name, i), a.Add(i*s), t.Elem, live) 343 | } 344 | if n*s != t.Size { 345 | fmt.Fprintf(w, ".........\n") 346 | } 347 | case gocore.KindStruct: 348 | for _, f := range t.Fields { 349 | htmlObject(w, c, name+"."+f.Name, a.Add(f.Off), f.Type, live) 350 | } 351 | } 352 | } 353 | 354 | func htmlPointer(c *gocore.Process, a core.Address) string { 355 | if a == 0 { 356 | return "nil" 357 | } 358 | x, i := c.FindObject(a) 359 | if x == 0 { 360 | return fmt.Sprintf("%x", a) 361 | } 362 | s := fmt.Sprintf("object %x", c.Addr(x), c.Addr(x)) 363 | if i == 0 { 364 | return s 365 | } 366 | t, r := c.Type(x) 367 | if t == nil || i >= r*t.Size { 368 | return fmt.Sprintf("%s+%d", s, i) 369 | } 370 | idx := "" 371 | if r > 1 { 372 | idx = fmt.Sprintf("[%d]", i/t.Size) 373 | i %= t.Size 374 | } 375 | return fmt.Sprintf("%s%s%s", s, idx, typeFieldName(t, i)) 376 | } 377 | 378 | func htmlPointerAt(c *gocore.Process, a core.Address, live map[core.Address]bool) string { 379 | if live != nil && !live[a] { 380 | return "dead" 381 | } 382 | return htmlPointer(c, c.Process().ReadPtr(a)) 383 | } 384 | 385 | func tableStyle(w http.ResponseWriter) { 386 | fmt.Fprintf(w, "\n") 397 | 398 | } 399 | 400 | // Returns the name of the field at offset off in x. 401 | func objField(c *gocore.Process, x gocore.Object, off int64) string { 402 | t, r := c.Type(x) 403 | if t == nil { 404 | return fmt.Sprintf("f%d", off) 405 | } 406 | s := "" 407 | if r > 1 { 408 | s = fmt.Sprintf("[%d]", off/t.Size) 409 | off %= t.Size 410 | } 411 | return s + typeFieldName(t, off) 412 | } 413 | 414 | func objRegion(c *gocore.Process, x gocore.Object, off int64) string { 415 | t, r := c.Type(x) 416 | if t == nil { 417 | return fmt.Sprintf("f%d", off) 418 | } 419 | if off == 0 { 420 | return "" 421 | } 422 | s := "" 423 | if r > 1 { 424 | s = fmt.Sprintf("[%d]", off/t.Size) 425 | off %= t.Size 426 | } 427 | return s + typeFieldName(t, off) 428 | } 429 | -------------------------------------------------------------------------------- /codereview.cfg: -------------------------------------------------------------------------------- 1 | issuerepo: golang/go 2 | -------------------------------------------------------------------------------- /dwtest/dwloc_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 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 | package dwtest_test 6 | 7 | // This file contains a set of DWARF variable location generation 8 | // tests that are intended to compliment the existing linker DWARF 9 | // tests. The tests make use of a harness / utility program 10 | // "dwdumploc" that is built during test setup and then 11 | // invoked (fork+exec) in testpoints. We do things this way (as 12 | // opposed to just incorporating all of the source code from 13 | // testdata/dwdumploc.go into this file) so that the dumper code can 14 | // import packages from Delve without needing to vendor everything 15 | // into the Go distribution itself. 16 | // 17 | // Notes on GOARCH/GOOS support: this test is guarded to execute only 18 | // on arch/os combinations supported by Delve (see the testpoint 19 | // below); as Delve evolves we may need to update accordingly. 20 | // 21 | // This test requires network support (the harness build has to 22 | // download packages), so only runs in "long" test mode at the moment, 23 | // and since we don't currently have longtest builders for every 24 | // arch/os pair that Delve supports (ex: no linux/arm64 longtest 25 | // builder, Issue #49649), this is something to keep in mind when 26 | // running trybots etc. 27 | // 28 | 29 | import ( 30 | "bytes" 31 | "flag" 32 | "fmt" 33 | "os" 34 | "os/exec" 35 | "path/filepath" 36 | "runtime" 37 | "strings" 38 | "testing" 39 | 40 | "golang.org/x/debug/internal/testenv" 41 | ) 42 | 43 | var preserveTemp = flag.Bool("keep", false, "keep tmpdir files for debugging") 44 | 45 | // copyFilesForHarness copies various files into the build dir for the 46 | // harness, including the main package, go.mod, and a copy of the 47 | // dwtest package (the latter is why we are doing an explicit copy as 48 | // opposed to just building directly from sources in testdata). 49 | // Return value is the path to a build directory for the harness 50 | // build. 51 | func copyFilesForHarness(t *testing.T, dir string) string { 52 | mkdir := func(d string) { 53 | if err := os.Mkdir(d, 0777); err != nil { 54 | t.Fatalf("mkdir failed: %v", err) 55 | } 56 | } 57 | cp := func(from, to string) { 58 | var payload []byte 59 | payload, err := os.ReadFile(from) 60 | if err != nil { 61 | t.Fatalf("os.ReadFile failed: %v", err) 62 | } 63 | if err = os.WriteFile(to, payload, 0644); err != nil { 64 | t.Fatalf("os.WriteFile failed: %v", err) 65 | } 66 | } 67 | join := filepath.Join 68 | bd := join(dir, "build") 69 | bdt := join(bd, "dwtest") 70 | mkdir(bd) 71 | mkdir(bdt) 72 | cp(join("testdata", "dwdumploc.go"), join(bd, "main.go")) 73 | cp(join("testdata", "go.mod.txt"), join(bd, "go.mod")) 74 | cp(join("testdata", "go.sum.txt"), join(bd, "go.sum")) 75 | cp("dwtest.go", join(bdt, "dwtest.go")) 76 | return bd 77 | } 78 | 79 | // buildHarness builds the helper program "dwdumploc.exe" 80 | // and a companion executable "dwdumploc.noopt.exe", built 81 | // with "-gcflags=all=-l -N". 82 | func buildHarness(t *testing.T, dir string) (string, string) { 83 | 84 | // Copy source files into build dir. 85 | bd := copyFilesForHarness(t, dir) 86 | 87 | // Run builds. 88 | harnessPath := filepath.Join(dir, "dumpdwloc.exe") 89 | cmd := exec.Command(testenv.GoToolPath(t), "build", "-trimpath", "-o", harnessPath) 90 | cmd.Dir = bd 91 | if b, err := cmd.CombinedOutput(); err != nil { 92 | t.Fatalf("build failed (%v): %s", err, b) 93 | } 94 | 95 | nooptHarnessPath := filepath.Join(dir, "dumpdwloc.exe") 96 | cmd = exec.Command(testenv.GoToolPath(t), "build", "-trimpath", "-gcflags=all=-l -N", "-o", nooptHarnessPath) 97 | cmd.Dir = bd 98 | if b, err := cmd.CombinedOutput(); err != nil { 99 | t.Fatalf("build failed (%v): %s", err, b) 100 | } 101 | return harnessPath, nooptHarnessPath 102 | } 103 | 104 | // runHarness runs our previously built harness exec on a Go binary 105 | // 'exePath' for function 'fcn' and returns the results. Stderr from 106 | // the harness is printed to test stderr. Note: to debug the harness, 107 | // try adding "-v=2" to the exec.Command below. 108 | func runHarness(t *testing.T, harnessPath string, exePath string, fcn string) string { 109 | cmd := exec.Command(harnessPath, "-m", exePath, "-f", fcn) 110 | var b bytes.Buffer 111 | cmd.Stderr = os.Stderr 112 | cmd.Stdout = &b 113 | if err := cmd.Run(); err != nil { 114 | t.Fatalf("running 'harness -m %s -f %s': %v", exePath, fcn, err) 115 | } 116 | return strings.TrimSpace(string(b.Bytes())) 117 | } 118 | 119 | // gobuild is a helper to build a Go program from source code, 120 | // so that we can inspect selected bits of DWARF in the resulting binary. 121 | // The first return value is the path to the binary compiled with optimizations, 122 | // the second is the path to the binary compiled without optimizations. 123 | func gobuild(t *testing.T, sourceCode string, pname string, dir string) (string, string) { 124 | spath := filepath.Join(dir, pname+".go") 125 | if err := os.WriteFile(spath, []byte(sourceCode), 0644); err != nil { 126 | t.Fatalf("write to %s failed: %s", spath, err) 127 | } 128 | epath := filepath.Join(dir, pname+".exe") 129 | nooppath := filepath.Join(dir, pname+".noop.exe") 130 | 131 | // A note on this build: Delve currently has problems digesting 132 | // PIE binaries on Windows; until this can be straightened out, 133 | // default to "exe" buildmode. 134 | cmd := exec.Command(testenv.GoToolPath(t), "build", "-trimpath", "-buildmode=exe", "-o", epath, spath) 135 | if b, err := cmd.CombinedOutput(); err != nil { 136 | t.Logf("%% build output: %s\n", b) 137 | t.Fatalf("build failed: %s", err) 138 | } 139 | cmd = exec.Command(testenv.GoToolPath(t), "build", "-gcflags=-N -l", "-trimpath", "-buildmode=exe", "-o", nooppath, spath) 140 | if b, err := cmd.CombinedOutput(); err != nil { 141 | t.Logf("%% build output: %s\n", b) 142 | t.Fatalf("build failed: %s", err) 143 | } 144 | return epath, nooppath 145 | } 146 | 147 | const programSourceCode = ` 148 | package main 149 | 150 | import ( 151 | "context" 152 | "strings" 153 | "fmt" 154 | ) 155 | 156 | var G int 157 | 158 | //go:noinline 159 | func another(x int) { 160 | println(G) 161 | } 162 | 163 | //go:noinline 164 | func docall(f func()) { 165 | f() 166 | } 167 | 168 | //go:noinline 169 | func Issue47354(s string) { 170 | docall(func() { 171 | println("s is", s) 172 | }) 173 | G++ 174 | another(int(s[0])) 175 | } 176 | 177 | type DB int 178 | type driverConn int 179 | type Result interface { 180 | Foo() 181 | } 182 | 183 | //go:noinline 184 | func (db *DB) Issue46845(ctx context.Context, dc *driverConn, release func(error), query string, args []interface{}) (res Result, err error) { 185 | defer func() { 186 | release(err) 187 | println(len(args)) 188 | }() 189 | return nil, nil 190 | } 191 | 192 | //go:noinline 193 | func Issue72053() { 194 | u := Address{Addr: "127.0.0.1"} 195 | fmt.Println(u) 196 | } 197 | 198 | type Address struct { 199 | TLS bool 200 | Addr string 201 | } 202 | 203 | //go:noinline 204 | func (a Address) String() string { 205 | sb := new(strings.Builder) 206 | sb.WriteString(a.Addr) 207 | return sb.String() 208 | } 209 | 210 | func main() { 211 | Issue47354("poo") 212 | var d DB 213 | d.Issue46845(context.Background(), nil, func(error) {}, "foo", nil) 214 | Issue72053() 215 | } 216 | 217 | ` 218 | 219 | func testIssue47354(t *testing.T, harnessPath string, ppath string) { 220 | expected := map[string]string{ 221 | "amd64": "1: in-param \"s\" loc=\"{ [0: S=8 RAX] [1: S=8 RBX] }\"", 222 | "arm64": "1: in-param \"s\" loc=\"{ [0: S=8 R0] [1: S=8 R1] }\"", 223 | } 224 | fname := "Issue47354" 225 | got := runHarness(t, harnessPath, ppath, "main."+fname) 226 | want := expected[runtime.GOARCH] 227 | if got != want { 228 | t.Errorf("failed Issue47354 arch %s:\ngot: %q\nwant: %q", 229 | runtime.GOARCH, got, want) 230 | } 231 | } 232 | 233 | func testIssue46845(t *testing.T, harnessPath string, ppath string) { 234 | 235 | // NB: note the "addr=0x1000" for the stack-based parameter "args" 236 | // below. This is not an accurate stack location, it's just an 237 | // artifact of the way we call into Delve. 238 | expected := map[string]string{ 239 | "amd64": ` 240 | 1: in-param "db" loc="{ [0: S=0 RAX] }" 241 | 2: in-param "ctx" loc="{ [0: S=8 RBX] [1: S=8 RCX] }" 242 | 3: in-param "dc" loc="{ [0: S=0 RDI] }" 243 | 4: in-param "release" loc="{ [0: S=0 RSI] }" 244 | 5: in-param "query" loc="{ [0: S=8 R8] [1: S=8 R9] }" 245 | 6: in-param "args" loc="{ [0: S=8 addr=0x1000] [1: S=8 addr=0x1008] [2: S=8 addr=0x1010] }" 246 | 7: out-param "res" loc="" 247 | 8: out-param "err" loc="" 248 | `, 249 | "arm64": ` 250 | 1: in-param "db" loc="{ [0: S=0 R0] }" 251 | 2: in-param "ctx" loc="{ [0: S=8 R1] [1: S=8 R2] }" 252 | 3: in-param "dc" loc="{ [0: S=0 R3] }" 253 | 4: in-param "release" loc="{ [0: S=0 R4] }" 254 | 5: in-param "query" loc="{ [0: S=8 R5] [1: S=8 R6] }" 255 | 6: in-param "args" loc="{ [0: S=8 R7] [1: S=8 R8] [2: S=8 R9] }" 256 | 7: out-param "res" loc="" 257 | 8: out-param "err" loc="" 258 | `, 259 | } 260 | fname := "(*DB).Issue46845" 261 | got := runHarness(t, harnessPath, ppath, "main."+fname) 262 | want := strings.TrimSpace(expected[runtime.GOARCH]) 263 | if got != want { 264 | t.Errorf("failed Issue47354 arch %s:\ngot: %s\nwant: %s", 265 | runtime.GOARCH, got, want) 266 | } 267 | } 268 | 269 | func testIssue72053(t *testing.T, harnessPath string, ppath string) { 270 | testenv.NeedsGo1Point(t, 25) 271 | testenv.NeedsArch(t, "amd64") 272 | 273 | want := "1: in-param \"a\" loc=\"{ [0: S=1 RAX] [1: S=7 addr=0x0] [2: S=8 RBX] [3: S=8 RCX] }\"\n2: out-param \"~r0\" loc=\"addr=fa8\"" 274 | got := runHarness(t, harnessPath, ppath, "main.Address.String") 275 | if got != want { 276 | t.Errorf("failed Issue72053 arch %s:\ngot: %q\nwant: %q", 277 | runtime.GOARCH, got, want) 278 | } 279 | } 280 | 281 | // testRuntimeThrow verifies that we have well-formed DWARF for the 282 | // single input parameter of 'runtime.throw'. This function is 283 | // particularly important to handle correctly, since it is 284 | // special-cased by Delve. The code below checks that things are ok 285 | // both for the regular optimized case and the "-gcflags=all=-l -N" 286 | // case, which Delve users are often selecting. 287 | func testRuntimeThrow(t *testing.T, harnessPath, nooptHarnessPath, ppath string) { 288 | expected := map[string]string{ 289 | "amd64": "1: in-param \"s\" loc=\"{ [0: S=8 RAX] [1: S=8 RBX] }\"", 290 | "arm64": "1: in-param \"s\" loc=\"{ [0: S=8 R0] [1: S=8 R1] }\"", 291 | } 292 | fname := "runtime.throw" 293 | harnesses := []string{harnessPath, nooptHarnessPath} 294 | for _, harness := range harnesses { 295 | got := runHarness(t, harness, ppath, fname) 296 | want := expected[runtime.GOARCH] 297 | if got != want { 298 | t.Errorf("failed RuntimeThrow arch %s, harness %s:\ngot: %q\nwant: %q", runtime.GOARCH, harness, got, want) 299 | } 300 | } 301 | } 302 | 303 | func TestDwarfVariableLocations(t *testing.T) { 304 | testenv.NeedsGo1Point(t, 18) 305 | testenv.MustHaveGoBuild(t) 306 | testenv.MustHaveExternalNetwork(t) 307 | 308 | // A note on the guard below: 309 | // - Delve doesn't officially support darwin/arm64, but I've run 310 | // this test by hand on darwin/arm64 and it seems to work, so 311 | // it is included for the moment 312 | // - the harness code currently only supports amd64 + arm64. If more 313 | // archs are added (ex: 386) the harness will need to be updated. 314 | pair := runtime.GOOS + "/" + runtime.GOARCH 315 | switch pair { 316 | case "linux/amd64", "linux/arm64", "windows/amd64", 317 | "darwin/amd64", "darwin/arm64": 318 | default: 319 | t.Skipf("unsupported OS/ARCH pair %s (this tests supports only OS values supported by Delve", pair) 320 | } 321 | 322 | tdir := t.TempDir() 323 | if *preserveTemp { 324 | if td, err := os.MkdirTemp("", "dwloctest"); err != nil { 325 | t.Fatal(err) 326 | } else { 327 | tdir = td 328 | fmt.Fprintf(os.Stderr, "** preserving tmpdir %s\n", td) 329 | } 330 | } 331 | 332 | // Build test harness. 333 | harnessPath, nooptHarnessPath := buildHarness(t, tdir) 334 | 335 | // Build program to inspect. NB: we're building at default (with 336 | // optimization); it might also be worth doing a "-l -N" build 337 | // to verify the location expressions in that case. 338 | ppath, nooppath := gobuild(t, programSourceCode, "prog", tdir) 339 | 340 | // Sub-tests for each function we want to inspect. 341 | t.Run("Issue47354", func(t *testing.T) { 342 | t.Parallel() 343 | testIssue47354(t, harnessPath, ppath) 344 | }) 345 | t.Run("Issue46845", func(t *testing.T) { 346 | t.Parallel() 347 | testIssue46845(t, harnessPath, ppath) 348 | }) 349 | t.Run("Issue72053", func(t *testing.T) { 350 | t.Parallel() 351 | testIssue72053(t, harnessPath, nooppath) 352 | }) 353 | t.Run("RuntimeThrow", func(t *testing.T) { 354 | t.Parallel() 355 | testRuntimeThrow(t, harnessPath, nooptHarnessPath, ppath) 356 | }) 357 | } 358 | -------------------------------------------------------------------------------- /dwtest/dwtest.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 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 | // Except for this comment and the import path, this file is a verbatim 6 | // copy of the file with the same name in $GOROOT/src/cmd/link/internal/dwtest. 7 | 8 | package dwtest 9 | 10 | import ( 11 | "debug/dwarf" 12 | "errors" 13 | "fmt" 14 | "os" 15 | ) 16 | 17 | // Helper type for supporting queries on DIEs within a DWARF 18 | // .debug_info section. Invoke the populate() method below passing in 19 | // a dwarf.Reader, which will read in all DIEs and keep track of 20 | // parent/child relationships. Queries can then be made to ask for 21 | // DIEs by name or by offset. This will hopefully reduce boilerplate 22 | // for future test writing. 23 | 24 | type Examiner struct { 25 | dies []*dwarf.Entry 26 | idxByOffset map[dwarf.Offset]int 27 | kids map[int][]int 28 | parent map[int]int 29 | byname map[string][]int 30 | } 31 | 32 | // Populate the Examiner using the DIEs read from rdr. 33 | func (ex *Examiner) Populate(rdr *dwarf.Reader) error { 34 | ex.idxByOffset = make(map[dwarf.Offset]int) 35 | ex.kids = make(map[int][]int) 36 | ex.parent = make(map[int]int) 37 | ex.byname = make(map[string][]int) 38 | var nesting []int 39 | for entry, err := rdr.Next(); entry != nil; entry, err = rdr.Next() { 40 | if err != nil { 41 | return err 42 | } 43 | if entry.Tag == 0 { 44 | // terminator 45 | if len(nesting) == 0 { 46 | return errors.New("nesting stack underflow") 47 | } 48 | nesting = nesting[:len(nesting)-1] 49 | continue 50 | } 51 | idx := len(ex.dies) 52 | ex.dies = append(ex.dies, entry) 53 | if _, found := ex.idxByOffset[entry.Offset]; found { 54 | return errors.New("DIE clash on offset") 55 | } 56 | ex.idxByOffset[entry.Offset] = idx 57 | if name, ok := entry.Val(dwarf.AttrName).(string); ok { 58 | ex.byname[name] = append(ex.byname[name], idx) 59 | } 60 | if len(nesting) > 0 { 61 | parent := nesting[len(nesting)-1] 62 | ex.kids[parent] = append(ex.kids[parent], idx) 63 | ex.parent[idx] = parent 64 | } 65 | if entry.Children { 66 | nesting = append(nesting, idx) 67 | } 68 | } 69 | if len(nesting) > 0 { 70 | return errors.New("unterminated child sequence") 71 | } 72 | return nil 73 | } 74 | 75 | func (ex *Examiner) DIEs() []*dwarf.Entry { 76 | return ex.dies 77 | } 78 | 79 | func indent(ilevel int) { 80 | for i := 0; i < ilevel; i++ { 81 | fmt.Printf(" ") 82 | } 83 | } 84 | 85 | // For debugging new tests 86 | func (ex *Examiner) DumpEntry(idx int, dumpKids bool, ilevel int) { 87 | if idx >= len(ex.dies) { 88 | fmt.Fprintf(os.Stderr, "DumpEntry: bad DIE %d: index out of range\n", idx) 89 | return 90 | } 91 | entry := ex.dies[idx] 92 | indent(ilevel) 93 | fmt.Printf("0x%x: %v\n", idx, entry.Tag) 94 | for _, f := range entry.Field { 95 | indent(ilevel) 96 | fmt.Printf("at=%v val=%v\n", f.Attr, f.Val) 97 | } 98 | if dumpKids { 99 | ksl := ex.kids[idx] 100 | for _, k := range ksl { 101 | ex.DumpEntry(k, true, ilevel+2) 102 | } 103 | } 104 | } 105 | 106 | // Given a DIE offset, return the previously read dwarf.Entry, or nil 107 | func (ex *Examiner) EntryFromOffset(off dwarf.Offset) *dwarf.Entry { 108 | if idx, found := ex.idxByOffset[off]; found && idx != -1 { 109 | return ex.entryFromIdx(idx) 110 | } 111 | return nil 112 | } 113 | 114 | // Return the ID that Examiner uses to refer to the DIE at offset off 115 | func (ex *Examiner) IdxFromOffset(off dwarf.Offset) int { 116 | if idx, found := ex.idxByOffset[off]; found { 117 | return idx 118 | } 119 | return -1 120 | } 121 | 122 | // Return the dwarf.Entry pointer for the DIE with id 'idx' 123 | func (ex *Examiner) entryFromIdx(idx int) *dwarf.Entry { 124 | if idx >= len(ex.dies) || idx < 0 { 125 | return nil 126 | } 127 | return ex.dies[idx] 128 | } 129 | 130 | // Returns a list of child entries for a die with ID 'idx' 131 | func (ex *Examiner) Children(idx int) []*dwarf.Entry { 132 | sl := ex.kids[idx] 133 | ret := make([]*dwarf.Entry, len(sl)) 134 | for i, k := range sl { 135 | ret[i] = ex.entryFromIdx(k) 136 | } 137 | return ret 138 | } 139 | 140 | // Returns parent DIE for DIE 'idx', or nil if the DIE is top level 141 | func (ex *Examiner) Parent(idx int) *dwarf.Entry { 142 | p, found := ex.parent[idx] 143 | if !found { 144 | return nil 145 | } 146 | return ex.entryFromIdx(p) 147 | } 148 | 149 | // ParentCU returns the enclosing compilation unit DIE for the DIE 150 | // with a given index, or nil if for some reason we can't establish a 151 | // parent. 152 | func (ex *Examiner) ParentCU(idx int) *dwarf.Entry { 153 | for { 154 | parentDie := ex.Parent(idx) 155 | if parentDie == nil { 156 | return nil 157 | } 158 | if parentDie.Tag == dwarf.TagCompileUnit { 159 | return parentDie 160 | } 161 | idx = ex.IdxFromOffset(parentDie.Offset) 162 | } 163 | } 164 | 165 | // FileRef takes a given DIE by index and a numeric file reference 166 | // (presumably from a decl_file or call_file attribute), looks up the 167 | // reference in the .debug_line file table, and returns the proper 168 | // string for it. We need to know which DIE is making the reference 169 | // so as to find the right compilation unit. 170 | func (ex *Examiner) FileRef(dw *dwarf.Data, dieIdx int, fileRef int64) (string, error) { 171 | 172 | // Find the parent compilation unit DIE for the specified DIE. 173 | cuDie := ex.ParentCU(dieIdx) 174 | if cuDie == nil { 175 | return "", fmt.Errorf("no parent CU DIE for DIE with idx %d?", dieIdx) 176 | } 177 | // Construct a line reader and then use it to get the file string. 178 | lr, lrerr := dw.LineReader(cuDie) 179 | if lrerr != nil { 180 | return "", fmt.Errorf("d.LineReader: %v", lrerr) 181 | } 182 | files := lr.Files() 183 | if fileRef < 0 || int(fileRef) > len(files)-1 { 184 | return "", fmt.Errorf("Examiner.FileRef: malformed file reference %d", fileRef) 185 | } 186 | return files[fileRef].Name, nil 187 | } 188 | 189 | // Return a list of all DIEs with name 'name'. When searching for DIEs 190 | // by name, keep in mind that the returned results will include child 191 | // DIEs such as params/variables. For example, asking for all DIEs named 192 | // "p" for even a small program will give you 400-500 entries. 193 | func (ex *Examiner) Named(name string) []*dwarf.Entry { 194 | sl := ex.byname[name] 195 | ret := make([]*dwarf.Entry, len(sl)) 196 | for i, k := range sl { 197 | ret[i] = ex.entryFromIdx(k) 198 | } 199 | return ret 200 | } 201 | 202 | // SubprogLoAndHighPc returns the values of the lo_pc and high_pc 203 | // attrs of the DWARF DIE subprogdie. For DWARF versions 2-3, both of 204 | // these attributes had to be of class address; with DWARF 4 the rules 205 | // were changed, allowing compilers to emit a high PC attr of class 206 | // constant, where the high PC could be computed by starting with the 207 | // low PC address and then adding in the high_pc attr offset (doing 208 | // things this way is more compact/efficient: 1 relocation instead of 209 | // 2). This function accepts both styles of specifying a hi/lo pair, 210 | // returning the values or an error if the attributes are malformed in 211 | // some way. 212 | func SubprogLoAndHighPc(subprogdie *dwarf.Entry) (lo uint64, hi uint64, err error) { 213 | // The low_pc attr for a subprogram DIE has to be of class address. 214 | lofield := subprogdie.AttrField(dwarf.AttrLowpc) 215 | if lofield == nil { 216 | err = fmt.Errorf("subprogram DIE has no low_pc attr") 217 | return 218 | } 219 | if lofield.Class != dwarf.ClassAddress { 220 | err = fmt.Errorf("subprogram DIE low_pc attr is not of class address") 221 | return 222 | } 223 | if lopc, ok := lofield.Val.(uint64); ok { 224 | lo = lopc 225 | } else { 226 | err = fmt.Errorf("subprogram DIE low_pc not convertible to uint64") 227 | return 228 | } 229 | 230 | // For the high_pc value, we'll accept either an address or a constant 231 | // offset from lo pc. 232 | hifield := subprogdie.AttrField(dwarf.AttrHighpc) 233 | if hifield == nil { 234 | err = fmt.Errorf("subprogram DIE has no high_pc attr") 235 | return 236 | } 237 | switch hifield.Class { 238 | case dwarf.ClassAddress: 239 | if hipc, ok := hifield.Val.(uint64); ok { 240 | hi = hipc 241 | } else { 242 | err = fmt.Errorf("subprogram DIE high not convertible to uint64") 243 | return 244 | } 245 | case dwarf.ClassConstant: 246 | if hioff, ok := hifield.Val.(int64); ok { 247 | hi = lo + uint64(hioff) 248 | } else { 249 | err = fmt.Errorf("subprogram DIE high_pc not convertible to uint64") 250 | return 251 | } 252 | default: 253 | err = fmt.Errorf("subprogram DIE high_pc unknown value class %s", 254 | hifield.Class) 255 | } 256 | return 257 | } 258 | -------------------------------------------------------------------------------- /dwtest/testdata/dwdumploc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 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 | package main 6 | 7 | // This file is the source code for a test helper binary 'dwdumploc', 8 | // which is built and then run by the tests in cmd/link/internal/dwtest. 9 | // It is set up to import packages "op" and "proc" from Delve (so 10 | // as to use Delve's DWARF location expression parser), and the 11 | // "dwtest" package from cmd/link/internal/dwtest (for general 12 | // DWARF examination/inspection). 13 | // 14 | // Given an input Go binary and a function name, this program 15 | // visits the DWARF for the function and dumps out the location expressions 16 | // for the function's input parameters on entry to the func. Example: 17 | // 18 | // ./dumpdwloc.exe -m ./dumpdwloc.exe -f main.locateFuncDetails 19 | // 1: in-param "executable" loc="{ [0: S=8 RAX] [1: S=8 RBX] }" 20 | // 2: in-param "fcn" loc="{ [0: S=8 RCX] [1: S=8 RDI] }" 21 | // 3: out-param "~r0" loc="" 22 | // 4: out-param "~r1" loc="" 23 | // 24 | // Since location expressions can refer to machine registers, dump 25 | // output for location expressions are architecture-dependent. The 26 | // dumper tool currently supports two archs: amd64 and arm64. 27 | // 28 | 29 | import ( 30 | "debug/dwarf" 31 | "debug/elf" 32 | "debug/macho" 33 | "debug/pe" 34 | "dwdumploc/dwtest" 35 | "flag" 36 | "fmt" 37 | "log" 38 | "os" 39 | "runtime" 40 | "strings" 41 | 42 | "github.com/go-delve/delve/pkg/dwarf/op" 43 | "github.com/go-delve/delve/pkg/proc" 44 | ) 45 | 46 | var verbflag = flag.Int("v", 0, "Verbose trace output level") 47 | var fcnflag = flag.String("f", "", "name of function to display") 48 | var moduleflag = flag.String("m", "", "load module to read") 49 | 50 | func verb(vlevel int, s string, a ...interface{}) { 51 | if *verbflag >= vlevel { 52 | fmt.Fprintf(os.Stderr, s, a...) 53 | fmt.Fprintf(os.Stderr, "\n") 54 | } 55 | } 56 | 57 | func warn(s string, a ...interface{}) { 58 | fmt.Fprintf(os.Stderr, s, a...) 59 | fmt.Fprintf(os.Stderr, "\n") 60 | } 61 | 62 | func usage(msg string) { 63 | if len(msg) > 0 { 64 | fmt.Fprintf(os.Stderr, "error: %s\n", msg) 65 | } 66 | fmt.Fprintf(os.Stderr, "usage: dwdumploc [flags] -m -f \n") 67 | flag.PrintDefaults() 68 | os.Exit(2) 69 | } 70 | 71 | type finfo struct { 72 | name string 73 | dwOffset dwarf.Offset 74 | dwLoPC uint64 75 | dwHiPC uint64 76 | valid bool 77 | dwx *dwtest.Examiner 78 | } 79 | 80 | // opener defines an interface for opening DWARF info in a Go binary. 81 | type opener interface { 82 | // Open examines binary pointed to by 'path', returning a pointer 83 | // to debug/dwarf.Data for it. If something goes wrong opening the 84 | // binary (file missing, or maybe not a supported binary) a 85 | // suitable error will be returned in the first error return. If 86 | // something goes wrong reading the DWARF, a suitable error will 87 | // be returned in the second error return. 88 | Open(path string) (*dwarf.Data, error, error) 89 | Which() string 90 | } 91 | 92 | // Remark: the boilerplate for three scenarios below (Elf, Macho, and 93 | // PE) seems like it should be an ideal scenario where you could use 94 | // generics, but my attempts at this were not especially successful. 95 | // The sticking point is that each of elf.Open/macho.Open etc return a 96 | // different thing, and I couldn't quite figure out how to get this to 97 | // work with generics. TODO: make another attempt. 98 | 99 | // elfOpener implements the opener interface for ELF 100 | // (https://en.wikipedia.org/wiki/Executable_and_Linkable_Format) binaries 101 | type elfOpener struct { 102 | } 103 | 104 | func (eo *elfOpener) Open(path string) (*dwarf.Data, error, error) { 105 | f, err := elf.Open(path) 106 | if err != nil { 107 | return nil, err, nil 108 | } 109 | d, err := f.DWARF() 110 | if err != nil { 111 | return nil, nil, err 112 | } 113 | return d, nil, nil 114 | } 115 | 116 | func (eo *elfOpener) Which() string { 117 | return "elf" 118 | } 119 | 120 | // machoOpener implements the opener for macos/darwin Macho-O 121 | // (https://en.wikipedia.org/wiki/Mach-O) binaries. 122 | type machoOpener struct { 123 | } 124 | 125 | func (mo *machoOpener) Open(path string) (*dwarf.Data, error, error) { 126 | f, err := macho.Open(path) 127 | if err != nil { 128 | return nil, err, nil 129 | } 130 | d, err := f.DWARF() 131 | if err != nil { 132 | return nil, nil, err 133 | } 134 | return d, nil, nil 135 | } 136 | 137 | func (mo *machoOpener) Which() string { 138 | return "macho" 139 | } 140 | 141 | // peOpener implements the opener interface for Windows PE 142 | // (https://en.wikipedia.org/wiki/Portable_Executable) binaries. 143 | type peOpener struct { 144 | } 145 | 146 | func (po *peOpener) Open(path string) (*dwarf.Data, error, error) { 147 | f, err := pe.Open(path) 148 | if err != nil { 149 | return nil, err, nil 150 | } 151 | d, err := f.DWARF() 152 | if err != nil { 153 | return nil, nil, err 154 | } 155 | return d, nil, nil 156 | } 157 | 158 | func (po *peOpener) Which() string { 159 | return "pe" 160 | } 161 | 162 | // openDwarf tries to open the Go binary 'path' as an ELF file, 163 | // a Macho file, or a PE file in turn. If an open succeeds, it 164 | // returns a dwarf.Data for the binary; if they all fail, it returns 165 | // an error. 166 | func openDwarf(path string) (*dwarf.Data, error) { 167 | eo := elfOpener{} 168 | mo := machoOpener{} 169 | po := peOpener{} 170 | var eoo opener = &eo 171 | var moo opener = &mo 172 | var poo opener = &po 173 | openers := []opener{eoo, moo, poo} 174 | postmortem := "" 175 | for k, o := range openers { 176 | d, errf, errd := o.Open(path) 177 | if d != nil { 178 | return d, nil 179 | } 180 | if errd != nil { 181 | return nil, errd 182 | } 183 | postmortem += fmt.Sprintf("\tattempt %d (%s) failed: %v\n", k, o.Which(), errf) 184 | } 185 | return nil, fmt.Errorf("init failed:\n%s", postmortem) 186 | } 187 | 188 | // locateFuncDetails walks the DWARF for Go binary 'executable' 189 | // looking for a subprogram DIE for function 'fcn'. If it finds a 190 | // function of the right name, it fills in info from the DWARF into 191 | // a "finfo" struct and returns it, or returns an empty struct 192 | // and an error if something went wrong. 193 | func locateFuncDetails(executable string, fcn string) (finfo, error) { 194 | rrv := finfo{} 195 | 196 | if inf, err := os.Open(executable); err != nil { 197 | return rrv, fmt.Errorf("unable to open input executable %s: %v", executable, err) 198 | } else { 199 | inf.Close() 200 | } 201 | 202 | verb(1, "loading DWARF for %s", executable) 203 | d, err := openDwarf(executable) 204 | if err != nil { 205 | return finfo{}, err 206 | } 207 | verb(1, "DWARF loaded for %s", executable) 208 | 209 | // Construct dwtest.Examiner helper object, to make 210 | // inspection of the DWARF easier. 211 | rdr := d.Reader() 212 | dwx := dwtest.Examiner{} 213 | if err := dwx.Populate(rdr); err != nil { 214 | return finfo{}, fmt.Errorf("error reading DWARF: %v", err) 215 | } 216 | 217 | // Walk DIEs looking for subprogram DIEs. 218 | dies := dwx.DIEs() 219 | for idx := 0; idx < len(dies); idx++ { 220 | die := dies[idx] 221 | off := die.Offset 222 | if die.Tag == dwarf.TagCompileUnit { 223 | if name, ok := die.Val(dwarf.AttrName).(string); ok { 224 | verb(2, "compilation unit: %s", name) 225 | } 226 | continue 227 | } 228 | if die.Tag != dwarf.TagSubprogram { 229 | // TODO: skip children 230 | continue 231 | } 232 | // Name has to match the function we're looking for. 233 | name, ok := die.Val(dwarf.AttrName).(string) 234 | if !ok { 235 | continue 236 | } 237 | if name != fcn { 238 | // TODO: skip children 239 | continue 240 | } 241 | 242 | verb(1, "found function %s at offset %x", fcn, off) 243 | rrv.dwOffset = off 244 | 245 | // Collect the start/end PC for the func. The format/class of 246 | // the high PC attr may vary depending on which DWARF version 247 | // we're generating; invoke a helper to handle the various 248 | // possibilities. 249 | lowpc, highpc, perr := dwtest.SubprogLoAndHighPc(die) 250 | if perr != nil { 251 | return finfo{}, fmt.Errorf("sibprog die malformed: %v", perr) 252 | } 253 | rrv.dwLoPC = lowpc 254 | rrv.dwHiPC = highpc 255 | rrv.dwx = &dwx 256 | rrv.name = fcn 257 | rrv.valid = true 258 | return rrv, nil 259 | } 260 | return finfo{}, fmt.Errorf("could not locate target function in DWARF") 261 | } 262 | 263 | var AMD64DWARFRegisters = map[int]string{ 264 | 0: "RAX", 265 | 1: "RDX", 266 | 2: "RCX", 267 | 3: "RBX", 268 | 4: "RSI", 269 | 5: "RDI", 270 | 6: "RBP", 271 | 7: "RSP", 272 | 8: "R8", 273 | 9: "R9", 274 | 10: "R10", 275 | 11: "R11", 276 | 12: "R12", 277 | 13: "R13", 278 | 14: "R14", 279 | 15: "R15", 280 | 17: "X0", 281 | 18: "X1", 282 | 19: "X2", 283 | 20: "X3", 284 | 21: "X4", 285 | 22: "X5", 286 | 23: "X6", 287 | 24: "X7", 288 | 25: "X8", 289 | 26: "X9", 290 | 27: "X10", 291 | 28: "X11", 292 | 29: "X12", 293 | 30: "X13", 294 | 31: "X14", 295 | 32: "X15", 296 | } 297 | 298 | var ARM64DWARFRegisters = map[int]string{ 299 | // int 300 | 0: "R0", 301 | 1: "R1", 302 | 2: "R2", 303 | 3: "R3", 304 | 4: "R4", 305 | 5: "R5", 306 | 6: "R6", 307 | 7: "R7", 308 | 8: "R8", 309 | 9: "R9", 310 | 10: "R10", 311 | 11: "R11", 312 | 12: "R12", 313 | 13: "R13", 314 | 14: "R14", 315 | 15: "R15", 316 | 16: "R16", 317 | 17: "R17", 318 | 18: "R18", 319 | 19: "R19", 320 | 20: "R20", 321 | 21: "R21", 322 | 22: "R22", 323 | 23: "R23", 324 | 24: "R24", 325 | 25: "R25", 326 | 26: "R26", 327 | 27: "R27", 328 | 28: "R28", 329 | 29: "R29", 330 | 30: "R30", 331 | 332 | // float 333 | 64: "F0", 334 | 65: "F1", 335 | 66: "F2", 336 | 67: "F3", 337 | 68: "F4", 338 | 69: "F5", 339 | 70: "F6", 340 | 71: "F7", 341 | 72: "F8", 342 | 73: "F9", 343 | 74: "F10", 344 | 75: "F11", 345 | 76: "F12", 346 | 77: "F13", 347 | 78: "F14", 348 | 79: "F15", 349 | 80: "F16", 350 | 81: "F17", 351 | 82: "F18", 352 | 83: "F19", 353 | 84: "F20", 354 | 85: "F21", 355 | 86: "F22", 356 | 87: "F23", 357 | 88: "F24", 358 | 89: "F25", 359 | 90: "F26", 360 | 91: "F27", 361 | 92: "F28", 362 | 93: "F29", 363 | 94: "F30", 364 | 95: "F31", 365 | } 366 | 367 | func regString(dwreg int) string { 368 | var v string 369 | switch runtime.GOARCH { 370 | case "amd64": 371 | v = AMD64DWARFRegisters[dwreg] 372 | case "arm64": 373 | v = ARM64DWARFRegisters[dwreg] 374 | default: 375 | panic(fmt.Sprintf("no support for arch %s", runtime.GOARCH)) 376 | } 377 | if v != "" { 378 | return v 379 | } 380 | return fmt.Sprintf("reg=%d", dwreg) 381 | } 382 | 383 | func pstring(addr int64, pcs []op.Piece, err error) (string, error) { 384 | if err != nil { 385 | serr := fmt.Sprintf("%s", err) 386 | if strings.HasPrefix(serr, "could not find loclist entry") { 387 | return "", nil 388 | } 389 | return "", err 390 | } 391 | if pcs == nil { 392 | return fmt.Sprintf("addr=%x", addr), nil 393 | } 394 | r := "{" 395 | for k, p := range pcs { 396 | r += fmt.Sprintf(" [%d: S=%d", k, p.Size) 397 | if p.Kind == op.RegPiece { 398 | r += fmt.Sprintf(" %s]", regString(int(p.Val))) 399 | } else { 400 | r += fmt.Sprintf(" addr=0x%x]", p.Val) 401 | } 402 | } 403 | r += " }" 404 | return r, nil 405 | } 406 | 407 | // processParams initializes a 'proc.BinaryInfo' object for the binary 408 | // in question and then walks the formal parameters of the selected 409 | // function, invoking the Location method on each param to read its 410 | // location expression. Results are dumped to stdout, and an error is 411 | // returned if something goes wrong. 412 | func processParams(executable string, fi *finfo) error { 413 | const _cfa = 0x1000 414 | bi := proc.NewBinaryInfo(runtime.GOOS, runtime.GOARCH) 415 | if err := bi.LoadBinaryInfo(executable, 0, []string{}); err != nil { 416 | return err 417 | } 418 | 419 | // Walk subprogram DIE's children. 420 | pidx := fi.dwx.IdxFromOffset(fi.dwOffset) 421 | childDies := fi.dwx.Children(pidx) 422 | idx := 0 423 | for _, e := range childDies { 424 | if e.Tag != dwarf.TagFormalParameter { 425 | continue 426 | } 427 | if e.Val(dwarf.AttrName) == nil { 428 | continue 429 | } 430 | idx++ 431 | name := e.Val(dwarf.AttrName).(string) 432 | var isrvar bool 433 | if e.Tag == dwarf.TagFormalParameter { 434 | isrvar = e.Val(dwarf.AttrVarParam).(bool) 435 | } 436 | addr, pieces, _, err := bi.Location(e, dwarf.AttrLocation, fi.dwLoPC, op.DwarfRegisters{CFA: _cfa, FrameBase: _cfa}, nil) 437 | pdump, err := pstring(addr, pieces, err) 438 | if err != nil { 439 | if fmt.Sprintf("%s", err) == "empty OP stack" { 440 | pdump = "" 441 | } else { 442 | return fmt.Errorf("bad return from bi.Location at pc 0x%x: %q\n", fi.dwLoPC, err) 443 | } 444 | } 445 | wh := "in" 446 | if isrvar { 447 | wh = "out" 448 | } 449 | fmt.Printf("%d: %s-param %q loc=%q\n", idx, wh, name, pdump) 450 | 451 | } 452 | return nil 453 | 454 | } 455 | 456 | // examineFile kicks off the search for DWARF info for 'fcn' within 457 | // Go binary 'executable', printing results to stdout if possible. 458 | func examineFile(executable string, fcn string) { 459 | verb(1, "examineFile(%s,%s)", executable, fcn) 460 | fi, err := locateFuncDetails(executable, fcn) 461 | if err != nil { 462 | log.Fatalf("error: %v\n", err) 463 | } 464 | if !fi.valid { 465 | log.Fatalf("could not locate target function %s in executable %s", fcn, executable) 466 | 467 | } 468 | if err := processParams(executable, &fi); err != nil { 469 | log.Fatalf("error: %v\n", err) 470 | } 471 | } 472 | 473 | func main() { 474 | log.SetFlags(0) 475 | log.SetPrefix("dwdumploc: ") 476 | flag.Parse() 477 | verb(1, "in main") 478 | if *fcnflag == "" || *moduleflag == "" { 479 | usage("please supply -f and -m options") 480 | } 481 | if flag.NArg() != 0 { 482 | usage("unexpected additional arguments") 483 | } 484 | examineFile(*moduleflag, *fcnflag) 485 | verb(1, "leaving main") 486 | } 487 | -------------------------------------------------------------------------------- /dwtest/testdata/go.mod.txt: -------------------------------------------------------------------------------- 1 | module dwdumploc 2 | 3 | go 1.21 4 | 5 | require github.com/go-delve/delve v1.24.1-0.20250303164244-e6e7aeb66705 6 | 7 | require ( 8 | github.com/cilium/ebpf v0.11.0 // indirect 9 | github.com/hashicorp/golang-lru v1.0.2 // indirect 10 | golang.org/x/arch v0.11.0 // indirect 11 | golang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2 // indirect 12 | golang.org/x/sys v0.26.0 // indirect 13 | ) 14 | -------------------------------------------------------------------------------- /dwtest/testdata/go.sum.txt: -------------------------------------------------------------------------------- 1 | github.com/cilium/ebpf v0.11.0 h1:V8gS/bTCCjX9uUnkUFUpPsksM8n1lXBAvHcpiFk1X2Y= 2 | github.com/cilium/ebpf v0.11.0/go.mod h1:WE7CZAnqOL2RouJ4f1uyNhqr2P4CCvXFIqdRDUgWsVs= 3 | github.com/frankban/quicktest v1.14.5 h1:dfYrrRyLtiqT9GyKXgdh+k4inNeTvmGbuSgZ3lx3GhA= 4 | github.com/frankban/quicktest v1.14.5/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= 5 | github.com/go-delve/delve v1.24.1-0.20250303164244-e6e7aeb66705 h1:VWoXnrDBGu171DJk2Xh+0KXz73IlwtKS4fhCunevDW0= 6 | github.com/go-delve/delve v1.24.1-0.20250303164244-e6e7aeb66705/go.mod h1:kJk12wo6PqzWknTP6M+Pg3/CrNhFMZvNq1iHESKkhv8= 7 | github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= 8 | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 9 | github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= 10 | github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= 11 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 12 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 13 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 14 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 15 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 16 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 17 | github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= 18 | github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= 19 | golang.org/x/arch v0.11.0 h1:KXV8WWKCXm6tRpLirl2szsO5j/oOODwZf4hATmGVNs4= 20 | golang.org/x/arch v0.11.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= 21 | golang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2 h1:Jvc7gsqn21cJHCmAWx0LiimpP18LZmUxkT5Mp7EZ1mI= 22 | golang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= 23 | golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= 24 | golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 25 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 26 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 27 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module golang.org/x/debug 2 | 3 | go 1.23.0 4 | 5 | require ( 6 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e 7 | github.com/spf13/cobra v1.7.0 8 | github.com/spf13/pflag v1.0.5 9 | golang.org/x/sys v0.33.0 10 | ) 11 | 12 | require ( 13 | github.com/chzyer/logex v1.1.10 // indirect 14 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 // indirect 15 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 16 | ) 17 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE= 2 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= 3 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8= 4 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= 5 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8= 6 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 7 | github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 8 | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= 9 | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 10 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 11 | github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= 12 | github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= 13 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 14 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 15 | golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= 16 | golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 17 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 18 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 19 | -------------------------------------------------------------------------------- /internal/core/address.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 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 | package core 6 | 7 | // An Address is a location in the inferior's address space. 8 | type Address uint64 9 | 10 | // Sub subtracts b from a. Requires a >= b. 11 | func (a Address) Sub(b Address) int64 { 12 | return int64(a - b) 13 | } 14 | 15 | // Add adds x to address a. 16 | func (a Address) Add(x int64) Address { 17 | return a + Address(x) 18 | } 19 | 20 | // Max returns the larger of a and b. 21 | func (a Address) Max(b Address) Address { 22 | if a > b { 23 | return a 24 | } 25 | return b 26 | } 27 | 28 | // Min returns the smaller of a and b. 29 | func (a Address) Min(b Address) Address { 30 | if a < b { 31 | return a 32 | } 33 | return b 34 | } 35 | 36 | // Align rounds a up to a multiple of x. 37 | // x must be a power of 2. 38 | func (a Address) Align(x int64) Address { 39 | return (a + Address(x) - 1) & ^(Address(x) - 1) 40 | } 41 | -------------------------------------------------------------------------------- /internal/core/core_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 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 | //go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris 6 | 7 | package core 8 | 9 | import ( 10 | "fmt" 11 | "testing" 12 | ) 13 | 14 | // loadExample loads a simple core file which resulted from running the 15 | // following program on linux/amd64 with go 1.9.0 (the earliest supported runtime): 16 | // 17 | // package main 18 | // 19 | // func main() { 20 | // _ = *(*int)(nil) 21 | // } 22 | func loadExample(t *testing.T, useExePath bool) *Process { 23 | t.Helper() 24 | var p *Process 25 | var err error 26 | if useExePath { 27 | p, err = Core("testdata/core", "", "testdata/tmp/test") 28 | } else { 29 | p, err = Core("testdata/core", "testdata", "") 30 | } 31 | if err != nil { 32 | t.Fatalf("can't load test core file: %s", err) 33 | } 34 | return p 35 | } 36 | 37 | // TestMappings makes sure we can find and load some data. 38 | func TestMappings(t *testing.T) { 39 | test := func(t *testing.T, useExePath bool) { 40 | p := loadExample(t, useExePath) 41 | s, err := p.Symbols() 42 | if err != nil { 43 | t.Errorf("can't read symbols: %s\n", err) 44 | } 45 | 46 | a := s["main.main"] 47 | m := p.pageTable.findMapping(a) 48 | if m == nil { 49 | t.Errorf("text mapping missing") 50 | } 51 | if m.Perm() != Read|Exec { 52 | t.Errorf("bad code section permissions") 53 | } 54 | if opcode := p.ReadUint8(a); opcode != 0x31 { 55 | // 0x31 = xorl instruction. 56 | // There's no particular reason why this instruction 57 | // is first. This just tests that reading code works 58 | // for our specific test binary. 59 | t.Errorf("opcode=0x%x, want 0x31", opcode) 60 | } 61 | 62 | a = s["runtime.class_to_size"] 63 | m = p.pageTable.findMapping(a) 64 | if m == nil { 65 | t.Errorf("data mapping missing") 66 | } 67 | if m.Perm() != Read|Write { 68 | t.Errorf("bad data section permissions") 69 | } 70 | if size := p.ReadUint16(a.Add(2)); size != 8 { 71 | t.Errorf("class_to_size[1]=%d, want 8", size) 72 | } 73 | } 74 | 75 | for _, useExePath := range []bool{false, true} { 76 | name := fmt.Sprintf("useExePath=%t", useExePath) 77 | t.Run(name, func(t *testing.T) { 78 | test(t, useExePath) 79 | }) 80 | } 81 | } 82 | 83 | // TestConfig checks the configuration accessors. 84 | func TestConfig(t *testing.T) { 85 | p := loadExample(t, false) 86 | if arch := p.Arch(); arch != "amd64" { 87 | t.Errorf("arch=%s, want amd64", arch) 88 | } 89 | if size := p.PtrSize(); size != 8 { 90 | t.Errorf("ptrSize=%d, want 8", size) 91 | } 92 | if log := p.LogPtrSize(); log != 3 { 93 | t.Errorf("logPtrSize=%d, want 3", log) 94 | } 95 | if bo := p.ByteOrder(); bo.String() != "LittleEndian" { 96 | t.Errorf("got %s, want LittleEndian", bo) 97 | } 98 | } 99 | 100 | // TestThread makes sure we get information about running threads. 101 | func TestThread(t *testing.T) { 102 | p := loadExample(t, true) 103 | syms, err := p.Symbols() 104 | if err != nil { 105 | t.Errorf("can't read symbols: %s\n", err) 106 | } 107 | raise := syms["runtime.raise"] 108 | var size int64 = 1 << 30 109 | for _, a := range syms { 110 | if a > raise && a.Sub(raise) < size { 111 | size = a.Sub(raise) 112 | } 113 | } 114 | found := false 115 | for _, thr := range p.Threads() { 116 | if thr.PC() >= raise && thr.PC() < raise.Add(size) { 117 | found = true 118 | } 119 | } 120 | if !found { 121 | t.Errorf("can't find thread that did runtime.raise") 122 | } 123 | } 124 | 125 | func TestArgs(t *testing.T) { 126 | p := loadExample(t, true) 127 | if got := p.Args(); got != "./test" { 128 | // this is how the program of testdata/core was invoked. 129 | t.Errorf("Args() = %q, want './test'", got) 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /internal/core/mapping.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 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 | package core 6 | 7 | import ( 8 | "fmt" 9 | "os" 10 | ) 11 | 12 | // A Mapping represents a contiguous subset of the inferior's address space. 13 | type Mapping struct { 14 | min Address 15 | max Address 16 | perm Perm 17 | 18 | f *os.File // file backing this region 19 | off int64 // offset of start of this mapping in f 20 | 21 | // For regions originally backed by a file but now in the core file, 22 | // (probably because it is copy-on-write) this is the original data source. 23 | // This info is just for printing; the data in this source is stale. 24 | origF *os.File 25 | origOff int64 26 | 27 | // Contents of f at offset off. Length=max-min. 28 | contents []byte 29 | } 30 | 31 | func (m Mapping) String() string { 32 | name := "anon" 33 | if m.f != nil { 34 | name = m.f.Name() 35 | } 36 | var orig string 37 | if m.origF != nil { 38 | orig = fmt.Sprintf(" (orig: %s+0x%x)", m.origF.Name(), m.origOff) 39 | } 40 | return fmt.Sprintf("0x%x-0x%x %s %8d %s+0x%x%s", m.min, m.max, m.perm, m.max-m.min, name, m.off, orig) 41 | } 42 | 43 | // namedMapping is equivalent to Mapping, just using the filename rather than 44 | // opened file. 45 | type namedMapping struct { 46 | min Address 47 | max Address 48 | 49 | f string // filename backing this region 50 | off int64 // offset of start of this mapping in f 51 | } 52 | 53 | func (m *namedMapping) String() string { 54 | return fmt.Sprintf("0x%x-0x%x ---- %8d %s+0x%x", m.min, m.max, m.max-m.min, m.f, m.off) 55 | } 56 | 57 | // Min returns the lowest virtual address of the mapping. 58 | func (m *Mapping) Min() Address { 59 | return m.min 60 | } 61 | 62 | // Max returns the virtual address of the byte just beyond the mapping. 63 | func (m *Mapping) Max() Address { 64 | return m.max 65 | } 66 | 67 | // Size returns int64(Max-Min) 68 | func (m *Mapping) Size() int64 { 69 | return m.max.Sub(m.min) 70 | } 71 | 72 | // Perm returns the permissions on the mapping. 73 | func (m *Mapping) Perm() Perm { 74 | return m.perm 75 | } 76 | 77 | // Source returns the backing file and offset for the mapping, or "", 0 if none. 78 | func (m *Mapping) Source() (string, int64) { 79 | if m.f == nil { 80 | return "", 0 81 | } 82 | return m.f.Name(), m.off 83 | } 84 | 85 | // CopyOnWrite reports whether the mapping is a copy-on-write region, i.e. 86 | // it started as a mapped file and is now writeable. 87 | // TODO: is this distinguishable from a write-back region? 88 | func (m *Mapping) CopyOnWrite() bool { 89 | return m.origF != nil 90 | } 91 | 92 | // For CopyOnWrite mappings, OrigSource returns the file/offset of the 93 | // original copy of the data, or "", 0 if none. 94 | func (m *Mapping) OrigSource() (string, int64) { 95 | if m.origF == nil { 96 | return "", 0 97 | } 98 | return m.origF.Name(), m.origOff 99 | } 100 | 101 | // A Perm represents the permissions allowed for a Mapping. 102 | type Perm uint8 103 | 104 | const ( 105 | Read Perm = 1 << iota 106 | Write 107 | Exec 108 | ) 109 | 110 | func (p Perm) String() string { 111 | // Print a permissions string in the same format as Linux (/proc/self/maps). 112 | // E.g.: rwxp, r--p, ... 113 | b := [4]byte{'-', '-', '-', 'p'} // TODO(aktau): Allow non-shared mappings? 114 | if p&Read != 0 { 115 | b[0] = 'r' 116 | } 117 | if p&Write != 0 { 118 | b[1] = 'w' 119 | } 120 | if p&Exec != 0 { 121 | b[2] = 'x' 122 | } 123 | return string(b[:]) 124 | } 125 | 126 | // We assume that OS pages are at least 4K in size. So every mapping 127 | // starts and ends at a multiple of 4K. 128 | // We divide the other 64-12 = 52 bits into levels in a page table. 129 | type pageTable0 [1 << 10]*Mapping 130 | type pageTable1 [1 << 10]*pageTable0 131 | type pageTable2 [1 << 10]*pageTable1 132 | type pageTable3 [1 << 10]*pageTable2 133 | type pageTable4 [1 << 12]*pageTable3 134 | 135 | const pageSize Address = 1 << 12 136 | 137 | // findMapping is simple enough that it inlines. 138 | func (p *pageTable4) findMapping(a Address) *Mapping { 139 | t3 := p[a>>52] 140 | if t3 == nil { 141 | return nil 142 | } 143 | t2 := t3[a>>42%(1<<10)] 144 | if t2 == nil { 145 | return nil 146 | } 147 | t1 := t2[a>>32%(1<<10)] 148 | if t1 == nil { 149 | return nil 150 | } 151 | t0 := t1[a>>22%(1<<10)] 152 | if t0 == nil { 153 | return nil 154 | } 155 | return t0[a>>12%(1<<10)] 156 | } 157 | 158 | func (p *pageTable4) addMapping(m *Mapping) error { 159 | if m.min%(pageSize) != 0 { 160 | return fmt.Errorf("mapping start %x isn't a multiple of 4096", m.min) 161 | } 162 | if m.max%(pageSize) != 0 { 163 | return fmt.Errorf("mapping end %x isn't a multiple of 4096", m.max) 164 | } 165 | for a := m.min; a < m.max; a += 1 << 12 { 166 | i3 := a >> 52 167 | t3 := p[i3] 168 | if t3 == nil { 169 | t3 = new(pageTable3) 170 | p[i3] = t3 171 | } 172 | i2 := a >> 42 % (1 << 10) 173 | t2 := t3[i2] 174 | if t2 == nil { 175 | t2 = new(pageTable2) 176 | t3[i2] = t2 177 | } 178 | i1 := a >> 32 % (1 << 10) 179 | t1 := t2[i1] 180 | if t1 == nil { 181 | t1 = new(pageTable1) 182 | t2[i1] = t1 183 | } 184 | i0 := a >> 22 % (1 << 10) 185 | t0 := t1[i0] 186 | if t0 == nil { 187 | t0 = new(pageTable0) 188 | t1[i0] = t0 189 | } 190 | t0[a>>12%(1<<10)] = m 191 | } 192 | return nil 193 | } 194 | 195 | // splicedMemory represents a memory space formed from multiple regions. 196 | // Much of the logic was copied from delve/pkg/proc/core.go. 197 | type splicedMemory struct { 198 | mappings []*Mapping 199 | } 200 | 201 | func (s *splicedMemory) Add(min, max Address, perm Perm, f *os.File, off int64) { 202 | if max-min <= 0 { 203 | return 204 | } 205 | 206 | // Align max. 207 | if max%pageSize != 0 { 208 | max = (max + pageSize) & ^(pageSize - 1) 209 | } 210 | // Align min. 211 | if gap := min % pageSize; gap != 0 { 212 | off -= int64(gap) 213 | min -= gap 214 | } 215 | 216 | newMappings := make([]*Mapping, 0, len(s.mappings)+1) 217 | add := func(m *Mapping) { 218 | if m.Size() <= 0 { 219 | return 220 | } 221 | newMappings = append(newMappings, m) 222 | } 223 | 224 | inserted := false 225 | for _, entry := range s.mappings { 226 | switch { 227 | case entry.max < min: // entry is completely before the new region. 228 | add(entry) 229 | case max < entry.min: // entry is completely after the new region. 230 | if !inserted { 231 | add(&Mapping{min: min, max: max, perm: perm, f: f, off: off}) 232 | inserted = true 233 | } 234 | add(entry) 235 | case min <= entry.min && entry.max <= max: 236 | // entry is completely overwritten by the new region. Drop. 237 | case entry.min <= min && entry.max <= max: 238 | // new region overwrites the end of the entry. 239 | entry.max = min 240 | add(entry) 241 | case min <= entry.min && max <= entry.max: 242 | // new region overwrites the beginning of the entry. 243 | if !inserted { 244 | add(&Mapping{min: min, max: max, perm: perm, f: f, off: off}) 245 | inserted = true 246 | } 247 | entry.off += int64(max - entry.min) 248 | entry.min = max 249 | add(entry) 250 | case entry.min < min && max < entry.max: 251 | // new region punches a hole in the entry. 252 | entry2 := *entry 253 | 254 | entry.max = min 255 | entry2.off += int64(max - entry.min) 256 | entry2.min = max 257 | add(entry) 258 | add(&Mapping{min: min, max: max, perm: perm, f: f, off: off}) 259 | add(&entry2) 260 | inserted = true 261 | default: 262 | panic(fmt.Sprintf("Unhandled case: existing entry is (min:0x%x max:0x%x), new entry is (min:0x%x max:0x%x)", entry.min, entry.max, min, max)) 263 | } 264 | } 265 | if !inserted { 266 | add(&Mapping{min: min, max: max, perm: perm, f: f, off: off}) 267 | } 268 | s.mappings = newMappings 269 | } 270 | 271 | // splitMappingsAt ensures that a is not in the middle of any mapping. 272 | // Splits mappings as necessary. 273 | func (s *splicedMemory) splitMappingsAt(a Address) { 274 | for _, m := range s.mappings { 275 | if a < m.min || a > m.max { 276 | continue 277 | } 278 | if a == m.min || a == m.max { 279 | return 280 | } 281 | // Split this mapping at a. 282 | m2 := new(Mapping) 283 | *m2 = *m 284 | m.max = a 285 | m2.min = a 286 | if m2.f != nil { 287 | m2.off += m.Size() 288 | } 289 | if m2.origF != nil { 290 | m2.origOff += m.Size() 291 | } 292 | s.mappings = append(s.mappings, m2) 293 | return 294 | } 295 | } 296 | -------------------------------------------------------------------------------- /internal/core/mapping_test.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestSplicedMemoryAdd(t *testing.T) { 9 | type region struct { 10 | min, max Address 11 | perm Perm 12 | off int64 13 | } 14 | tests := []struct { 15 | name string 16 | in []region 17 | want []region 18 | }{ 19 | { 20 | "Insert after", 21 | []region{ 22 | {min: 0 * pageSize, max: 1 * pageSize, off: 1}, 23 | {min: 1 * pageSize, max: 2 * pageSize, off: 2}, 24 | }, 25 | []region{ 26 | {min: 0 * pageSize, max: 1 * pageSize, off: 1}, 27 | {min: 1 * pageSize, max: 2 * pageSize, off: 2}, 28 | }, 29 | }, 30 | { 31 | "Insert before", 32 | []region{ 33 | {min: 1 * pageSize, max: 2 * pageSize, off: 1}, 34 | {min: 0 * pageSize, max: 1 * pageSize, off: 2}, 35 | }, 36 | []region{ 37 | {min: 0 * pageSize, max: 1 * pageSize, off: 2}, 38 | {min: 1 * pageSize, max: 2 * pageSize, off: 1}, 39 | }, 40 | }, 41 | { 42 | "Completely overwrite", 43 | []region{ 44 | {min: 1 * pageSize, max: 2 * pageSize, perm: Read, off: 2}, 45 | {min: 1 * pageSize, max: 2 * pageSize, perm: Write, off: 1}, 46 | }, 47 | []region{ 48 | {min: 1 * pageSize, max: 2 * pageSize, perm: Write, off: 1}, 49 | }, 50 | }, 51 | { 52 | "Overwrite end", 53 | []region{ 54 | {min: 0 * pageSize, max: 2 * pageSize, perm: Read, off: 0}, 55 | {min: 1 * pageSize, max: 2 * pageSize, perm: Write, off: 1}, 56 | }, 57 | []region{ 58 | {min: 0 * pageSize, max: 1 * pageSize, perm: Read, off: 0}, 59 | {min: 1 * pageSize, max: 2 * pageSize, perm: Write, off: 1}, 60 | }, 61 | }, 62 | { 63 | "Overwrite start", 64 | []region{ 65 | {min: 0 * pageSize, max: 2 * pageSize, perm: Read, off: 0}, 66 | {min: 0 * pageSize, max: 1 * pageSize, perm: Write, off: 1}, 67 | }, 68 | []region{ 69 | {min: 0 * pageSize, max: 1 * pageSize, perm: Write, off: 1}, 70 | {min: 1 * pageSize, max: 2 * pageSize, perm: Read, off: int64(1 * pageSize)}, 71 | }, 72 | }, 73 | { 74 | "Punch hole", 75 | []region{ 76 | {min: 10 * pageSize, max: 30 * pageSize, perm: Read, off: 2}, 77 | {min: 15 * pageSize, max: 25 * pageSize, perm: Write, off: 1}, 78 | }, 79 | []region{ 80 | {min: 10 * pageSize, max: 15 * pageSize, perm: Read, off: 2}, 81 | {min: 15 * pageSize, max: 25 * pageSize, perm: Write, off: 1}, 82 | {min: 25 * pageSize, max: 30 * pageSize, perm: Read, off: int64(2 + 15*pageSize)}, 83 | }, 84 | }, 85 | { 86 | "Overlap two", 87 | []region{ 88 | {min: 10 * pageSize, max: 14 * pageSize, perm: Read, off: 1}, 89 | {min: 14 * pageSize, max: 18 * pageSize, perm: Write, off: 2}, 90 | {min: 12 * pageSize, max: 16 * pageSize, perm: Exec, off: 3}, 91 | }, 92 | []region{ 93 | {min: 10 * pageSize, max: 12 * pageSize, perm: Read, off: 1}, 94 | {min: 12 * pageSize, max: 16 * pageSize, perm: Exec, off: 3}, 95 | {min: 16 * pageSize, max: 18 * pageSize, perm: Write, off: int64(2 + 2*pageSize)}, 96 | }, 97 | }, 98 | { 99 | "Align max", 100 | []region{ 101 | {min: 10 * pageSize, max: 14*pageSize - 1, perm: Read, off: 1}, 102 | {min: 14 * pageSize, max: 18*pageSize - 1, perm: Write, off: 2}, 103 | {min: 12 * pageSize, max: 16*pageSize - 1, perm: Exec, off: 3}, 104 | }, 105 | []region{ 106 | {min: 10 * pageSize, max: 12 * pageSize, perm: Read, off: 1}, 107 | {min: 12 * pageSize, max: 16 * pageSize, perm: Exec, off: 3}, 108 | {min: 16 * pageSize, max: 18 * pageSize, perm: Write, off: int64(2 + 2*pageSize)}, 109 | }, 110 | }, 111 | { 112 | "Align min", 113 | []region{ 114 | {min: 10*pageSize + 1, max: 14 * pageSize, perm: Read, off: 1}, 115 | {min: 14*pageSize + 2, max: 18 * pageSize, perm: Write, off: 2}, 116 | {min: 12*pageSize + 3, max: 16 * pageSize, perm: Exec, off: 3}, 117 | }, 118 | []region{ 119 | {min: 10 * pageSize, max: 12 * pageSize, perm: Read, off: 0}, 120 | {min: 12 * pageSize, max: 16 * pageSize, perm: Exec, off: 0}, 121 | {min: 16 * pageSize, max: 18 * pageSize, perm: Write, off: int64(2 * pageSize)}, 122 | }, 123 | }, 124 | } 125 | for _, test := range tests { 126 | t.Run(test.name, func(t *testing.T) { 127 | mem := &splicedMemory{} 128 | for _, in := range test.in { 129 | mem.Add(in.min, in.max, in.perm, nil, in.off) 130 | } 131 | var got []region 132 | for _, m := range mem.mappings { 133 | got = append(got, region{m.min, m.max, m.perm, m.off}) 134 | } 135 | if !reflect.DeepEqual(got, test.want) { 136 | t.Errorf("mappings = %+v,\nwant %+v", got, test.want) 137 | } 138 | }) 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /internal/core/process_unix.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 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 | //go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris 6 | 7 | package core 8 | 9 | import ( 10 | "syscall" 11 | 12 | "golang.org/x/sys/unix" 13 | ) 14 | 15 | func init() { 16 | mapFile = func(fd int, offset int64, length int) (data []byte, err error) { 17 | return unix.Mmap(fd, offset, length, syscall.PROT_READ, syscall.MAP_SHARED) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /internal/core/read.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 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 | package core 6 | 7 | import ( 8 | "encoding/binary" 9 | "fmt" 10 | ) 11 | 12 | // All the Read* functions below will panic if something goes wrong. 13 | 14 | // ReadAt reads len(b) bytes at address a in the inferior 15 | // and stores them in b. 16 | func (p *Process) ReadAt(b []byte, a Address) { 17 | for { 18 | m := p.pageTable.findMapping(a) 19 | if m == nil { 20 | panic(fmt.Errorf("address %x is not mapped in the core file", a)) 21 | } 22 | n := copy(b, m.contents[a.Sub(m.min):]) 23 | if n == len(b) { 24 | return 25 | } 26 | // Modify request to get data from the next mapping. 27 | b = b[n:] 28 | a = a.Add(int64(n)) 29 | } 30 | } 31 | 32 | // ReadUint8 returns a uint8 read from address a of the inferior. 33 | func (p *Process) ReadUint8(a Address) uint8 { 34 | m := p.pageTable.findMapping(a) 35 | if m == nil { 36 | panic(fmt.Errorf("address %x is not mapped in the core file", a)) 37 | } 38 | return m.contents[a.Sub(m.min)] 39 | } 40 | 41 | // ReadUint16 returns a uint16 read from address a of the inferior. 42 | func (p *Process) ReadUint16(a Address) uint16 { 43 | m := p.pageTable.findMapping(a) 44 | if m == nil { 45 | panic(fmt.Errorf("address %x is not mapped in the core file", a)) 46 | } 47 | b := m.contents[a.Sub(m.min):] 48 | if len(b) < 2 { 49 | var buf [2]byte 50 | b = buf[:] 51 | p.ReadAt(b, a) 52 | } 53 | if p.meta.littleEndian { 54 | return binary.LittleEndian.Uint16(b) 55 | } 56 | return binary.BigEndian.Uint16(b) 57 | } 58 | 59 | // ReadUint32 returns a uint32 read from address a of the inferior. 60 | func (p *Process) ReadUint32(a Address) uint32 { 61 | m := p.pageTable.findMapping(a) 62 | if m == nil { 63 | panic(fmt.Errorf("address %x is not mapped in the core file", a)) 64 | } 65 | b := m.contents[a.Sub(m.min):] 66 | if len(b) < 4 { 67 | var buf [4]byte 68 | b = buf[:] 69 | p.ReadAt(b, a) 70 | } 71 | if p.meta.littleEndian { 72 | return binary.LittleEndian.Uint32(b) 73 | } 74 | return binary.BigEndian.Uint32(b) 75 | } 76 | 77 | // ReadUint64 returns a uint64 read from address a of the inferior. 78 | func (p *Process) ReadUint64(a Address) uint64 { 79 | m := p.pageTable.findMapping(a) 80 | if m == nil { 81 | panic(fmt.Errorf("address %x is not mapped in the core file", a)) 82 | } 83 | b := m.contents[a.Sub(m.min):] 84 | if len(b) < 8 { 85 | var buf [8]byte 86 | b = buf[:] 87 | p.ReadAt(b, a) 88 | } 89 | if p.meta.littleEndian { 90 | return binary.LittleEndian.Uint64(b) 91 | } 92 | return binary.BigEndian.Uint64(b) 93 | } 94 | 95 | // ReadInt8 returns an int8 read from address a of the inferior. 96 | func (p *Process) ReadInt8(a Address) int8 { 97 | return int8(p.ReadUint8(a)) 98 | } 99 | 100 | // ReadInt16 returns an int16 read from address a of the inferior. 101 | func (p *Process) ReadInt16(a Address) int16 { 102 | return int16(p.ReadUint16(a)) 103 | } 104 | 105 | // ReadInt32 returns an int32 read from address a of the inferior. 106 | func (p *Process) ReadInt32(a Address) int32 { 107 | return int32(p.ReadUint32(a)) 108 | } 109 | 110 | // ReadInt64 returns an int64 read from address a of the inferior. 111 | func (p *Process) ReadInt64(a Address) int64 { 112 | return int64(p.ReadUint64(a)) 113 | } 114 | 115 | // ReadUintptr returns a uint of pointer size read from address a of the inferior. 116 | func (p *Process) ReadUintptr(a Address) uint64 { 117 | if p.meta.ptrSize == 4 { 118 | return uint64(p.ReadUint32(a)) 119 | } 120 | return p.ReadUint64(a) 121 | } 122 | 123 | // ReadInt returns an int (of pointer size) read from address a of the inferior. 124 | func (p *Process) ReadInt(a Address) int64 { 125 | if p.meta.ptrSize == 4 { 126 | return int64(p.ReadInt32(a)) 127 | } 128 | return p.ReadInt64(a) 129 | } 130 | 131 | // ReadPtr returns a pointer loaded from address a of the inferior. 132 | func (p *Process) ReadPtr(a Address) Address { 133 | return Address(p.ReadUintptr(a)) 134 | } 135 | 136 | // ReadCString reads a null-terminated string starting at address a. 137 | func (p *Process) ReadCString(a Address) string { 138 | for n := int64(0); ; n++ { 139 | if p.ReadUint8(a.Add(n)) == 0 { 140 | b := make([]byte, n) 141 | p.ReadAt(b, a) 142 | return string(b) 143 | } 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /internal/core/testdata/README: -------------------------------------------------------------------------------- 1 | This directory contains a simple core file for use in testing. 2 | 3 | It was generated by running the following program with go1.9.0. 4 | 5 | package main 6 | 7 | func main() { 8 | _ = *(*int)(nil) 9 | } 10 | 11 | The core file includes the executable by reference using an absolute 12 | path. The executable was at /tmp/test, so that path is reproduced 13 | here so the core dump reader can find the executable using this 14 | testdata directory as the base directory. 15 | -------------------------------------------------------------------------------- /internal/core/testdata/core: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang/debug/dbb23ec93454284381b98bee9284160ed2c67a51/internal/core/testdata/core -------------------------------------------------------------------------------- /internal/core/testdata/tmp/test: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang/debug/dbb23ec93454284381b98bee9284160ed2c67a51/internal/core/testdata/tmp/test -------------------------------------------------------------------------------- /internal/core/thread.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 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 | package core 6 | 7 | // A Thread represents an operating system thread. 8 | type Thread struct { 9 | pid uint64 // thread/process ID 10 | regs []Register // set depends on arch 11 | pc Address // program counter 12 | sp Address // stack pointer 13 | } 14 | 15 | type Register struct { 16 | Name string 17 | Value uint64 18 | } 19 | 20 | func (t *Thread) Pid() uint64 { 21 | return t.pid 22 | } 23 | 24 | // Regs returns the set of register values for the thread. 25 | // What registers go where is architecture-dependent. 26 | func (t *Thread) Regs() []Register { 27 | return t.regs 28 | } 29 | 30 | func (t *Thread) PC() Address { 31 | return t.pc 32 | } 33 | 34 | func (t *Thread) SP() Address { 35 | return t.sp 36 | } 37 | 38 | // TODO: link register? 39 | -------------------------------------------------------------------------------- /internal/gocore/dominator.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 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 | package gocore 6 | 7 | import ( 8 | "fmt" 9 | "io" 10 | ) 11 | 12 | // Code liberally adapted from cmd/compile/internal/ssa/dom.go and 13 | // x/tools/go/ssa/dom.go. 14 | // 15 | // We use the algorithm described in Lengauer & Tarjan. 1979. A fast 16 | // algorithm for finding dominators in a flowgraph. 17 | // http://doi.acm.org/10.1145/357062.357071 18 | // 19 | // We also apply the optimizations to SLT described in Georgiadis et 20 | // al, Finding Dominators in Practice, JGAA 2006, 21 | // http://jgaa.info/accepted/2006/GeorgiadisTarjanWerneck2006.10.1.pdf 22 | // to avoid the need for buckets of size > 1. 23 | 24 | // Vertex name, as used in the papers. 25 | // 0 -> the pseudo-root, a made-up object that parents all the GC roots. 26 | // 1...nRoots -> a root, found at p.rootIdx[#-1] 27 | // nRoots+1... -> an object, with object index # - nRoots - 1 28 | type vName int 29 | 30 | const pseudoRoot vName = 0 31 | 32 | // Vertex number, assigned in the DFS traversal in step 1. 33 | type vNumber int 34 | 35 | type ltDom struct { 36 | p *Process 37 | 38 | // mapping from object ID to object 39 | objs []Object 40 | 41 | // number -> name 42 | vertices []vName 43 | 44 | // name -> parent name 45 | parents []vName 46 | 47 | // name -> vertex number before step 2 or semidominator number after. 48 | semis []vNumber 49 | 50 | // name -> ancestor name 51 | ancestor []vName 52 | labels []vName 53 | 54 | // name -> dominator name 55 | idom []vName 56 | 57 | nVertices, nRoots int 58 | } 59 | 60 | type dominators struct { 61 | p *Process 62 | 63 | // mapping from object ID to object 64 | objs []Object 65 | 66 | // name -> dominator name 67 | idom []vName 68 | 69 | // Reverse dominator tree edges, stored just like the ones in Process. name -> child name. 70 | ridx []int 71 | redge []vName 72 | 73 | // Retained size for each vertex. name -> retained size. 74 | size []int64 75 | } 76 | 77 | func (p *Process) calculateDominators() *dominators { 78 | lt := runLT(p) 79 | d := dominators{p: p, idom: lt.idom, objs: lt.objs} 80 | lt = ltDom{} 81 | 82 | d.reverse() 83 | d.calcSize(p) 84 | 85 | return &d 86 | } 87 | 88 | func runLT(p *Process) ltDom { 89 | p.typeHeap() 90 | p.reverseEdges() 91 | 92 | nVertices := 1 + len(p.rootIdx) + p.nObj 93 | lt := ltDom{ 94 | p: p, 95 | nRoots: len(p.rootIdx), 96 | nVertices: nVertices, 97 | objs: make([]Object, p.nObj), 98 | vertices: make([]vName, nVertices), 99 | parents: make([]vName, nVertices), 100 | semis: make([]vNumber, nVertices), 101 | ancestor: make([]vName, nVertices), 102 | labels: make([]vName, nVertices), 103 | idom: make([]vName, nVertices), 104 | } 105 | // TODO: increment all the names and use 0 as the uninitialized value. 106 | for i := range lt.semis { 107 | lt.semis[i] = -1 108 | } 109 | for i := range lt.ancestor { 110 | lt.ancestor[i] = -1 111 | } 112 | for i := range lt.labels { 113 | lt.labels[i] = vName(i) 114 | } 115 | lt.initialize() 116 | lt.calculate() 117 | return lt 118 | } 119 | 120 | // initialize implements step 1 of LT. 121 | func (d *ltDom) initialize() { 122 | type workItem struct { 123 | name vName 124 | parentName vName 125 | } 126 | 127 | // Initialize objs for mapping from object index back to Object. 128 | i := 0 129 | d.p.ForEachObject(func(x Object) bool { 130 | d.objs[i] = x 131 | i++ 132 | return true 133 | }) 134 | 135 | // Add roots to the work stack, essentially pretending to visit 136 | // the pseudo-root, numbering it 0. 137 | d.semis[pseudoRoot] = 0 138 | d.parents[pseudoRoot] = -1 139 | d.vertices[0] = pseudoRoot 140 | var work []workItem 141 | for i := 1; i < 1+d.nRoots; i++ { 142 | work = append(work, workItem{name: vName(i), parentName: 0}) 143 | } 144 | 145 | n := vNumber(1) // 0 was the pseudo-root. 146 | 147 | // Build the spanning tree, assigning vertex numbers to each object 148 | // and initializing semi and parent. 149 | for len(work) != 0 { 150 | item := work[len(work)-1] 151 | work = work[:len(work)-1] 152 | 153 | if d.semis[item.name] != -1 { 154 | continue 155 | } 156 | 157 | d.semis[item.name] = n 158 | d.parents[item.name] = item.parentName 159 | d.vertices[n] = item.name 160 | n++ 161 | 162 | visitChild := func(_ int64, child Object, _ int64) bool { 163 | childIdx, _ := d.p.findObjectIndex(d.p.Addr(child)) 164 | work = append(work, workItem{name: vName(childIdx + d.nRoots + 1), parentName: item.name}) 165 | return true 166 | } 167 | 168 | root, object := d.findVertexByName(item.name) 169 | if root != nil { 170 | d.p.ForEachRootPtr(root, visitChild) 171 | } else { 172 | d.p.ForEachPtr(object, visitChild) 173 | } 174 | 175 | } 176 | } 177 | 178 | // findVertexByName returns the root/object named by n, or nil,0 for the pseudo-root. 179 | func (d *ltDom) findVertexByName(n vName) (*Root, Object) { 180 | if n == 0 { 181 | return nil, 0 182 | } 183 | if int(n) < len(d.p.rootIdx)+1 { 184 | return d.p.rootIdx[n-1], 0 185 | } 186 | return nil, d.objs[int(n)-len(d.p.rootIdx)-1] 187 | } 188 | func (d *dominators) findVertexByName(n vName) (*Root, Object) { 189 | if n == 0 { 190 | return nil, 0 191 | } 192 | if int(n) < len(d.p.rootIdx)+1 { 193 | return d.p.rootIdx[n-1], 0 194 | } 195 | return nil, d.objs[int(n)-len(d.p.rootIdx)-1] 196 | } 197 | 198 | // calculate runs the main part of LT. 199 | func (d *ltDom) calculate() { 200 | // name -> bucket (a name), per Georgiadis. 201 | buckets := make([]vName, d.nVertices) 202 | for i := range buckets { 203 | buckets[i] = vName(i) 204 | } 205 | 206 | for i := vNumber(len(d.vertices)) - 1; i > 0; i-- { 207 | w := d.vertices[i] 208 | 209 | // Step 3. Implicitly define the immediate dominator of each node. 210 | for v := buckets[w]; v != w; v = buckets[v] { 211 | u := d.eval(v) 212 | if d.semis[u] < d.semis[v] { 213 | d.idom[v] = u 214 | } else { 215 | d.idom[v] = w 216 | } 217 | } 218 | 219 | // Step 2. Compute the semidominators of all nodes. 220 | root, obj := d.findVertexByName(w) 221 | // This loop never visits the pseudo-root. 222 | if root != nil { 223 | u := d.eval(pseudoRoot) 224 | if d.semis[u] < d.semis[w] { 225 | d.semis[w] = d.semis[u] 226 | } 227 | } else { 228 | d.p.ForEachReversePtr(obj, func(x Object, r *Root, _, _ int64) bool { 229 | var v int 230 | if r != nil { 231 | v = r.id + 1 232 | } else { 233 | v, _ = d.p.findObjectIndex(d.p.Addr(x)) 234 | v += d.nRoots + 1 235 | } 236 | u := d.eval(vName(v)) 237 | if d.semis[u] < d.semis[w] { 238 | d.semis[w] = d.semis[u] 239 | } 240 | return true 241 | }) 242 | } 243 | 244 | d.link(d.parents[w], w) 245 | 246 | if d.parents[w] == d.vertices[d.semis[w]] { 247 | d.idom[w] = d.parents[w] 248 | } else { 249 | buckets[w] = buckets[d.vertices[d.semis[w]]] 250 | buckets[d.vertices[d.semis[w]]] = w 251 | } 252 | } 253 | 254 | // The final 'Step 3' is now outside the loop. 255 | for v := buckets[pseudoRoot]; v != pseudoRoot; v = buckets[v] { 256 | d.idom[v] = pseudoRoot 257 | } 258 | 259 | // Step 4. Explicitly define the immediate dominator of each 260 | // node, in preorder. 261 | for _, w := range d.vertices[1:] { 262 | if d.idom[w] != d.vertices[d.semis[w]] { 263 | d.idom[w] = d.idom[d.idom[w]] 264 | } 265 | } 266 | } 267 | 268 | // eval is EVAL from the papers. 269 | func (d *ltDom) eval(v vName) vName { 270 | if d.ancestor[v] == -1 { 271 | return v 272 | } 273 | d.compress(v) 274 | return d.labels[v] 275 | } 276 | 277 | // compress is COMPRESS from the papers. 278 | func (d *ltDom) compress(v vName) { 279 | var stackBuf [20]vName 280 | stack := stackBuf[:0] 281 | for d.ancestor[d.ancestor[v]] != -1 { 282 | stack = append(stack, v) 283 | v = d.ancestor[v] 284 | } 285 | 286 | for len(stack) != 0 { 287 | v := stack[len(stack)-1] 288 | stack = stack[:len(stack)-1] 289 | 290 | if d.semis[d.labels[d.ancestor[v]]] < d.semis[d.labels[v]] { 291 | d.labels[v] = d.labels[d.ancestor[v]] 292 | } 293 | d.ancestor[v] = d.ancestor[d.ancestor[v]] 294 | } 295 | } 296 | 297 | // link is LINK from the papers. 298 | func (d *ltDom) link(v, w vName) { 299 | d.ancestor[w] = v 300 | } 301 | 302 | // reverse computes and stores reverse edges for each vertex. 303 | func (d *dominators) reverse() { 304 | // One inbound edge per vertex. Then we need an extra so that you can 305 | // always look at ridx[i+1], and another for working storage while 306 | // populating redge. 307 | cnt := make([]int, len(d.idom)+2) 308 | 309 | // Fill cnt[2:] with the number of outbound edges for each vertex. 310 | tmp := cnt[2:] 311 | for _, idom := range d.idom { 312 | tmp[idom]++ 313 | } 314 | 315 | // Make tmp cumulative. After this step, cnt[1:] is what we want for 316 | // ridx, but the next step messes it up. 317 | var n int 318 | for idx, c := range tmp { 319 | n += c 320 | tmp[idx] = n 321 | } 322 | 323 | // Store outbound edges in redge, using cnt[1:] as the index to store 324 | // the next edge for each vertex. After we're done, everything's been 325 | // shifted over one, and cnt is ridx. 326 | redge := make([]vName, len(d.idom)) 327 | tmp = cnt[1:] 328 | for i, idom := range d.idom { 329 | redge[tmp[idom]] = vName(i) 330 | tmp[idom]++ 331 | } 332 | d.redge, d.ridx = redge, cnt[:len(cnt)-1] 333 | } 334 | 335 | type dfsMode int 336 | 337 | const ( 338 | down dfsMode = iota 339 | up 340 | ) 341 | 342 | // calcSize calculates the total retained size for each vertex. 343 | func (d *dominators) calcSize(p *Process) { 344 | d.size = make([]int64, len(d.idom)) 345 | type workItem struct { 346 | v vName 347 | mode dfsMode 348 | } 349 | work := []workItem{{pseudoRoot, down}} 350 | 351 | for len(work) > 0 { 352 | item := &work[len(work)-1] 353 | 354 | kids := d.redge[d.ridx[item.v]:d.ridx[item.v+1]] 355 | if item.mode == down && len(kids) != 0 { 356 | item.mode = up 357 | for _, w := range kids { 358 | if w == 0 { 359 | // bogus self-edge. Ignore. 360 | continue 361 | } 362 | work = append(work, workItem{w, down}) 363 | } 364 | continue 365 | } 366 | 367 | work = work[:len(work)-1] 368 | 369 | root, obj := d.findVertexByName(item.v) 370 | var size int64 371 | switch { 372 | case item.v == pseudoRoot: 373 | break 374 | case root != nil: 375 | size += root.Type.Size 376 | default: 377 | size += p.Size(obj) 378 | } 379 | for _, w := range kids { 380 | size += d.size[w] 381 | } 382 | d.size[item.v] = size 383 | } 384 | } 385 | 386 | func (d *ltDom) dot(w io.Writer) { 387 | fmt.Fprintf(w, "digraph %s {\nrankdir=\"LR\"\n", "dominators") 388 | for number, name := range d.vertices { 389 | var label string 390 | root, obj := d.findVertexByName(name) 391 | 392 | switch { 393 | case name == 0: 394 | label = "pseudo-root" 395 | case root != nil: 396 | typeName := root.Type.Name 397 | if len(typeName) > 30 { 398 | typeName = typeName[:30] 399 | } 400 | label = fmt.Sprintf("root %s (type %s)", root.Name, typeName) 401 | default: 402 | typ, _ := d.p.Type(obj) 403 | var typeName string 404 | if typ != nil { 405 | typeName = typ.Name 406 | if len(typeName) > 30 { 407 | typeName = typeName[:30] 408 | } 409 | } 410 | label = fmt.Sprintf("object %#x (type %s)", obj, typeName) 411 | } 412 | 413 | fmt.Fprintf(w, "\t%v [label=\"name #%04v, number #%04v: %s\"]\n", name, name, number, label) 414 | } 415 | 416 | fmt.Fprint(w, "\n\n") 417 | for v, parent := range d.parents { 418 | fmt.Fprintf(w, "\t%v -> %v [style=\"solid\"]\n", parent, v) 419 | } 420 | for v, idom := range d.idom { 421 | fmt.Fprintf(w, "\t%v -> %v [style=\"bold\"]\n", idom, v) 422 | } 423 | for v, sdom := range d.semis { 424 | fmt.Fprintf(w, "\t%v -> %v [style=\"dotted\"]\n", v, d.vertices[sdom]) 425 | } 426 | fmt.Fprint(w, "}\n") 427 | } 428 | -------------------------------------------------------------------------------- /internal/gocore/dominator_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 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 | //go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris 6 | 7 | package gocore 8 | 9 | import ( 10 | "fmt" 11 | "testing" 12 | ) 13 | 14 | func checkDominator(t *testing.T, d ltDom) bool { 15 | t.Helper() 16 | // Build pointer-y graph. 17 | pRoot := sanityVertex{} 18 | roots := make([]sanityVertex, d.nRoots) 19 | objects := make([]sanityVertex, d.p.nObj) 20 | 21 | for i, r := range d.p.rootIdx { 22 | v := &roots[i] 23 | v.root = r 24 | pRoot.succ = append(pRoot.succ, v) 25 | v.pred = append(v.pred, &pRoot) 26 | d.p.ForEachRootPtr(r, func(_ int64, x Object, _ int64) bool { 27 | idx, _ := d.p.findObjectIndex(d.p.Addr(x)) 28 | v.succ = append(v.succ, &objects[idx]) 29 | objects[idx].pred = append(objects[idx].pred, v) 30 | return true 31 | }) 32 | } 33 | d.p.ForEachObject(func(x Object) bool { 34 | xIdx, _ := d.p.findObjectIndex(d.p.Addr(x)) 35 | v := &objects[xIdx] 36 | v.obj = x 37 | d.p.ForEachPtr(x, func(_ int64, y Object, _ int64) bool { 38 | yIdx, _ := d.p.findObjectIndex(d.p.Addr(y)) 39 | v.succ = append(v.succ, &objects[yIdx]) 40 | objects[yIdx].pred = append(objects[yIdx].pred, v) 41 | return true 42 | }) 43 | return true 44 | }) 45 | 46 | // Precompute postorder traversal. 47 | var postorder []*sanityVertex 48 | type workItem struct { 49 | v *sanityVertex 50 | mode dfsMode 51 | } 52 | seen := make(map[*sanityVertex]bool, d.nVertices) 53 | work := []workItem{{&pRoot, down}} 54 | for len(work) > 0 { 55 | item := &work[len(work)-1] 56 | 57 | if item.mode == down && len(item.v.succ) != 0 { 58 | item.mode = up 59 | for _, w := range item.v.succ { 60 | // Only push each node once. 61 | if seen[w] { 62 | continue 63 | } 64 | seen[w] = true 65 | 66 | work = append(work, workItem{w, down}) 67 | } 68 | continue 69 | } 70 | 71 | work = work[:len(work)-1] 72 | postorder = append(postorder, item.v) 73 | } 74 | 75 | // Make map from block id to order index (for intersect call) 76 | postnum := make(map[*sanityVertex]int, d.nVertices) 77 | for i, b := range postorder { 78 | postnum[b] = i 79 | } 80 | 81 | // Make the pseudo-root a self-loop 82 | pRoot.idom = &pRoot 83 | if postnum[&pRoot] != len(postorder)-1 { 84 | panic("pseudo-root not last in postorder") 85 | } 86 | 87 | // Compute relaxation of idom entries 88 | for { 89 | changed := false 90 | 91 | for i := len(postorder) - 2; i >= 0; i-- { 92 | v := postorder[i] 93 | var d *sanityVertex 94 | 95 | for _, pred := range v.pred { 96 | if pred.idom == nil { 97 | continue 98 | } 99 | 100 | if d == nil { 101 | d = pred 102 | continue 103 | } 104 | 105 | d = intersect(d, pred, postnum) 106 | } 107 | if v.idom != d { 108 | v.idom = d 109 | changed = true 110 | } 111 | } 112 | 113 | if !changed { 114 | break 115 | } 116 | } 117 | 118 | pRoot.idom = nil 119 | 120 | getVertex := func(n vName) *sanityVertex { 121 | r, o := d.findVertexByName(n) 122 | switch { 123 | case n == pseudoRoot: 124 | return &pRoot 125 | case r != nil: 126 | return &roots[r.id] 127 | default: 128 | idx, _ := d.p.findObjectIndex(d.p.Addr(o)) 129 | return &objects[idx] 130 | } 131 | } 132 | 133 | matches := true 134 | for vertName, domName := range d.idom { 135 | if vName(vertName) == pseudoRoot { 136 | continue 137 | } 138 | vert := getVertex(vName(vertName)) 139 | dom := getVertex(domName) 140 | 141 | if vert.idom != dom { 142 | matches = false 143 | t.Errorf("Mismatch in idom for %v, name #%04v: fast reports %v, sanity reports %v\n", vert.String(d.p), vertName, dom.String(d.p), vert.idom.String(d.p)) 144 | } 145 | } 146 | return matches 147 | } 148 | 149 | func intersect(v, w *sanityVertex, postnum map[*sanityVertex]int) *sanityVertex { 150 | for v != w { 151 | if postnum[v] < postnum[w] { 152 | v = v.idom 153 | } else { 154 | w = w.idom 155 | } 156 | } 157 | return v 158 | } 159 | 160 | type sanityVertex struct { 161 | root *Root 162 | obj Object 163 | pred []*sanityVertex 164 | succ []*sanityVertex 165 | idom *sanityVertex 166 | } 167 | 168 | func (v *sanityVertex) String(p *Process) string { 169 | switch { 170 | case v.root != nil: 171 | return fmt.Sprintf("root %s (type %s)", v.root.Name, v.root.Type) 172 | case v.obj != 0: 173 | typ, _ := p.Type(v.obj) 174 | var typeName string 175 | if typ != nil { 176 | typeName = typ.Name 177 | } 178 | return fmt.Sprintf("object %#x (type %s)", v.obj, typeName) 179 | default: 180 | return "pseudo-root" 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /internal/gocore/gocore_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Go Authors. All rights reserved. 2 | // Use of this srcFile code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | //go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris 6 | 7 | package gocore 8 | 9 | import ( 10 | "bytes" 11 | "cmp" 12 | "errors" 13 | "fmt" 14 | "os" 15 | "os/exec" 16 | "path/filepath" 17 | "runtime" 18 | "strings" 19 | "testing" 20 | 21 | "golang.org/x/debug/internal/core" 22 | "golang.org/x/debug/internal/testenv" 23 | "golang.org/x/sys/unix" 24 | ) 25 | 26 | func loadCore(t *testing.T, corePath, base, exePath string) *Process { 27 | t.Helper() 28 | c, err := core.Core(corePath, base, exePath) 29 | if err != nil { 30 | t.Fatalf("can't load test core file: %s", err) 31 | } 32 | p, err := Core(c) 33 | if err != nil { 34 | t.Fatalf("can't parse Go core: %s", err) 35 | } 36 | return p 37 | } 38 | 39 | // createAndLoadCore generates a core from a binary built with runtime.GOROOT(). 40 | func createAndLoadCore(t *testing.T, srcFile string, buildFlags, env []string) *Process { 41 | t.Helper() 42 | testenv.MustHaveGoBuild(t) 43 | switch runtime.GOOS { 44 | case "js", "plan9", "windows": 45 | t.Skipf("skipping: no core files on %s", runtime.GOOS) 46 | } 47 | if runtime.GOARCH != "amd64" { 48 | t.Skipf("skipping: only parsing of amd64 cores is supported") 49 | } 50 | 51 | cleanup := setupCorePattern(t) 52 | defer cleanup() 53 | 54 | if err := adjustCoreRlimit(t); err != nil { 55 | t.Fatalf("unable to adjust core limit, can't test generated core dump: %v", err) 56 | } 57 | 58 | dir := t.TempDir() 59 | file, output, err := generateCore(srcFile, dir, buildFlags, env) 60 | t.Logf("crasher output: %s", output) 61 | if err != nil { 62 | t.Fatalf("generateCore() got err %v want nil", err) 63 | } 64 | return loadCore(t, file, "", "") 65 | } 66 | 67 | func setupCorePattern(t *testing.T) func() { 68 | if runtime.GOOS != "linux" { 69 | t.Skip("skipping: core file pattern check implemented only for Linux") 70 | } 71 | 72 | const ( 73 | corePatternPath = "/proc/sys/kernel/core_pattern" 74 | newPattern = "core" 75 | ) 76 | 77 | b, err := os.ReadFile(corePatternPath) 78 | if err != nil { 79 | t.Fatalf("unable to read core pattern: %v", err) 80 | } 81 | pattern := string(b) 82 | t.Logf("original core pattern: %s", pattern) 83 | 84 | // We want a core file in the working directory containing "core" in 85 | // the name. If the pattern already matches this, there is nothing to 86 | // do. What we don't want: 87 | // - Pipe to another process 88 | // - Path components 89 | if !strings.HasPrefix(pattern, "|") && !strings.Contains(pattern, "/") && strings.Contains(pattern, "core") { 90 | // Pattern is fine as-is, nothing to do. 91 | return func() {} 92 | } 93 | 94 | if os.Getenv("GO_BUILDER_NAME") == "" { 95 | // Don't change the core pattern on arbitrary machines, as it 96 | // has global effect. 97 | t.Skipf("skipping: unable to generate core file due to incompatible core pattern %q; set %s to %q", pattern, corePatternPath, newPattern) 98 | } 99 | 100 | t.Logf("updating core pattern to %q", newPattern) 101 | 102 | err = os.WriteFile(corePatternPath, []byte(newPattern), 0) 103 | if err != nil { 104 | t.Skipf("skipping: unable to write core pattern: %v", err) 105 | } 106 | 107 | return func() { 108 | t.Logf("resetting core pattern to %q", pattern) 109 | err := os.WriteFile(corePatternPath, []byte(pattern), 0) 110 | if err != nil { 111 | t.Errorf("unable to write core pattern back to original value: %v", err) 112 | } 113 | } 114 | } 115 | 116 | func adjustCoreRlimit(t *testing.T) error { 117 | var limit unix.Rlimit 118 | if err := unix.Getrlimit(unix.RLIMIT_CORE, &limit); err != nil { 119 | return fmt.Errorf("getrlimit(RLIMIT_CORE) error: %v", err) 120 | } 121 | 122 | if limit.Max == 0 { 123 | return fmt.Errorf("RLIMIT_CORE maximum is 0, core dumping is not possible") 124 | } 125 | 126 | // Increase the core limit to the maximum (hard limit), if the current soft 127 | // limit is lower. 128 | if limit.Cur < limit.Max { 129 | oldLimit := limit 130 | limit.Cur = limit.Max 131 | if err := unix.Setrlimit(unix.RLIMIT_CORE, &limit); err != nil { 132 | return fmt.Errorf("setrlimit(RLIMIT_CORE, %+v) error: %v", limit, err) 133 | } 134 | t.Logf("adjusted RLIMIT_CORE from %+v to %+v", oldLimit, limit) 135 | } 136 | 137 | return nil 138 | } 139 | 140 | // doRunCrasher spawns the supplied cmd, propagating parent state (see 141 | // [exec.Cmd.Run]), and returns an error if the process failed to start or did 142 | // *NOT* crash. 143 | func doRunCrasher(cmd *exec.Cmd) (pid int, output []byte, err error) { 144 | var b bytes.Buffer 145 | cmd.Stdout = &b 146 | cmd.Stderr = &b 147 | 148 | runtime.LockOSThread() // Propagate parent state, see [exec.Cmd.Run]. 149 | err = cmd.Run() 150 | runtime.UnlockOSThread() 151 | 152 | // We expect a crash. 153 | var ee *exec.ExitError 154 | if !errors.As(err, &ee) { 155 | return cmd.Process.Pid, b.Bytes(), fmt.Errorf("crasher did not crash, got err %T %w", err, err) 156 | } 157 | return cmd.Process.Pid, b.Bytes(), nil 158 | } 159 | 160 | func generateCore(srcFile, dir string, buildFlags, env []string) (string, []byte, error) { 161 | goTool, err := testenv.GoTool() 162 | if err != nil { 163 | return "", nil, fmt.Errorf("cannot find go tool: %w", err) 164 | } 165 | 166 | cwd, err := os.Getwd() 167 | if err != nil { 168 | return "", nil, fmt.Errorf("erroring getting cwd: %w", err) 169 | } 170 | 171 | srcPath := filepath.Join(cwd, srcFile) 172 | argv := []string{"build"} 173 | argv = append(argv, buildFlags...) 174 | argv = append(argv, "-o", filepath.Join(dir, "test.exe"), "./"+filepath.Base(srcFile)) 175 | cmd := exec.Command(goTool, argv...) 176 | cmd.Dir = filepath.Dir(srcPath) 177 | 178 | b, err := cmd.CombinedOutput() 179 | if err != nil { 180 | return "", nil, fmt.Errorf("error building crasher: %w\n%s", err, string(b)) 181 | } 182 | 183 | cmd = exec.Command("./test.exe") 184 | cmd.Env = append(os.Environ(), "GOMAXPROCS=2", "GOTRACEBACK=crash") 185 | cmd.Env = append(cmd.Env, env...) 186 | cmd.Dir = dir 187 | _, b, err = doRunCrasher(cmd) 188 | if err != nil { 189 | return "", b, err 190 | } 191 | 192 | // Look for any file with "core" in the name. 193 | dd, err := os.ReadDir(dir) 194 | if err != nil { 195 | return "", b, fmt.Errorf("error reading output directory: %w", err) 196 | } 197 | 198 | for _, d := range dd { 199 | if strings.Contains(d.Name(), "core") { 200 | return filepath.Join(dir, d.Name()), b, nil 201 | } 202 | } 203 | 204 | names := make([]string, 0, len(dd)) 205 | for _, d := range dd { 206 | names = append(names, d.Name()) 207 | } 208 | return "", b, fmt.Errorf("did not find core file in %+v", names) 209 | } 210 | 211 | func checkProcess(t *testing.T, p *Process) { 212 | t.Helper() 213 | if gs := p.Goroutines(); len(gs) == 0 { 214 | t.Error("len(p.Goroutines()) == 0, want >0") 215 | } 216 | 217 | const heapName = "heap" 218 | heapStat := p.Stats().Sub(heapName) 219 | if heapStat == nil || heapStat.Value == 0 { 220 | t.Errorf("stat[%q].Size == 0, want >0", heapName) 221 | } 222 | 223 | lt := runLT(p) 224 | if !checkDominator(t, lt) { 225 | t.Errorf("sanityCheckDominator(...) = false, want true") 226 | } 227 | } 228 | 229 | type parameters struct { 230 | buildFlags []string 231 | env []string 232 | } 233 | 234 | func (p parameters) String() string { 235 | var parts []string 236 | if len(p.buildFlags) != 0 { 237 | parts = append(parts, "gcflags="+strings.Join(p.buildFlags, ",")) 238 | } 239 | if len(p.env) != 0 { 240 | parts = append(parts, "env="+strings.Join(p.env, ",")) 241 | } 242 | return cmp.Or(strings.Join(parts, "%"), "default") 243 | } 244 | 245 | // Variations in build and execution environments common to different tests. 246 | var variations = [...]parameters{ 247 | {}, // Default. 248 | {buildFlags: []string{"-buildmode=pie"}}, 249 | {buildFlags: []string{"-buildmode=pie"}, env: []string{"GO_DEBUG_TEST_COREDUMP_FILTER=0x3f"}}, 250 | } 251 | 252 | func testSrcFiles(t *testing.T) []string { 253 | srcs, err := filepath.Glob("testdata/testprogs/*.go") 254 | if err != nil { 255 | t.Skipf("failed to find sources: %v", err) 256 | } 257 | return srcs 258 | } 259 | 260 | func TestVersions(t *testing.T) { 261 | t.Run("goroot", func(t *testing.T) { 262 | for _, test := range variations { 263 | for _, src := range testSrcFiles(t) { 264 | t.Run(test.String()+"/"+filepath.Base(src), func(t *testing.T) { 265 | p := createAndLoadCore(t, src, test.buildFlags, test.env) 266 | checkProcess(t, p) 267 | }) 268 | } 269 | } 270 | }) 271 | } 272 | 273 | func TestObjects(t *testing.T) { 274 | const largeObjectThreshold = 32768 275 | 276 | t.Run("goroot", func(t *testing.T) { 277 | for _, test := range variations { 278 | t.Run(test.String(), func(t *testing.T) { 279 | t.Run("bigslice.go", func(t *testing.T) { 280 | p := createAndLoadCore(t, "testdata/testprogs/bigslice.go", test.buildFlags, test.env) 281 | 282 | // Statistics to check. 283 | largeObjects := 0 // Number of objects larger than (or equal to largeObjectThreshold) 284 | bigSliceElemObjects := 0 285 | 286 | p.ForEachObject(func(x Object) bool { 287 | siz := p.Size(x) 288 | typ := typeName(p, x) 289 | //t.Logf("%s size=%d", typ, p.Size(x)) 290 | if siz >= largeObjectThreshold { 291 | largeObjects++ 292 | } 293 | switch typ { 294 | case "main.bigSliceElem": 295 | bigSliceElemObjects++ 296 | } 297 | return true 298 | }) 299 | if largeObjects != 3 { 300 | t.Errorf("expected exactly three object larger than %d, found %d", largeObjectThreshold, largeObjects) 301 | } 302 | 303 | // Check object counts. 304 | if want := 3 * (32 << 10); bigSliceElemObjects != want { 305 | t.Errorf("expected exactly %d main.bigSliceElem objects, found %d", want, bigSliceElemObjects) 306 | } 307 | }) 308 | t.Run("large.go", func(t *testing.T) { 309 | p := createAndLoadCore(t, "testdata/testprogs/large.go", test.buildFlags, test.env) 310 | 311 | // Statistics to check. 312 | largeObjects := 0 // Number of objects larger than (or equal to largeObjectThreshold) 313 | p.ForEachObject(func(x Object) bool { 314 | siz := p.Size(x) 315 | //typ := typeName(p, x) 316 | //t.Logf("%s size=%d", typ, p.Size(x)) 317 | if siz >= largeObjectThreshold { 318 | largeObjects++ 319 | } 320 | return true 321 | }) 322 | if largeObjects != 1 { 323 | t.Errorf("expected exactly one object larger than %d, found %d", largeObjectThreshold, largeObjects) 324 | } 325 | }) 326 | t.Run("trees.go", func(t *testing.T) { 327 | p := createAndLoadCore(t, "testdata/testprogs/trees.go", test.buildFlags, test.env) 328 | 329 | // Statistics to check. 330 | n := 0 331 | myPairObjects := 0 332 | anyNodeObjects := 0 333 | typeSafeNodeObjects := 0 334 | 335 | p.ForEachObject(func(x Object) bool { 336 | typ := typeName(p, x) 337 | //t.Logf("%s size=%d", typ, p.Size(x)) 338 | switch typ { 339 | case "main.myPair": 340 | myPairObjects++ 341 | case "main.anyNode": 342 | anyNodeObjects++ 343 | case "main.typeSafeNode[main.myPair]": 344 | typeSafeNodeObjects++ 345 | } 346 | n++ 347 | return true 348 | }) 349 | if n < 10 { 350 | t.Errorf("#objects = %d, want >10", n) 351 | } 352 | 353 | // Check object counts. 354 | const depth = 5 355 | const tsTrees = 3 356 | const anTrees = 2 357 | const nodes = 1< 1 { 383 | if repeat < n { 384 | name = fmt.Sprintf("[%d+%d?]%s", repeat, n-repeat, name) 385 | } else { 386 | name = fmt.Sprintf("[%d]%s", repeat, name) 387 | } 388 | } 389 | return name 390 | } 391 | -------------------------------------------------------------------------------- /internal/gocore/goroutine.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 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 | package gocore 6 | 7 | import ( 8 | "golang.org/x/debug/internal/core" 9 | ) 10 | 11 | type Goroutine struct { 12 | r region // inferior region holding the runtime.g 13 | stackSize int64 // current stack allocation 14 | frames []*Frame 15 | 16 | // TODO: defers, in-progress panics 17 | } 18 | 19 | // Stack returns the total allocated stack for g. 20 | func (g *Goroutine) Stack() int64 { 21 | return g.stackSize 22 | } 23 | 24 | // Addr returns the address of the runtime.g that identifies this goroutine. 25 | func (g *Goroutine) Addr() core.Address { 26 | return g.r.a 27 | } 28 | 29 | // Frames returns the list of frames on the stack of the Goroutine. 30 | // The first frame is the most recent one. 31 | // This list is post-optimization, so any inlined calls, tail calls, etc. 32 | // will not appear. 33 | func (g *Goroutine) Frames() []*Frame { 34 | return g.frames 35 | } 36 | 37 | // A Frame represents the local variables of a single Go function invocation. 38 | // (Note that in the presence of inlining, a Frame may contain local variables 39 | // for more than one Go function invocation.) 40 | type Frame struct { 41 | parent *Frame 42 | f *Func // function whose activation record this frame is 43 | pc core.Address // resumption point 44 | min, max core.Address // extent of stack frame 45 | 46 | // Set of locations that contain a live pointer. Note that this set 47 | // may contain locations outside the frame (in particular, the args 48 | // for the frame). 49 | Live map[core.Address]bool 50 | 51 | roots []*Root // GC roots in this frame 52 | 53 | // TODO: keep vars from dwarf around? 54 | } 55 | 56 | // Func returns the function for which this frame is an activation record. 57 | func (f *Frame) Func() *Func { 58 | return f.f 59 | } 60 | 61 | // Min returns the minimum address of this frame. 62 | func (f *Frame) Min() core.Address { 63 | return f.min 64 | } 65 | 66 | // Max returns the maximum address of this frame. 67 | func (f *Frame) Max() core.Address { 68 | return f.max 69 | } 70 | 71 | // PC returns the program counter of the next instruction to be executed by this frame. 72 | func (f *Frame) PC() core.Address { 73 | return f.pc 74 | } 75 | 76 | // Roots returns a list of all the garbage collection roots in the frame. 77 | func (f *Frame) Roots() []*Root { 78 | return f.roots 79 | } 80 | 81 | // Parent returns the parent frame of f, or nil if it is the top of the stack. 82 | func (f *Frame) Parent() *Frame { 83 | return f.parent 84 | } 85 | 86 | // A Func represents a Go function. 87 | type Func struct { 88 | r region // inferior region holding a runtime._func 89 | module *module 90 | name string 91 | entry core.Address 92 | frameSize pcTab // map from pc to frame size at that pc 93 | pcdata []int32 94 | funcdata []core.Address 95 | stackMap pcTab // map from pc to stack map # (index into locals and args bitmaps) 96 | methodValueFor *Func // if != nil, this Func is a method value wrapper, and *Func is the method 97 | closure *Type // the type to use for closures of this function. Lazily allocated. 98 | } 99 | 100 | // Name returns the name of the function, as reported by DWARF. 101 | // Names are opaque; do not depend on the format of the returned name. 102 | func (f *Func) Name() string { 103 | return f.name 104 | } 105 | 106 | // Entry returns the address of the entry point of f. 107 | func (f *Func) Entry() core.Address { 108 | return f.entry 109 | } 110 | -------------------------------------------------------------------------------- /internal/gocore/module.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 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 | package gocore 6 | 7 | import ( 8 | "fmt" 9 | "math" 10 | "sort" 11 | 12 | "golang.org/x/debug/internal/core" 13 | ) 14 | 15 | type module struct { 16 | r region // inferior region holding a runtime.moduledata 17 | types, etypes core.Address // range that holds all the runtime._type data in this module 18 | } 19 | 20 | func readModules(rtTypeByName map[string]*Type, rtConsts map[string]int64, rtGlobals map[string]region) ([]*module, *funcTab, error) { 21 | ms := rtGlobals["modulesSlice"].Deref() 22 | n := ms.SliceLen() 23 | var modules []*module 24 | var fnTab funcTab 25 | fnTab.byName = make(map[string]*Func) 26 | for i := int64(0); i < n; i++ { 27 | md := ms.SliceIndex(i).Deref() 28 | modules = append(modules, readModule(md, &fnTab, rtTypeByName, rtConsts)) 29 | } 30 | fnTab.sort() 31 | return modules, &fnTab, nil 32 | } 33 | 34 | func readModule(r region, fns *funcTab, rtTypeByName map[string]*Type, rtConsts map[string]int64) *module { 35 | m := &module{r: r} 36 | m.types = core.Address(r.Field("types").Uintptr()) 37 | m.etypes = core.Address(r.Field("etypes").Uintptr()) 38 | 39 | // Read the pc->function table 40 | pcln := r.Field("pclntable") 41 | var pctab, funcnametab region 42 | havePCtab := r.HasField("pctab") 43 | if havePCtab { 44 | // In 1.16, pclntable was split up into pctab and funcnametab. 45 | pctab = r.Field("pctab") 46 | funcnametab = r.Field("funcnametab") 47 | } 48 | ftab := r.Field("ftab") 49 | n := ftab.SliceLen() - 1 // last slot is a dummy, just holds entry 50 | for i := int64(0); i < n; i++ { 51 | ft := ftab.SliceIndex(i) 52 | min := m.textAddr(ft.Field("entryoff").Uint32()) 53 | max := m.textAddr(ftab.SliceIndex(i + 1).Field("entryoff").Uint32()) 54 | funcoff := int64(ft.Field("funcoff").Uint32()) 55 | fr := pcln.SliceIndex(funcoff).Cast(rtTypeByName["runtime._func"]) 56 | var f *Func 57 | if havePCtab { 58 | f = m.readFunc(fr, pctab, funcnametab, rtConsts) 59 | } else { 60 | f = m.readFunc(fr, pcln, pcln, rtConsts) 61 | } 62 | if f.entry != min { 63 | panic(fmt.Errorf("entry %x and min %x don't match for %s", f.entry, min, f.name)) 64 | } 65 | fns.add(min, max, f) 66 | } 67 | 68 | return m 69 | } 70 | 71 | // readFunc parses a runtime._func and returns a *Func. 72 | // r must have type runtime._func. 73 | // pcln must have type []byte and represent the module's pcln table region. 74 | func (m *module) readFunc(r region, pctab region, funcnametab region, rtConsts constsMap) *Func { 75 | f := &Func{module: m, r: r} 76 | f.entry = m.textAddr(r.Field("entryOff").Uint32()) 77 | nameOff := r.Field("nameOff").Int32() 78 | f.name = r.p.ReadCString(funcnametab.SliceIndex(int64(nameOff)).a) 79 | pcspIdx := int64(r.Field("pcsp").Uint32()) 80 | f.frameSize.read(r.p, pctab.SliceIndex(pcspIdx).a) 81 | 82 | // Parse pcdata and funcdata, which are laid out beyond the end of the _func. 83 | npcdata := r.Field("npcdata") 84 | n := npcdata.Uint32() 85 | nfd := r.Field("nfuncdata") 86 | a := nfd.a.Add(nfd.typ.Size) 87 | 88 | for i := uint32(0); i < n; i++ { 89 | f.pcdata = append(f.pcdata, r.p.ReadInt32(a)) 90 | a = a.Add(4) 91 | } 92 | 93 | n = uint32(nfd.Uint8()) 94 | for i := uint32(0); i < n; i++ { 95 | // Since 1.18, funcdata contains offsets from go.func.*. 96 | off := r.p.ReadUint32(a) 97 | if off == ^uint32(0) { 98 | // No entry. 99 | f.funcdata = append(f.funcdata, 0) 100 | } else { 101 | f.funcdata = append(f.funcdata, core.Address(m.r.Field("gofunc").Uintptr()+uint64(off))) 102 | } 103 | a = a.Add(4) 104 | } 105 | 106 | // Read pcln tables we need. 107 | if stackmap := int(rtConsts.get("internal/abi.PCDATA_StackMapIndex")); stackmap < len(f.pcdata) { 108 | f.stackMap.read(r.p, pctab.SliceIndex(int64(f.pcdata[stackmap])).a) 109 | } else { 110 | f.stackMap.setEmpty() 111 | } 112 | 113 | return f 114 | } 115 | 116 | // textAddr returns the address of a text offset. 117 | // 118 | // Equivalent to runtime.moduledata.textAddr. 119 | func (m *module) textAddr(off32 uint32) core.Address { 120 | off := uint64(off32) 121 | res := m.r.Field("text").Uintptr() + off 122 | 123 | textsectmap := m.r.Field("textsectmap") 124 | length := textsectmap.SliceLen() 125 | if length > 1 { 126 | for i := int64(0); i < length; i++ { 127 | sect := textsectmap.SliceIndex(i) 128 | 129 | vaddr := sect.Field("vaddr").Uintptr() 130 | end := sect.Field("end").Uintptr() 131 | baseaddr := sect.Field("baseaddr").Uintptr() 132 | 133 | if off >= vaddr && off < end || (i == length-1 && off == end) { 134 | res = baseaddr + off - vaddr 135 | } 136 | } 137 | } 138 | 139 | return core.Address(res) 140 | } 141 | 142 | type funcTabEntry struct { 143 | min, max core.Address 144 | f *Func 145 | } 146 | 147 | type funcTab struct { 148 | entries []funcTabEntry 149 | byName map[string]*Func 150 | } 151 | 152 | // add records that PCs in the range [min,max) map to function f. 153 | func (t *funcTab) add(min, max core.Address, f *Func) { 154 | t.byName[f.name] = f 155 | t.entries = append(t.entries, funcTabEntry{min: min, max: max, f: f}) 156 | } 157 | 158 | // sort must be called after all the adds, but before any find. 159 | func (t *funcTab) sort() { 160 | sort.Slice(t.entries, func(i, j int) bool { 161 | return t.entries[i].min < t.entries[j].min 162 | }) 163 | } 164 | 165 | // find finds a Func for the given address. sort must have been called already. 166 | func (t *funcTab) find(pc core.Address) *Func { 167 | n := sort.Search(len(t.entries), func(i int) bool { 168 | return t.entries[i].max > pc 169 | }) 170 | if n == len(t.entries) || pc < t.entries[n].min || pc >= t.entries[n].max { 171 | return nil 172 | } 173 | return t.entries[n].f 174 | } 175 | 176 | // findByName finds a Func for the given name. 177 | func (t *funcTab) findByName(name string) *Func { 178 | return t.byName[name] 179 | } 180 | 181 | // a pcTab maps from an offset in a function to an int64. 182 | type pcTab struct { 183 | entries []pcTabEntry 184 | } 185 | 186 | type pcTabEntry struct { 187 | bytes int64 // # of bytes this entry covers 188 | val int64 // value over that range of bytes 189 | } 190 | 191 | // read parses a pctab from the core file at address data. 192 | func (t *pcTab) read(core *core.Process, data core.Address) { 193 | var pcQuantum int64 194 | switch core.Arch() { 195 | case "386", "amd64", "amd64p32": 196 | pcQuantum = 1 197 | case "s390x": 198 | pcQuantum = 2 199 | case "arm", "arm64", "mips", "mipsle", "mips64", "mips64le", "ppc64", "ppc64le": 200 | pcQuantum = 4 201 | default: 202 | panic("unknown architecture " + core.Arch()) 203 | } 204 | val := int64(-1) 205 | first := true 206 | for { 207 | // Advance value. 208 | v, n := readVarint(core, data) 209 | if v == 0 && !first { 210 | return 211 | } 212 | data = data.Add(n) 213 | if v&1 != 0 { 214 | val += ^(v >> 1) 215 | } else { 216 | val += v >> 1 217 | } 218 | 219 | // Advance pc. 220 | v, n = readVarint(core, data) 221 | data = data.Add(n) 222 | t.entries = append(t.entries, pcTabEntry{bytes: v * pcQuantum, val: val}) 223 | first = false 224 | } 225 | } 226 | 227 | func (t *pcTab) setEmpty() { 228 | t.entries = []pcTabEntry{{bytes: math.MaxInt64, val: -1}} 229 | } 230 | 231 | func (t *pcTab) find(off int64) (int64, error) { 232 | for _, e := range t.entries { 233 | if off < e.bytes { 234 | return e.val, nil 235 | } 236 | off -= e.bytes 237 | } 238 | return 0, fmt.Errorf("can't find pctab entry for offset %#x", off) 239 | } 240 | 241 | // readVarint reads a varint from the core file. 242 | // val is the value, n is the number of bytes consumed. 243 | func readVarint(core *core.Process, a core.Address) (val, n int64) { 244 | for { 245 | b := core.ReadUint8(a) 246 | val |= int64(b&0x7f) << uint(n*7) 247 | n++ 248 | a++ 249 | if b&0x80 == 0 { 250 | return 251 | } 252 | } 253 | } 254 | -------------------------------------------------------------------------------- /internal/gocore/object.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 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 | package gocore 6 | 7 | import ( 8 | "iter" 9 | "math/bits" 10 | 11 | "golang.org/x/debug/internal/core" 12 | ) 13 | 14 | // An Object represents a single reachable object in the Go heap. 15 | // Unreachable (garbage) objects are not represented as Objects. 16 | type Object core.Address 17 | 18 | // markObjects finds all the live objects in the heap and marks them 19 | // in the p.heapInfo mark fields. 20 | func (p *Process) markObjects() { 21 | ptrSize := p.proc.PtrSize() 22 | 23 | // number of live objects found so far 24 | n := 0 25 | // total size of live objects 26 | var live int64 27 | 28 | var q []Object 29 | 30 | // Function to call when we find a new pointer. 31 | add := func(x core.Address) { 32 | h := p.heap.get(x) 33 | if h == nil { // not in heap or not in a valid span 34 | // Invalid spans can happen with intra-stack pointers. 35 | return 36 | } 37 | // Round down to object start. 38 | x = h.base.Add(x.Sub(h.base) / h.size * h.size) 39 | // Object start may map to a different info. Reload heap info. 40 | h = p.heap.get(x) 41 | // Find mark bit 42 | b := uint64(x) % heapInfoSize / 8 43 | if h.mark&(uint64(1)< 0 { 73 | x := q[len(q)-1] 74 | q = q[:len(q)-1] 75 | 76 | // Scan object for pointers. 77 | size := p.Size(x) 78 | for i := int64(0); i < size; i += ptrSize { 79 | a := core.Address(x).Add(i) 80 | if p.isPtrFromHeap(a) { 81 | add(p.proc.ReadPtr(a)) 82 | } 83 | } 84 | } 85 | 86 | p.nObj = n 87 | 88 | // Initialize firstIdx fields in the heapInfo, for fast object index lookups. 89 | n = 0 90 | p.ForEachObject(func(x Object) bool { 91 | h := p.heap.get(p.Addr(x)) 92 | if h.firstIdx == -1 { 93 | h.firstIdx = n 94 | } 95 | n++ 96 | return true 97 | }) 98 | if n != p.nObj { 99 | panic("object count wrong") 100 | } 101 | 102 | // Update stats to include the live/garbage distinction. 103 | allocSize := p.Stats().Sub("heap", "in use spans", "alloc").Value 104 | p.Stats().Sub("heap", "in use spans").setChild( 105 | groupStat("alloc", 106 | leafStat("live", live), 107 | leafStat("garbage", allocSize-live), 108 | ), 109 | ) 110 | } 111 | 112 | // isPtrFromHeap reports whether the inferior at address a contains a pointer. 113 | // a must be somewhere in the heap. 114 | func (p *Process) isPtrFromHeap(a core.Address) bool { 115 | return p.heap.get(a).isPtr(a, p.proc.PtrSize()) 116 | } 117 | 118 | // IsPtr reports whether the inferior at address a contains a pointer. 119 | func (p *Process) IsPtr(a core.Address) bool { 120 | h := p.heap.get(a) 121 | if h != nil { 122 | return h.isPtr(a, p.proc.PtrSize()) 123 | } 124 | for _, m := range p.modules { 125 | for _, s := range [2]string{"data", "bss"} { 126 | min := core.Address(m.r.Field(s).Uintptr()) 127 | max := core.Address(m.r.Field("e" + s).Uintptr()) 128 | if a < min || a >= max { 129 | continue 130 | } 131 | gc := m.r.Field("gc" + s + "mask").Field("bytedata").Address() 132 | i := a.Sub(min) 133 | return p.proc.ReadUint8(gc.Add(i/8))>>uint(i%8) != 0 134 | } 135 | } 136 | // Everywhere else can't be a pointer. At least, not a pointer into the Go heap. 137 | // TODO: stacks? 138 | // TODO: finalizers? 139 | return false 140 | } 141 | 142 | // FindObject finds the object containing a. Returns that object and the offset within 143 | // that object to which a points. 144 | // Returns 0,0 if a doesn't point to a live heap object. 145 | func (p *Process) FindObject(a core.Address) (Object, int64) { 146 | // Round down to the start of an object. 147 | h := p.heap.get(a) 148 | if h == nil { 149 | // Not in Go heap, or in a span 150 | // that doesn't hold Go objects (freed, stacks, ...) 151 | return 0, 0 152 | } 153 | x := h.base.Add(a.Sub(h.base) / h.size * h.size) 154 | // Check if object is marked. 155 | h = p.heap.get(x) 156 | if h.mark>>(uint64(x)%heapInfoSize/8)&1 == 0 { // free or garbage 157 | return 0, 0 158 | } 159 | return Object(x), a.Sub(x) 160 | } 161 | 162 | func (p *Process) findObjectIndex(a core.Address) (int, int64) { 163 | x, off := p.FindObject(a) 164 | if x == 0 { 165 | return -1, 0 166 | } 167 | h := p.heap.get(core.Address(x)) 168 | return h.firstIdx + bits.OnesCount64(h.mark&(uint64(1)<<(uint64(x)%heapInfoSize/8)-1)), off 169 | } 170 | 171 | // ForEachObject calls fn with each object in the Go heap. 172 | // If fn returns false, ForEachObject returns immediately. 173 | func (p *Process) ForEachObject(fn func(x Object) bool) { 174 | for a, h := range p.heap.all() { 175 | m := h.mark 176 | for m != 0 { 177 | j := bits.TrailingZeros64(m) 178 | m &= m - 1 179 | x := Object(a + core.Address(j*8)) 180 | if !fn(x) { 181 | return 182 | } 183 | } 184 | } 185 | } 186 | 187 | // ForEachRoot calls fn with each garbage collection root. 188 | // If fn returns false, ForEachRoot returns immediately. 189 | func (p *Process) ForEachRoot(fn func(r *Root) bool) { 190 | for _, r := range p.globals { 191 | if !fn(r) { 192 | return 193 | } 194 | } 195 | for _, g := range p.goroutines { 196 | for _, f := range g.frames { 197 | for _, r := range f.roots { 198 | if !fn(r) { 199 | return 200 | } 201 | } 202 | } 203 | } 204 | } 205 | 206 | // Addr returns the starting address of x. 207 | func (p *Process) Addr(x Object) core.Address { 208 | return core.Address(x) 209 | } 210 | 211 | // Size returns the size of x in bytes. 212 | func (p *Process) Size(x Object) int64 { 213 | return p.heap.get(core.Address(x)).size 214 | } 215 | 216 | // Type returns the type and repeat count for the object x. 217 | // x contains at least repeat copies of the returned type. 218 | func (p *Process) Type(x Object) (*Type, int64) { 219 | p.typeHeap() 220 | 221 | i, _ := p.findObjectIndex(core.Address(x)) 222 | return p.types[i].t, p.types[i].r 223 | } 224 | 225 | // ForEachPtr calls fn for all heap pointers it finds in x. 226 | // It calls fn with: 227 | // 228 | // the offset of the pointer slot in x 229 | // the pointed-to object y 230 | // the offset in y where the pointer points. 231 | // 232 | // If fn returns false, ForEachPtr returns immediately. 233 | // For an edge from an object to its finalizer, the first argument 234 | // passed to fn will be -1. (TODO: implement) 235 | func (p *Process) ForEachPtr(x Object, fn func(int64, Object, int64) bool) { 236 | size := p.Size(x) 237 | for i := int64(0); i < size; i += p.proc.PtrSize() { 238 | a := core.Address(x).Add(i) 239 | if !p.isPtrFromHeap(a) { 240 | continue 241 | } 242 | ptr := p.proc.ReadPtr(a) 243 | y, off := p.FindObject(ptr) 244 | if y != 0 { 245 | if !fn(i, y, off) { 246 | return 247 | } 248 | } 249 | } 250 | } 251 | 252 | // ForEachRootPtr behaves like ForEachPtr but it starts with a Root instead of an Object. 253 | func (p *Process) ForEachRootPtr(r *Root, fn func(int64, Object, int64) bool) { 254 | p.forEachRootPtr(r, func(off int64, ptr core.Address) bool { 255 | dst, off2 := p.FindObject(ptr) 256 | if dst != 0 { 257 | if !fn(off, dst, off2) { 258 | return false 259 | } 260 | } 261 | return true 262 | }) 263 | } 264 | 265 | // forEachRootPtr walks all the pointers of the root, even if they don't point to 266 | // a valid object. 267 | func (p *Process) forEachRootPtr(r *Root, fn func(int64, core.Address) bool) { 268 | ptrBuf := make([]byte, 8) 269 | walkRootTypePtrs(p, r, ptrBuf, 0, r.Type, fn) 270 | } 271 | 272 | const heapInfoSize = 512 273 | 274 | // Information for heapInfoSize bytes of heap. 275 | type heapInfo struct { 276 | base core.Address // start of the span containing this heap region 277 | size int64 // size of objects in the span 278 | mark uint64 // 64 mark bits, one for every 8 bytes 279 | firstIdx int // the index of the first object that starts in this region, or -1 if none 280 | // For 64-bit inferiors, ptr[0] contains 64 pointer bits, one 281 | // for every 8 bytes. On 32-bit inferiors, ptr contains 128 282 | // pointer bits, one for every 4 bytes. 283 | ptr [2]uint64 284 | } 285 | 286 | func (h *heapInfo) isPtr(a core.Address, ptrSize int64) bool { 287 | if ptrSize == 8 { 288 | i := uint(a%heapInfoSize) / 8 289 | return h.ptr[0]>>i&1 != 0 290 | } 291 | i := a % heapInfoSize / 4 292 | return h.ptr[i/64]>>(i%64)&1 != 0 293 | } 294 | 295 | type heapTableID uint64 296 | 297 | func (id heapTableID) addr() core.Address { 298 | return core.Address(id * heapInfoSize * heapTableSize) 299 | } 300 | 301 | type heapTable struct { 302 | table map[heapTableID]*heapTableEntry 303 | entries []heapTableID 304 | ptrSize uint64 305 | } 306 | 307 | func heapTableIndex(a core.Address) (heapTableID, uint64) { 308 | return heapTableID(a / heapInfoSize / heapTableSize), uint64(a / heapInfoSize % heapTableSize) 309 | } 310 | 311 | // Heap info structures cover 9 bits of address. 312 | // A page table entry covers 20 bits of address (1MB). 313 | const heapTableSize = 1 << 11 314 | 315 | type heapTableEntry [heapTableSize]heapInfo 316 | 317 | func (ht *heapTable) setIsPointer(a core.Address) { 318 | h := ht.getOrCreate(a) 319 | ptrSize := ht.ptrSize 320 | if ptrSize == 8 { 321 | i := uint64(a%heapInfoSize) / 8 322 | h.ptr[0] |= uint64(1) << i 323 | return 324 | } 325 | i := uint64(a%heapInfoSize) / 4 326 | h.ptr[i/64] |= uint64(1) << (i % 64) 327 | } 328 | 329 | // get returns the heap info for an address. Returns nil 330 | // if a is not a heap address. 331 | func (ht *heapTable) get(a core.Address) *heapInfo { 332 | k, i := heapTableIndex(a) 333 | t := ht.table[k] 334 | if t == nil { 335 | return nil 336 | } 337 | h := &t[i] 338 | if h.base == 0 { 339 | return nil 340 | } 341 | return h 342 | } 343 | 344 | // getOrCreate returns the heap info for an address, or if nil, 345 | // creates it, marking it as a heap address. 346 | func (ht *heapTable) getOrCreate(a core.Address) *heapInfo { 347 | k, i := heapTableIndex(a) 348 | t := ht.table[k] 349 | if t == nil { 350 | t = new(heapTableEntry) 351 | for j := 0; j < heapTableSize; j++ { 352 | t[j].firstIdx = -1 353 | } 354 | ht.table[k] = t 355 | ht.entries = append(ht.entries, k) 356 | } 357 | return &t[i] 358 | } 359 | 360 | func (ht *heapTable) all() iter.Seq2[core.Address, *heapInfo] { 361 | return func(yield func(core.Address, *heapInfo) bool) { 362 | for _, k := range ht.entries { 363 | e := ht.table[k] 364 | for i := range e { 365 | h := &e[i] 366 | a := k.addr() + core.Address(uint64(i)*heapInfoSize) 367 | if !yield(a, h) { 368 | return 369 | } 370 | } 371 | } 372 | } 373 | } 374 | -------------------------------------------------------------------------------- /internal/gocore/region.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 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 | package gocore 6 | 7 | import "golang.org/x/debug/internal/core" 8 | 9 | // A region is a piece of the virtual address space of the inferior. 10 | // It has an address and a type. 11 | // Note that it is the type of the thing in the region, 12 | // not the type of the reference to the region. 13 | type region struct { 14 | p *core.Process 15 | a core.Address 16 | typ *Type 17 | } 18 | 19 | // Address returns the address that a region of pointer type points to. 20 | func (r region) Address() core.Address { 21 | if r.typ.Kind != KindPtr { 22 | panic("can't ask for the Address of a non-pointer " + r.typ.Name) 23 | } 24 | return r.p.ReadPtr(r.a) 25 | } 26 | 27 | // Int returns the int value stored in r. 28 | func (r region) Int() int64 { 29 | if r.typ.Kind != KindInt || r.typ.Size != r.p.PtrSize() { 30 | panic("not an int: " + r.typ.Name) 31 | } 32 | return r.p.ReadInt(r.a) 33 | } 34 | 35 | // Uintptr returns the uintptr value stored in r. 36 | func (r region) Uintptr() uint64 { 37 | if r.typ.Kind != KindUint || r.typ.Size != r.p.PtrSize() { 38 | panic("not a uintptr: " + r.typ.Name) 39 | } 40 | return r.p.ReadUintptr(r.a) 41 | } 42 | 43 | // Cast the region to the given type. 44 | func (r region) Cast(typ *Type) region { 45 | return region{p: r.p, a: r.a, typ: typ} 46 | } 47 | 48 | // Deref loads from a pointer. r must contain a pointer. 49 | func (r region) Deref() region { 50 | if r.typ.Kind != KindPtr { 51 | panic("can't deref on non-pointer: " + r.typ.Name) 52 | } 53 | if r.typ.Elem == nil { 54 | panic("can't deref unsafe.Pointer") 55 | } 56 | p := r.p.ReadPtr(r.a) 57 | return region{p: r.p, a: p, typ: r.typ.Elem} 58 | } 59 | 60 | // Uint64 returns the uint64 value stored in r. 61 | // r must have type uint64. 62 | func (r region) Uint64() uint64 { 63 | if r.typ.Kind != KindUint || r.typ.Size != 8 { 64 | panic("bad uint64 type " + r.typ.Name) 65 | } 66 | return r.p.ReadUint64(r.a) 67 | } 68 | 69 | // Uint32 returns the uint32 value stored in r. 70 | // r must have type uint32. 71 | func (r region) Uint32() uint32 { 72 | if r.typ.Kind != KindUint || r.typ.Size != 4 { 73 | panic("bad uint32 type " + r.typ.Name) 74 | } 75 | return r.p.ReadUint32(r.a) 76 | } 77 | 78 | // Int32 returns the int32 value stored in r. 79 | // r must have type int32. 80 | func (r region) Int32() int32 { 81 | if r.typ.Kind != KindInt || r.typ.Size != 4 { 82 | panic("bad int32 type " + r.typ.Name) 83 | } 84 | return r.p.ReadInt32(r.a) 85 | } 86 | 87 | // Uint16 returns the uint16 value stored in r. 88 | // r must have type uint16. 89 | func (r region) Uint16() uint16 { 90 | if r.typ.Kind != KindUint || r.typ.Size != 2 { 91 | panic("bad uint16 type " + r.typ.Name) 92 | } 93 | return r.p.ReadUint16(r.a) 94 | } 95 | 96 | // Uint8 returns the uint8 value stored in r. 97 | // r must have type uint8. 98 | func (r region) Uint8() uint8 { 99 | if r.typ.Kind != KindUint || r.typ.Size != 1 { 100 | panic("bad uint8 type " + r.typ.Name) 101 | } 102 | return r.p.ReadUint8(r.a) 103 | } 104 | 105 | // Bool returns the bool value stored in r. 106 | // r must have type bool. 107 | func (r region) Bool() bool { 108 | if r.typ.Kind != KindBool { 109 | panic("bad bool type " + r.typ.Name) 110 | } 111 | return r.p.ReadUint8(r.a) != 0 112 | } 113 | 114 | // String returns the value of the string stored in r. 115 | func (r region) String() string { 116 | if r.typ.Kind != KindString { 117 | panic("bad string type " + r.typ.Name) 118 | } 119 | p := r.p.ReadPtr(r.a) 120 | n := r.p.ReadUintptr(r.a.Add(r.p.PtrSize())) 121 | b := make([]byte, n) 122 | r.p.ReadAt(b, p) 123 | return string(b) 124 | } 125 | 126 | // SliceIndex indexes a slice (a[n]). r must contain a slice. 127 | // n must be in bounds for the slice. 128 | func (r region) SliceIndex(n int64) region { 129 | if r.typ.Kind != KindSlice { 130 | panic("can't index a non-slice") 131 | } 132 | p := r.p.ReadPtr(r.a) 133 | return region{p: r.p, a: p.Add(n * r.typ.Elem.Size), typ: r.typ.Elem} 134 | } 135 | 136 | // SlicePtr returns the pointer inside a slice. r must contain a slice. 137 | func (r region) SlicePtr() region { 138 | if r.typ.Kind != KindSlice { 139 | panic("can't Ptr a non-slice") 140 | } 141 | return region{p: r.p, a: r.a, typ: &Type{Name: "*" + r.typ.Name[2:], Size: r.p.PtrSize(), Kind: KindPtr, Elem: r.typ.Elem}} 142 | } 143 | 144 | // SliceLen returns the length of a slice. r must contain a slice. 145 | func (r region) SliceLen() int64 { 146 | if r.typ.Kind != KindSlice { 147 | panic("can't len a non-slice") 148 | } 149 | return r.p.ReadInt(r.a.Add(r.p.PtrSize())) 150 | } 151 | 152 | // SliceCap returns the capacity of a slice. r must contain a slice. 153 | func (r region) SliceCap() int64 { 154 | if r.typ.Kind != KindSlice { 155 | panic("can't cap a non-slice") 156 | } 157 | return r.p.ReadInt(r.a.Add(2 * r.p.PtrSize())) 158 | } 159 | 160 | // Field returns the part of r which contains the field f. 161 | // r must contain a struct, and f must be one of its fields. 162 | func (r region) Field(f string) region { 163 | finfo := r.typ.field(f) 164 | if finfo == nil { 165 | panic("can't find field " + r.typ.Name + "." + f) 166 | } 167 | return region{p: r.p, a: r.a.Add(finfo.Off), typ: finfo.Type} 168 | } 169 | 170 | func (r region) HasField(f string) bool { 171 | finfo := r.typ.field(f) 172 | return finfo != nil 173 | } 174 | 175 | func (r region) ArrayLen() int64 { 176 | if r.typ.Kind != KindArray { 177 | panic("can't ArrayLen a non-array") 178 | } 179 | return r.typ.Count 180 | } 181 | 182 | func (r region) ArrayIndex(i int64) region { 183 | if r.typ.Kind != KindArray { 184 | panic("can't ArrayIndex a non-array") 185 | } 186 | if i < 0 || i >= r.typ.Count { 187 | panic("array index out of bounds") 188 | } 189 | return region{p: r.p, a: r.a.Add(i * r.typ.Elem.Size), typ: r.typ.Elem} 190 | } 191 | 192 | func (r region) IsStruct() bool { 193 | return r.typ.Kind == KindStruct 194 | } 195 | 196 | func (r region) IsUint16() bool { 197 | return r.typ.Kind == KindUint && r.typ.Size == 2 198 | } 199 | -------------------------------------------------------------------------------- /internal/gocore/reverse.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 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 | package gocore 6 | 7 | func (p *Process) reverseEdges() { 8 | p.initReverseEdges.Do(func() { 9 | // First, count the number of edges into each object. 10 | // This allows for efficient packing of the reverse edge storage. 11 | cnt := make([]int64, p.nObj+1) 12 | p.ForEachObject(func(x Object) bool { 13 | p.ForEachPtr(x, func(_ int64, y Object, _ int64) bool { 14 | idx, _ := p.findObjectIndex(p.Addr(y)) 15 | cnt[idx]++ 16 | return true 17 | }) 18 | return true 19 | }) 20 | p.ForEachRoot(func(r *Root) bool { 21 | p.ForEachRootPtr(r, func(_ int64, y Object, _ int64) bool { 22 | idx, _ := p.findObjectIndex(p.Addr(y)) 23 | cnt[idx]++ 24 | return true 25 | }) 26 | return true 27 | }) 28 | 29 | // Compute cumulative count of all incoming edges up to and including each object. 30 | var n int64 31 | for idx, c := range cnt { 32 | n += c 33 | cnt[idx] = n 34 | } 35 | 36 | // Allocate all the storage for the reverse edges. 37 | p.redge = make([]reverseEdge, n) 38 | 39 | // Add edges to the lists. 40 | p.ForEachObject(func(x Object) bool { 41 | p.ForEachPtr(x, func(i int64, y Object, _ int64) bool { 42 | idx, _ := p.findObjectIndex(p.Addr(y)) 43 | e := cnt[idx] 44 | e-- 45 | cnt[idx] = e 46 | p.redge[e] = reverseEdge{addr: p.Addr(x).Add(i)} 47 | return true 48 | }) 49 | return true 50 | }) 51 | p.ForEachRoot(func(r *Root) bool { 52 | p.ForEachRootPtr(r, func(i int64, y Object, _ int64) bool { 53 | idx, _ := p.findObjectIndex(p.Addr(y)) 54 | e := cnt[idx] 55 | e-- 56 | cnt[idx] = e 57 | p.redge[e] = reverseEdge{root: r, rOff: i} 58 | return true 59 | }) 60 | return true 61 | }) 62 | // At this point, cnt contains the cumulative count of all edges up to 63 | // but *not* including each object. 64 | p.ridx = cnt 65 | 66 | // Make root index. 67 | p.rootIdx = make([]*Root, p.nRoots) 68 | p.ForEachRoot(func(r *Root) bool { 69 | p.rootIdx[r.id] = r 70 | return true 71 | }) 72 | }) 73 | } 74 | 75 | // ForEachReversePtr calls fn for all pointers it finds pointing to y. 76 | // It calls fn with: 77 | // 78 | // the object or root which points to y (exactly one will be non-nil) 79 | // the offset i in that object or root where the pointer appears. 80 | // the offset j in y where the pointer points. 81 | // 82 | // If fn returns false, ForEachReversePtr returns immediately. 83 | func (p *Process) ForEachReversePtr(y Object, fn func(x Object, r *Root, i, j int64) bool) { 84 | p.reverseEdges() 85 | 86 | idx, _ := p.findObjectIndex(p.Addr(y)) 87 | for _, a := range p.redge[p.ridx[idx]:p.ridx[idx+1]] { 88 | if a.root != nil { 89 | ptr, _ := p.readRootPtr(a.root, a.rOff) 90 | if !fn(0, a.root, a.rOff, ptr.Sub(p.Addr(y))) { 91 | return 92 | } 93 | } else { 94 | // Read pointer, compute offset in y. 95 | ptr := p.proc.ReadPtr(a.addr) 96 | j := ptr.Sub(p.Addr(y)) 97 | 98 | // Find source of pointer. 99 | x, i := p.FindObject(a.addr) 100 | if x != 0 { 101 | // Source is an object. 102 | if !fn(x, nil, i, j) { 103 | return 104 | } 105 | } 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /internal/gocore/root.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 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 | package gocore 6 | 7 | import ( 8 | "encoding/binary" 9 | "unsafe" 10 | 11 | "golang.org/x/debug/internal/core" 12 | ) 13 | 14 | // A Root is an area of memory that might have pointers into the Go heap. 15 | type Root struct { 16 | Name string 17 | Type *Type // always non-nil 18 | // Frame, if non-nil, points to the frame in which this root lives. 19 | // Roots with non-nil Frame fields refer to local variables on a stack. 20 | // A stack root might be a large type, with some of its fields live and 21 | // others dead. Consult Frame.Live to find out which pointers in a stack 22 | // root are live. 23 | Frame *Frame 24 | 25 | pieces []rootPiece 26 | id int 27 | } 28 | 29 | // HasAddress returns true if the root is simple and contiguous, and can be 30 | // described with just a single address. 31 | func (r *Root) HasAddress() bool { 32 | return len(r.pieces) == 1 && r.pieces[0].kind == addrPiece 33 | } 34 | 35 | // Addr returns the address of the root, if it has one. 36 | func (r *Root) Addr() core.Address { 37 | if r.HasAddress() { 38 | return core.Address(r.pieces[0].value) 39 | } 40 | return 0 41 | } 42 | 43 | func (p *Process) makeMemRoot(name string, typ *Type, fr *Frame, addr core.Address) *Root { 44 | return makeMemRoot(&p.nRoots, name, typ, fr, addr) 45 | } 46 | 47 | func makeMemRoot(nRoots *int, name string, typ *Type, fr *Frame, addr core.Address) *Root { 48 | r := &Root{ 49 | Name: name, 50 | Type: typ, 51 | Frame: fr, 52 | pieces: []rootPiece{{size: typ.Size, kind: addrPiece, value: uint64(addr)}}, 53 | id: *nRoots, 54 | } 55 | *nRoots += 1 56 | return r 57 | } 58 | 59 | func (p *Process) makeCompositeRoot(name string, typ *Type, fr *Frame, pieces []rootPiece) *Root { 60 | r := &Root{ 61 | Name: name, 62 | Type: typ, 63 | Frame: fr, 64 | pieces: pieces, 65 | id: p.nRoots, 66 | } 67 | p.nRoots++ 68 | return r 69 | } 70 | 71 | func (pr *Process) readRootPtr(r *Root, offset int64) (value, from core.Address) { 72 | // TODO(mknyszek): Little-endian only. 73 | ptrBuf := make([]byte, pr.proc.PtrSize()) 74 | from = pr.readRootAt(r, ptrBuf, offset) 75 | if pr.proc.PtrSize() == 4 { 76 | return core.Address(binary.LittleEndian.Uint32(ptrBuf)), from 77 | } 78 | return core.Address(binary.LittleEndian.Uint64(ptrBuf)), from 79 | } 80 | 81 | // ReadRootAt reads data out of this root. offset+len(b) must be less than r.Type.Size. 82 | // Returns the address read from, if the read was contiguous and from memory. 83 | func (pr *Process) readRootAt(r *Root, b []byte, offset int64) core.Address { 84 | if offset+int64(len(b)) > r.Type.Size { 85 | panic("invalid range to read from root") 86 | } 87 | if len(b) == 0 { 88 | return 0 89 | } 90 | bOff := int64(0) 91 | var addr core.Address 92 | first := true 93 | for _, p := range r.pieces { 94 | if offset > p.off+p.size { 95 | continue 96 | } 97 | if offset+int64(len(b)) <= p.off { 98 | break 99 | } 100 | pOff := max(p.off, offset) 101 | base := pOff - p.off 102 | rlen := min(int64(len(b))-bOff, p.size-base) 103 | switch p.kind { 104 | case addrPiece: 105 | pr.proc.ReadAt(b[bOff:bOff+rlen], core.Address(p.value).Add(base)) 106 | if first { 107 | addr = core.Address(p.value).Add(base) 108 | } else { 109 | addr = 0 110 | } 111 | case regPiece, immPiece: 112 | // TODO(mknyszek): Supports little-endian only. 113 | v := ((*[8]byte)(unsafe.Pointer(&p.value)))[:p.size] 114 | copy(b[bOff:bOff+rlen], v[base:base+rlen]) 115 | addr = 0 116 | } 117 | if first { 118 | first = false 119 | } 120 | bOff += rlen 121 | if bOff == int64(len(b)) { 122 | break 123 | } 124 | } 125 | return addr 126 | } 127 | 128 | // walkRootTypePtrs calls fn for the edges found in an object of type t living at offset off in the root r. 129 | // If fn returns false, return immediately with false. 130 | func walkRootTypePtrs(p *Process, r *Root, ptrBuf []byte, off int64, t *Type, fn func(int64, core.Address) bool) bool { 131 | switch t.Kind { 132 | case KindBool, KindInt, KindUint, KindFloat, KindComplex: 133 | // no edges here 134 | case KindIface, KindEface: 135 | // The first word is a type or itab. 136 | // Itabs are never in the heap. 137 | // Types might be, though. 138 | // We have no idea about the liveness of registers, when a == 0. 139 | a := p.readRootAt(r, ptrBuf[:p.proc.PtrSize()], off) 140 | if a != 0 && (r.Frame == nil || r.Frame.Live[a]) { 141 | var ptr core.Address 142 | if p.proc.PtrSize() == 4 { 143 | ptr = core.Address(binary.LittleEndian.Uint32(ptrBuf[:])) 144 | } else { 145 | ptr = core.Address(binary.LittleEndian.Uint64(ptrBuf[:])) 146 | } 147 | if !fn(off, ptr) { 148 | return false 149 | } 150 | } 151 | // Treat second word like a pointer. 152 | off += p.proc.PtrSize() 153 | fallthrough 154 | case KindPtr, KindString, KindSlice, KindFunc: 155 | a := p.readRootAt(r, ptrBuf[:p.proc.PtrSize()], off) 156 | if a != 0 && (r.Frame == nil || r.Frame.Live[a]) { 157 | var ptr core.Address 158 | if p.proc.PtrSize() == 4 { 159 | ptr = core.Address(binary.LittleEndian.Uint32(ptrBuf[:])) 160 | } else { 161 | ptr = core.Address(binary.LittleEndian.Uint64(ptrBuf[:])) 162 | } 163 | if !fn(off, ptr) { 164 | return false 165 | } 166 | } 167 | case KindArray: 168 | s := t.Elem.Size 169 | for i := int64(0); i < t.Count; i++ { 170 | if !walkRootTypePtrs(p, r, ptrBuf, off+i*s, t.Elem, fn) { 171 | return false 172 | } 173 | } 174 | case KindStruct: 175 | for _, f := range t.Fields { 176 | if !walkRootTypePtrs(p, r, ptrBuf, off+f.Off, f.Type, fn) { 177 | return false 178 | } 179 | } 180 | } 181 | return true 182 | } 183 | 184 | type rootPieceKind int 185 | 186 | const ( 187 | addrPiece rootPieceKind = iota 188 | regPiece 189 | immPiece 190 | ) 191 | 192 | type rootPiece struct { 193 | off int64 // Logical offset into the root, or specifically the root's Type. 194 | size int64 // Size of the piece. 195 | kind rootPieceKind // Where the piece is. 196 | value uint64 // Address if kind == AddrPiece, value if kind == RegPiece or kind == ImmPiece. 197 | } 198 | -------------------------------------------------------------------------------- /internal/gocore/testdata/testprogs/bigslice.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 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 | //go:build ignore 6 | 7 | // Tests to make sure pointers in big slices are handled correctly. 8 | 9 | package main 10 | 11 | import ( 12 | "os" 13 | "runtime" 14 | 15 | "golang.org/x/debug/internal/testenv" 16 | ) 17 | 18 | type bigSliceElem struct { 19 | x, y, z float64 20 | } 21 | 22 | var globalBigSlice []*bigSliceElem 23 | var block chan struct{} 24 | 25 | func main() { 26 | testenv.RunThenCrash(os.Getenv("GO_DEBUG_TEST_COREDUMP_FILTER"), func() any { 27 | globalBigSlice = *makeBigSlice() 28 | ready := make(chan []*bigSliceElem) 29 | go func() { 30 | // This funny incantation exists to force the bs0 and bs1 slice 31 | // headers to be deconstructed by the compiler and stored in pieces 32 | // on the stack. This tests whether gocore can piece it back 33 | // together. 34 | bsp0 := makeBigSlice() 35 | bsp1 := makeBigSlice() 36 | bs0 := *bsp0 37 | bs1 := *bsp1 38 | runtime.GC() 39 | ready <- bs0 40 | ready <- bs1 41 | runtime.KeepAlive(bs0) 42 | runtime.KeepAlive(bs1) 43 | }() 44 | <-ready 45 | 46 | return nil 47 | }) 48 | } 49 | 50 | // This function signature looks weird, returning a pointer to a slice, but it's 51 | // to try and force deconstruction of the slice value by the compiler in the caller. 52 | // See callers of makeBigSlice. 53 | // 54 | //go:noinline 55 | func makeBigSlice() *[]*bigSliceElem { 56 | bs := make([]*bigSliceElem, 32<<10) 57 | for i := range bs { 58 | bs[i] = &bigSliceElem{float64(i), float64(i) - 0.5, float64(i * 124)} 59 | } 60 | return &bs 61 | } 62 | -------------------------------------------------------------------------------- /internal/gocore/testdata/testprogs/large.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 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 | //go:build ignore 6 | 7 | // Tests to make sure a large object referenced only from a stack 8 | // can be found. 9 | 10 | package main 11 | 12 | import ( 13 | "os" 14 | 15 | "golang.org/x/debug/internal/testenv" 16 | ) 17 | 18 | // Large is an object that (since Go 1.22) is allocated in a span that has a 19 | // non-nil largeType field. Meaning it must be (>maxSmallSize-mallocHeaderSize). 20 | // At the time of writing this is (32768 - 8). 21 | type Large struct { 22 | ptr *uint8 // Object must contain a pointer to trigger code path. 23 | arr [32768 - 8]uint8 24 | } 25 | 26 | func useLarge(o *Large, ready chan<- struct{}) { 27 | o.ptr = &o.arr[5] 28 | o.arr[5] = 0xCA 29 | ready <- struct{}{} 30 | <-block 31 | } 32 | 33 | var block = make(chan struct{}) 34 | 35 | func main() { 36 | testenv.RunThenCrash(os.Getenv("GO_DEBUG_TEST_COREDUMP_FILTER"), func() any { 37 | ready := make(chan struct{}) 38 | 39 | // Create a large value and reference 40 | var o Large 41 | go useLarge(&o, ready) // Force an escape of o. 42 | o.arr[14] = 0xDE // Prevent a future smart compiler from allocating o directly on useLarge's stack. 43 | 44 | <-ready 45 | return &o 46 | }) 47 | } 48 | -------------------------------------------------------------------------------- /internal/gocore/testdata/testprogs/trees.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 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 | //go:build ignore 6 | 7 | package main 8 | 9 | import ( 10 | "os" 11 | "runtime" 12 | 13 | "golang.org/x/debug/internal/testenv" 14 | ) 15 | 16 | type AnyTree struct { 17 | root any 18 | } 19 | 20 | type anyNode struct { 21 | left, right any // *anyNode, to test direct eface type walking 22 | entry1 any // myPair, to test indirect eface type walking 23 | entry2 Xer // myPair, to test indirect iface type walking 24 | } 25 | 26 | func makeAnyTree(depth int64) any { 27 | if depth == 0 { 28 | return nil 29 | } 30 | e1 := myPair{depth, depth} 31 | e2 := myPair{depth, depth} 32 | n := &anyNode{ 33 | left: makeAnyTree(depth - 1), 34 | right: makeAnyTree(depth - 1), 35 | entry1: e1, 36 | entry2: e2, 37 | } 38 | if depth%2 == 0 { 39 | // Test dealing with direct interfaces of wrapped structures. 40 | return anyNodeWrap2{{n}} 41 | } 42 | return n 43 | } 44 | 45 | //go:noinline 46 | func (a *AnyTree) count() int { 47 | return countAnyNode(a.root) 48 | } 49 | 50 | func countAnyNode(a any) int { 51 | switch v := a.(type) { 52 | case *anyNode: 53 | return v.count() 54 | case anyNodeWrap2: 55 | return v.unwrap().count() 56 | } 57 | return 0 58 | } 59 | 60 | // This is load-bearing to make sure anyNodeWrap2 ends up in the binary. 61 | // 62 | //go:noinline 63 | func (w anyNodeWrap2) unwrap() *anyNode { 64 | return w[0].anyNode 65 | } 66 | 67 | func (a *anyNode) count() int { 68 | return 1 + countAnyNode(a.left) + countAnyNode(a.right) 69 | } 70 | 71 | type anyNodeWrap struct{ *anyNode } 72 | type anyNodeWrap2 [1]anyNodeWrap 73 | 74 | type TypeSafeTree[K any] struct { 75 | root *typeSafeNode[K] 76 | } 77 | 78 | type typeSafeNode[K any] struct { 79 | left, right *typeSafeNode[K] 80 | entry *K 81 | } 82 | 83 | func makeTypeSafeTree(depth int64) *typeSafeNode[myPair] { 84 | if depth == 0 { 85 | return nil 86 | } 87 | return &typeSafeNode[myPair]{ 88 | left: makeTypeSafeTree(depth - 1), 89 | right: makeTypeSafeTree(depth - 1), 90 | entry: &myPair{depth, depth}, 91 | } 92 | } 93 | 94 | //go:noinline 95 | func (t *TypeSafeTree[K]) count() int { 96 | return t.root.count() 97 | } 98 | 99 | func (t *typeSafeNode[K]) count() int { 100 | if t == nil { 101 | return 0 102 | } 103 | return 1 + t.left.count() + t.right.count() 104 | } 105 | 106 | type myPair struct { 107 | x, y int64 108 | } 109 | 110 | func (p myPair) X() int64 { 111 | return p.x 112 | } 113 | 114 | type Xer interface { 115 | X() int64 116 | } 117 | 118 | var globalAnyTree AnyTree 119 | var globalAnyTreeFM func() int 120 | var globalTypeSafeTree TypeSafeTree[myPair] 121 | var globalTypeSafeTreeFM func() int 122 | 123 | var block = make(chan struct{}) 124 | var a anyNode 125 | 126 | func main() { 127 | testenv.RunThenCrash(os.Getenv("GO_DEBUG_TEST_COREDUMP_FILTER"), func() any { 128 | globalAnyTree.root = makeAnyTree(5) 129 | globalTypeSafeTree.root = makeTypeSafeTree(5) 130 | 131 | ready := make(chan struct{}) 132 | go func() { 133 | var anyTree AnyTree 134 | var anyTreeFM AnyTree // Captured in a method value. 135 | var typeSafeTree TypeSafeTree[myPair] 136 | var typeSafeTreeFM TypeSafeTree[myPair] // Captured in a method value. 137 | 138 | // TODO(mknyszek): AnyTree is described in DWARF in pieces, and we can't handle 139 | // that yet. 140 | // 141 | // anyTree.root = makeAnyTree(5) 142 | anyTreeFM.root = makeAnyTree(5) 143 | globalAnyTreeFM = anyTreeFM.count 144 | typeSafeTree.root = makeTypeSafeTree(5) 145 | typeSafeTreeFM.root = makeTypeSafeTree(5) 146 | globalTypeSafeTreeFM = typeSafeTreeFM.count 147 | 148 | ready <- struct{}{} 149 | <-block 150 | 151 | runtime.KeepAlive(anyTree) 152 | runtime.KeepAlive(typeSafeTree) 153 | }() 154 | 155 | // This is load-bearing to make sure anyNodeWrap2 and the count methods end up in the DWARF. 156 | println("tree counts:", globalAnyTree.count(), globalTypeSafeTree.count()) 157 | 158 | <-ready 159 | return nil 160 | }) 161 | } 162 | -------------------------------------------------------------------------------- /internal/testenv/crash.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 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 | package testenv 6 | 7 | import ( 8 | "os" 9 | "runtime" 10 | ) 11 | 12 | // RunThenCrash sets the provided core dump filter (optional) for 13 | // the process, runs f, then crashes. 14 | // 15 | // The slice returned by f is kept alive across the crash. 16 | func RunThenCrash(coredumpFilter string, f func() any) { 17 | // Set coredump filter (Linux only). 18 | if runtime.GOOS == "linux" && coredumpFilter != "" { 19 | if err := os.WriteFile("/proc/self/coredump_filter", []byte(coredumpFilter), 0600); err != nil { 20 | os.Stderr.WriteString("crash: unable to set coredump_filter: ") 21 | os.Stderr.WriteString(err.Error()) 22 | os.Stderr.WriteString("\n") 23 | os.Exit(0) // Don't crash (which is an error for the called). 24 | } 25 | } 26 | 27 | // Run f. 28 | result := f() 29 | crash() 30 | runtime.KeepAlive(result) 31 | } 32 | 33 | // Crash crashes the program. 34 | // 35 | // Make it noinline so registers are spilled before entering, otherwise imprecise DWARF will be our doom. 36 | // Delve has trouble with this too; 'result' in RunThenCrash won't be visible otherwise. 37 | // 38 | //go:noinline 39 | func crash() { 40 | _ = *(*int)(nil) 41 | } 42 | -------------------------------------------------------------------------------- /internal/testenv/testenv.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 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 | // The helper functions in this package are copied from the original 6 | // versions from the file of the same name in $GOROOT/src/internal. 7 | 8 | package testenv 9 | 10 | import ( 11 | "errors" 12 | "fmt" 13 | "go/build" 14 | "os" 15 | "os/exec" 16 | "path/filepath" 17 | "runtime" 18 | "testing" 19 | ) 20 | 21 | // HasGoBuild reports whether the current system can build programs 22 | // with “go build” and then run them with os.StartProcess or 23 | // exec.Command. 24 | func HasGoBuild() bool { 25 | if os.Getenv("GO_GCFLAGS") != "" { 26 | // It's too much work to require every caller of the go command 27 | // to pass along "-gcflags="+os.Getenv("GO_GCFLAGS"). 28 | // For now, if $GO_GCFLAGS is set, report that we simply can't 29 | // run go build. 30 | return false 31 | } 32 | switch runtime.GOOS { 33 | case "android", "js", "ios": 34 | return false 35 | } 36 | return true 37 | } 38 | 39 | // MustHaveGoBuild checks that the current system can build programs with “go build” 40 | // and then run them with os.StartProcess or exec.Command. 41 | // If not, MustHaveGoBuild calls t.Skip with an explanation. 42 | func MustHaveGoBuild(t testing.TB) { 43 | if os.Getenv("GO_GCFLAGS") != "" { 44 | t.Skipf("skipping test: 'go build' not compatible with setting $GO_GCFLAGS") 45 | } 46 | if !HasGoBuild() { 47 | t.Skipf("skipping test: 'go build' not available on %s/%s", runtime.GOOS, runtime.GOARCH) 48 | } 49 | } 50 | 51 | // GoToolPath reports the path to the Go tool. 52 | // It is a convenience wrapper around GoTool. 53 | // If the tool is unavailable GoToolPath calls t.Skip. 54 | // If the tool should be available and isn't, GoToolPath calls t.Fatal. 55 | func GoToolPath(t testing.TB) string { 56 | MustHaveGoBuild(t) 57 | path, err := GoTool() 58 | if err != nil { 59 | t.Fatal(err) 60 | } 61 | return path 62 | } 63 | 64 | // GoTool reports the path to the Go tool. 65 | func GoTool() (string, error) { 66 | if !HasGoBuild() { 67 | return "", errors.New("platform cannot run go tool") 68 | } 69 | var exeSuffix string 70 | if runtime.GOOS == "windows" { 71 | exeSuffix = ".exe" 72 | } 73 | path := filepath.Join(runtime.GOROOT(), "bin", "go"+exeSuffix) 74 | if _, err := os.Stat(path); err == nil { 75 | return path, nil 76 | } 77 | goBin, err := exec.LookPath("go" + exeSuffix) 78 | if err != nil { 79 | return "", errors.New("cannot find go tool: " + err.Error()) 80 | } 81 | return goBin, nil 82 | } 83 | 84 | // HasExternalNetwork reports whether the current system can use 85 | // external (non-localhost) networks. 86 | func HasExternalNetwork() bool { 87 | return !testing.Short() && runtime.GOOS != "js" 88 | } 89 | 90 | // MustHaveExternalNetwork checks that the current system can use 91 | // external (non-localhost) networks. 92 | // If not, MustHaveExternalNetwork calls t.Skip with an explanation. 93 | func MustHaveExternalNetwork(t testing.TB) { 94 | if runtime.GOOS == "js" { 95 | t.Skipf("skipping test: no external network on %s", runtime.GOOS) 96 | } 97 | if testing.Short() { 98 | t.Skipf("skipping test: no external network in -short mode") 99 | } 100 | } 101 | 102 | // Go1Point returns the x in Go 1.x. 103 | func Go1Point() int { 104 | for i := len(build.Default.ReleaseTags) - 1; i >= 0; i-- { 105 | var version int 106 | if _, err := fmt.Sscanf(build.Default.ReleaseTags[i], "go1.%d", &version); err != nil { 107 | continue 108 | } 109 | return version 110 | } 111 | panic("bad release tags") 112 | } 113 | 114 | // Testing is an abstraction of a *testing.T. 115 | type Testing interface { 116 | Skipf(format string, args ...interface{}) 117 | Fatalf(format string, args ...interface{}) 118 | } 119 | 120 | type helperer interface { 121 | Helper() 122 | } 123 | 124 | // NeedsGo1Point skips t if the Go version used to run the test is 125 | // older than 1.x. 126 | func NeedsGo1Point(t Testing, x int) { 127 | if t, ok := t.(helperer); ok { 128 | t.Helper() 129 | } 130 | if Go1Point() < x { 131 | t.Skipf("running Go version %q is version 1.%d, older than required 1.%d", runtime.Version(), Go1Point(), x) 132 | } 133 | } 134 | 135 | // NeedsArch skips test if the current arch is different than the one required. 136 | func NeedsArch(t Testing, arch string) { 137 | if t, ok := t.(helperer); ok { 138 | t.Helper() 139 | } 140 | if runtime.GOARCH != arch { 141 | t.Skipf("current arch is %q, test requires %q", runtime.GOARCH, arch) 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /third_party/delve/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Derek Parker 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /third_party/delve/README.md: -------------------------------------------------------------------------------- 1 | # delve 2 | 3 | This is a partial import of github.com/go-delve/delve/pkg focusing 4 | primarily on DWARF utilities. The most important part copied over is 5 | the stack program interpreter, though the regnum package is useful too. 6 | 7 | Copied at commit e6e7aeb667057ad6a98120ebf3591fbafdedb19d, with additions to support DWARF5. 8 | 9 | 10 | -------------------------------------------------------------------------------- /third_party/delve/dwarf/godwarf/addr.go: -------------------------------------------------------------------------------- 1 | package godwarf 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "errors" 7 | 8 | "golang.org/x/debug/third_party/delve/dwarf" 9 | ) 10 | 11 | // DebugAddrSection represents the debug_addr section of DWARFv5. 12 | // See DWARFv5 section 7.27 page 241 and following. 13 | type DebugAddrSection struct { 14 | byteOrder binary.ByteOrder 15 | ptrSz int 16 | data []byte 17 | } 18 | 19 | // ParseAddr parses the header of a debug_addr section. 20 | func ParseAddr(data []byte) *DebugAddrSection { 21 | if len(data) == 0 { 22 | return nil 23 | } 24 | r := &DebugAddrSection{data: data} 25 | _, dwarf64, _, byteOrder := dwarf.ReadDwarfLengthVersion(data) 26 | r.byteOrder = byteOrder 27 | data = data[6:] 28 | if dwarf64 { 29 | data = data[8:] 30 | } 31 | 32 | addrSz := data[0] 33 | segSelSz := data[1] 34 | r.ptrSz = int(addrSz + segSelSz) 35 | 36 | return r 37 | } 38 | 39 | // GetSubsection returns the subsection of debug_addr starting at addrBase 40 | func (addr *DebugAddrSection) GetSubsection(addrBase uint64) *DebugAddr { 41 | if addr == nil { 42 | return nil 43 | } 44 | return &DebugAddr{DebugAddrSection: addr, addrBase: addrBase} 45 | } 46 | 47 | // DebugAddr represents a subsection of the debug_addr section with a specific base address 48 | type DebugAddr struct { 49 | *DebugAddrSection 50 | addrBase uint64 51 | } 52 | 53 | // Get returns the address at index idx starting from addrBase. 54 | func (addr *DebugAddr) Get(idx uint64) (uint64, error) { 55 | if addr == nil || addr.DebugAddrSection == nil { 56 | return 0, errors.New("debug_addr section not present") 57 | } 58 | off := idx*uint64(addr.ptrSz) + addr.addrBase 59 | return dwarf.ReadUintRaw(bytes.NewReader(addr.data[off:]), addr.byteOrder, addr.ptrSz) 60 | } 61 | -------------------------------------------------------------------------------- /third_party/delve/dwarf/leb128/decode.go: -------------------------------------------------------------------------------- 1 | package leb128 2 | 3 | import ( 4 | "io" 5 | ) 6 | 7 | // Reader is a io.ByteReader with a Len method. This interface is 8 | // satisfied by both bytes.Buffer and bytes.Reader. 9 | type Reader interface { 10 | io.ByteReader 11 | io.Reader 12 | Len() int 13 | } 14 | 15 | // DecodeUnsigned decodes an unsigned Little Endian Base 128 16 | // represented number. 17 | func DecodeUnsigned(buf Reader) (uint64, uint32) { 18 | var ( 19 | result uint64 20 | shift uint64 21 | length uint32 22 | ) 23 | 24 | if buf.Len() == 0 { 25 | return 0, 0 26 | } 27 | 28 | for { 29 | b, err := buf.ReadByte() 30 | if err != nil { 31 | panic("Could not parse ULEB128 value") 32 | } 33 | length++ 34 | 35 | result |= uint64((uint(b) & 0x7f) << shift) 36 | 37 | // If high order bit is 1. 38 | if b&0x80 == 0 { 39 | break 40 | } 41 | 42 | shift += 7 43 | } 44 | 45 | return result, length 46 | } 47 | 48 | // DecodeSigned decodes a signed Little Endian Base 128 49 | // represented number. 50 | func DecodeSigned(buf Reader) (int64, uint32) { 51 | var ( 52 | b byte 53 | err error 54 | result int64 55 | shift uint64 56 | length uint32 57 | ) 58 | 59 | if buf.Len() == 0 { 60 | return 0, 0 61 | } 62 | 63 | for { 64 | b, err = buf.ReadByte() 65 | if err != nil { 66 | panic("Could not parse SLEB128 value") 67 | } 68 | length++ 69 | 70 | result |= (int64(b) & 0x7f) << shift 71 | shift += 7 72 | if b&0x80 == 0 { 73 | break 74 | } 75 | } 76 | 77 | if (shift < 8*uint64(length)) && (b&0x40 > 0) { 78 | result |= -(1 << shift) 79 | } 80 | 81 | return result, length 82 | } 83 | -------------------------------------------------------------------------------- /third_party/delve/dwarf/leb128/decode_test.go: -------------------------------------------------------------------------------- 1 | package leb128 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | ) 7 | 8 | func TestDecodeUnsigned(t *testing.T) { 9 | leb128 := bytes.NewBuffer([]byte{0xE5, 0x8E, 0x26}) 10 | 11 | n, c := DecodeUnsigned(leb128) 12 | if n != 624485 { 13 | t.Fatal("Number was not decoded properly, got: ", n, c) 14 | } 15 | 16 | if c != 3 { 17 | t.Fatal("Count not returned correctly") 18 | } 19 | } 20 | 21 | func TestDecodeSigned(t *testing.T) { 22 | sleb128 := bytes.NewBuffer([]byte{0x9b, 0xf1, 0x59}) 23 | 24 | n, c := DecodeSigned(sleb128) 25 | if n != -624485 { 26 | t.Fatal("Number was not decoded properly, got: ", n, c) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /third_party/delve/dwarf/leb128/doc.go: -------------------------------------------------------------------------------- 1 | // Package leb128 provides encoders and decoders for The Little Endian Base 128 format. 2 | // The Little Endian Base 128 format is defined in the DWARF v4 standard, 3 | // section 7.6, page 161 and following. 4 | package leb128 5 | -------------------------------------------------------------------------------- /third_party/delve/dwarf/leb128/encode.go: -------------------------------------------------------------------------------- 1 | package leb128 2 | 3 | import ( 4 | "io" 5 | ) 6 | 7 | // EncodeUnsigned encodes x to the unsigned Little Endian Base 128 format. 8 | func EncodeUnsigned(out io.ByteWriter, x uint64) { 9 | for { 10 | b := byte(x & 0x7f) 11 | x = x >> 7 12 | if x != 0 { 13 | b = b | 0x80 14 | } 15 | out.WriteByte(b) 16 | if x == 0 { 17 | break 18 | } 19 | } 20 | } 21 | 22 | // EncodeSigned encodes x to the signed Little Endian Base 128 format. 23 | func EncodeSigned(out io.ByteWriter, x int64) { 24 | for { 25 | b := byte(x & 0x7f) 26 | x >>= 7 27 | 28 | signb := b & 0x40 29 | 30 | last := false 31 | if (x == 0 && signb == 0) || (x == -1 && signb != 0) { 32 | last = true 33 | } else { 34 | b = b | 0x80 35 | } 36 | out.WriteByte(b) 37 | 38 | if last { 39 | break 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /third_party/delve/dwarf/leb128/encode_test.go: -------------------------------------------------------------------------------- 1 | package leb128 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | ) 7 | 8 | func TestEncodeUnsigned(t *testing.T) { 9 | tc := []uint64{0x00, 0x7f, 0x80, 0x8f, 0xffff, 0xfffffff7} 10 | for i := range tc { 11 | var buf bytes.Buffer 12 | EncodeUnsigned(&buf, tc[i]) 13 | enc := append([]byte{}, buf.Bytes()...) 14 | buf.Write([]byte{0x1, 0x2, 0x3}) 15 | out, c := DecodeUnsigned(&buf) 16 | t.Logf("input %x output %x encoded %x", tc[i], out, enc) 17 | if c != uint32(len(enc)) { 18 | t.Errorf("wrong encode") 19 | } 20 | if out != tc[i] { 21 | t.Errorf("wrong encode") 22 | } 23 | } 24 | } 25 | 26 | func TestEncodeSigned(t *testing.T) { 27 | tc := []int64{2, -2, 127, -127, 128, -128, 129, -129} 28 | for i := range tc { 29 | var buf bytes.Buffer 30 | EncodeSigned(&buf, tc[i]) 31 | enc := append([]byte{}, buf.Bytes()...) 32 | buf.Write([]byte{0x1, 0x2, 0x3}) 33 | out, c := DecodeSigned(&buf) 34 | t.Logf("input %x output %x encoded %x", tc[i], out, enc) 35 | if c != uint32(len(enc)) { 36 | t.Errorf("wrong encode") 37 | } 38 | if out != tc[i] { 39 | t.Errorf("wrong encode") 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /third_party/delve/dwarf/loclist/dwarf2_loclist.go: -------------------------------------------------------------------------------- 1 | package loclist 2 | 3 | import ( 4 | "encoding/binary" 5 | ) 6 | 7 | // Dwarf2Reader parses and presents DWARF loclist information for DWARF versions 2 through 4. 8 | type Dwarf2Reader struct { 9 | data []byte 10 | cur int 11 | ptrSz int 12 | } 13 | 14 | // NewDwarf2Reader returns an initialized loclist Reader for DWARF versions 2 through 4. 15 | func NewDwarf2Reader(data []byte, ptrSz int) *Dwarf2Reader { 16 | return &Dwarf2Reader{data: data, ptrSz: ptrSz} 17 | } 18 | 19 | // Empty returns true if this reader has no data. 20 | func (rdr *Dwarf2Reader) Empty() bool { 21 | return rdr.data == nil 22 | } 23 | 24 | // Seek moves the data pointer to the specified offset. 25 | func (rdr *Dwarf2Reader) Seek(off int) { 26 | rdr.cur = off 27 | } 28 | 29 | // Next advances the reader to the next loclist entry, returning 30 | // the entry and true if successful, or nil, false if not. 31 | func (rdr *Dwarf2Reader) Next(e *Entry) bool { 32 | e.LowPC = rdr.oneAddr() 33 | e.HighPC = rdr.oneAddr() 34 | 35 | if e.LowPC == 0 && e.HighPC == 0 { 36 | return false 37 | } 38 | 39 | if e.BaseAddressSelection() { 40 | e.Instr = nil 41 | return true 42 | } 43 | 44 | instrlen := binary.LittleEndian.Uint16(rdr.read(2)) 45 | e.Instr = rdr.read(int(instrlen)) 46 | return true 47 | } 48 | 49 | func (rdr *Dwarf2Reader) read(sz int) []byte { 50 | r := rdr.data[rdr.cur : rdr.cur+sz] 51 | rdr.cur += sz 52 | return r 53 | } 54 | 55 | func (rdr *Dwarf2Reader) oneAddr() uint64 { 56 | switch rdr.ptrSz { 57 | case 4: 58 | addr := binary.LittleEndian.Uint32(rdr.read(rdr.ptrSz)) 59 | if addr == ^uint32(0) { 60 | return ^uint64(0) 61 | } 62 | return uint64(addr) 63 | case 8: 64 | addr := binary.LittleEndian.Uint64(rdr.read(rdr.ptrSz)) 65 | return addr 66 | default: 67 | panic("bad address size") 68 | } 69 | } 70 | 71 | // Entry represents a single entry in the loclist section. 72 | type Entry struct { 73 | LowPC, HighPC uint64 74 | Instr []byte 75 | } 76 | 77 | // BaseAddressSelection returns true if entry.highpc should 78 | // be used as the base address for subsequent entries. 79 | func (e *Entry) BaseAddressSelection() bool { 80 | return e.LowPC == ^uint64(0) 81 | } 82 | -------------------------------------------------------------------------------- /third_party/delve/dwarf/loclist/dwarf5_loclist.go: -------------------------------------------------------------------------------- 1 | package loclist 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "fmt" 7 | 8 | "golang.org/x/debug/third_party/delve/dwarf" 9 | "golang.org/x/debug/third_party/delve/dwarf/godwarf" 10 | "golang.org/x/debug/third_party/delve/dwarf/leb128" 11 | ) 12 | 13 | // Dwarf5Reader parses and presents DWARF loclist information for DWARF version 5 and later. 14 | // See DWARFv5 section 7.29 page 243 and following. 15 | type Dwarf5Reader struct { 16 | byteOrder binary.ByteOrder 17 | ptrSz int 18 | data []byte 19 | } 20 | 21 | func NewDwarf5Reader(data []byte) *Dwarf5Reader { 22 | if len(data) == 0 { 23 | return nil 24 | } 25 | r := &Dwarf5Reader{data: data} 26 | 27 | _, dwarf64, _, byteOrder := dwarf.ReadDwarfLengthVersion(data) 28 | r.byteOrder = byteOrder 29 | 30 | data = data[6:] 31 | if dwarf64 { 32 | data = data[8:] 33 | } 34 | 35 | addrSz := data[0] 36 | segSelSz := data[1] 37 | r.ptrSz = int(addrSz + segSelSz) 38 | 39 | // Not read: 40 | // - offset_entry_count (4 bytes) 41 | // - offset table (offset_entry_count*4 or offset_entry_count*8 if dwarf64 is set) 42 | 43 | return r 44 | } 45 | 46 | func (rdr *Dwarf5Reader) Empty() bool { 47 | return rdr == nil 48 | } 49 | 50 | type loclistsIterator struct { 51 | rdr *Dwarf5Reader 52 | debugAddr *godwarf.DebugAddr 53 | buf *bytes.Buffer 54 | staticBase uint64 55 | base uint64 // base for offsets in the list 56 | 57 | onRange bool 58 | atEnd bool 59 | start, end uint64 60 | instr []byte 61 | defaultInstr []byte 62 | err error 63 | } 64 | 65 | const ( 66 | _DW_LLE_end_of_list uint8 = 0x0 67 | _DW_LLE_base_addressx uint8 = 0x1 68 | _DW_LLE_startx_endx uint8 = 0x2 69 | _DW_LLE_startx_length uint8 = 0x3 70 | _DW_LLE_offset_pair uint8 = 0x4 71 | _DW_LLE_default_location uint8 = 0x5 72 | _DW_LLE_base_address uint8 = 0x6 73 | _DW_LLE_start_end uint8 = 0x7 74 | _DW_LLE_start_length uint8 = 0x8 75 | ) 76 | 77 | func (it *loclistsIterator) next() bool { 78 | if it.err != nil || it.atEnd { 79 | return false 80 | } 81 | opcode, err := it.buf.ReadByte() 82 | if err != nil { 83 | it.err = err 84 | return false 85 | } 86 | switch opcode { 87 | case _DW_LLE_end_of_list: 88 | it.atEnd = true 89 | it.onRange = false 90 | return false 91 | 92 | case _DW_LLE_base_addressx: 93 | baseIdx, _ := leb128.DecodeUnsigned(it.buf) 94 | it.base, it.err = it.debugAddr.Get(baseIdx) 95 | it.base += it.staticBase 96 | it.onRange = false 97 | 98 | case _DW_LLE_startx_endx: 99 | startIdx, _ := leb128.DecodeUnsigned(it.buf) 100 | endIdx, _ := leb128.DecodeUnsigned(it.buf) 101 | it.readInstr() 102 | 103 | it.start, it.err = it.debugAddr.Get(startIdx) 104 | if it.err == nil { 105 | it.end, it.err = it.debugAddr.Get(endIdx) 106 | } 107 | it.onRange = true 108 | 109 | case _DW_LLE_startx_length: 110 | startIdx, _ := leb128.DecodeUnsigned(it.buf) 111 | length, _ := leb128.DecodeUnsigned(it.buf) 112 | it.readInstr() 113 | 114 | it.start, it.err = it.debugAddr.Get(startIdx) 115 | it.end = it.start + length 116 | it.onRange = true 117 | 118 | case _DW_LLE_offset_pair: 119 | off1, _ := leb128.DecodeUnsigned(it.buf) 120 | off2, _ := leb128.DecodeUnsigned(it.buf) 121 | it.readInstr() 122 | 123 | it.start = it.base + off1 124 | it.end = it.base + off2 125 | it.onRange = true 126 | 127 | case _DW_LLE_default_location: 128 | it.readInstr() 129 | it.defaultInstr = it.instr 130 | it.onRange = false 131 | 132 | case _DW_LLE_base_address: 133 | it.base, it.err = dwarf.ReadUintRaw(it.buf, it.rdr.byteOrder, it.rdr.ptrSz) 134 | it.base += it.staticBase 135 | it.onRange = false 136 | 137 | case _DW_LLE_start_end: 138 | it.start, it.err = dwarf.ReadUintRaw(it.buf, it.rdr.byteOrder, it.rdr.ptrSz) 139 | it.end, it.err = dwarf.ReadUintRaw(it.buf, it.rdr.byteOrder, it.rdr.ptrSz) 140 | it.readInstr() 141 | it.onRange = true 142 | 143 | case _DW_LLE_start_length: 144 | it.start, it.err = dwarf.ReadUintRaw(it.buf, it.rdr.byteOrder, it.rdr.ptrSz) 145 | length, _ := leb128.DecodeUnsigned(it.buf) 146 | it.readInstr() 147 | it.end = it.start + length 148 | it.onRange = true 149 | 150 | default: 151 | it.err = fmt.Errorf("unknown opcode %#x at %#x", opcode, len(it.rdr.data)-it.buf.Len()) 152 | it.onRange = false 153 | it.atEnd = true 154 | return false 155 | } 156 | 157 | return true 158 | } 159 | 160 | func (it *loclistsIterator) readInstr() { 161 | length, _ := leb128.DecodeUnsigned(it.buf) 162 | it.instr = it.buf.Next(int(length)) 163 | } 164 | -------------------------------------------------------------------------------- /third_party/delve/dwarf/loclist/dwarf5_loclist_additions.go: -------------------------------------------------------------------------------- 1 | package loclist 2 | 3 | import ( 4 | "bytes" 5 | 6 | "golang.org/x/debug/third_party/delve/dwarf/godwarf" 7 | ) 8 | 9 | // Enumerate walks through all of the location list entries for a 10 | // given variable in a given function and enumerates them, returning 11 | // to the client. Note that this function doesn't exist in the delve 12 | // source; it was written here so as to be able to do something 13 | // similar to what's done with DWARF 2 location lists. Here off is the 14 | // offset within the .debug_loclists section containing the start of 15 | // the entries for the function in question, staticBase is the 16 | // start-of-text address for the executable, and debugAddr 17 | // encapsulates the portion of the .debug_addr section containing 18 | // entries for the current compilation unit. 19 | func (rdr *Dwarf5Reader) Enumerate(off int64, staticBase uint64, debugAddr *godwarf.DebugAddr) ([]Entry, error) { 20 | result := []Entry{} 21 | 22 | it := &loclistsIterator{rdr: rdr, debugAddr: debugAddr, buf: bytes.NewBuffer(rdr.data), staticBase: staticBase} 23 | it.buf.Next(int(off)) 24 | 25 | for it.next() { 26 | if !it.onRange { 27 | continue 28 | } 29 | e := Entry{it.start, it.end, it.instr} 30 | result = append(result, e) 31 | } 32 | 33 | return result, it.err 34 | } 35 | -------------------------------------------------------------------------------- /third_party/delve/dwarf/op/op_test.go: -------------------------------------------------------------------------------- 1 | package op 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | ) 7 | 8 | func assertExprResult(t *testing.T, expected int64, instructions []byte) { 9 | t.Helper() 10 | actual, _, err := ExecuteStackProgram(DwarfRegisters{}, instructions, 8, nil) 11 | if err != nil { 12 | t.Error(err) 13 | } 14 | if actual != expected { 15 | buf := new(strings.Builder) 16 | PrettyPrint(buf, instructions, nil) 17 | t.Errorf("actual %d != expected %d (in %s)", actual, expected, buf.String()) 18 | } 19 | } 20 | 21 | func TestExecuteStackProgram(t *testing.T) { 22 | assertExprResult(t, 56, []byte{byte(DW_OP_consts), 0x1c, byte(DW_OP_consts), 0x1c, byte(DW_OP_plus)}) 23 | } 24 | 25 | func TestSignExtension(t *testing.T) { 26 | var tgt uint64 = 0xffffffffffffff88 27 | assertExprResult(t, int64(tgt), []byte{byte(DW_OP_const1s), 0x88}) 28 | tgt = 0xffffffffffff8888 29 | assertExprResult(t, int64(tgt), []byte{byte(DW_OP_const2s), 0x88, 0x88}) 30 | } 31 | 32 | func TestStackOps(t *testing.T) { 33 | assertExprResult(t, 1, []byte{byte(DW_OP_lit1), byte(DW_OP_lit2), byte(DW_OP_drop)}) 34 | assertExprResult(t, 0, []byte{byte(DW_OP_lit1), byte(DW_OP_lit0), byte(DW_OP_pick), 0}) 35 | assertExprResult(t, 1, []byte{byte(DW_OP_lit1), byte(DW_OP_lit0), byte(DW_OP_pick), 1}) 36 | } 37 | 38 | func TestBra(t *testing.T) { 39 | assertExprResult(t, 32, []byte{ 40 | byte(DW_OP_lit1), 41 | byte(DW_OP_lit5), 42 | 43 | byte(DW_OP_dup), 44 | byte(DW_OP_lit0), 45 | byte(DW_OP_eq), 46 | byte(DW_OP_bra), 9, 0x0, 47 | 48 | byte(DW_OP_swap), 49 | byte(DW_OP_dup), 50 | byte(DW_OP_plus), 51 | byte(DW_OP_swap), 52 | byte(DW_OP_lit1), 53 | byte(DW_OP_minus), 54 | byte(DW_OP_skip), 0xf1, 0xff, 55 | 56 | byte(DW_OP_drop), 57 | }) 58 | } 59 | -------------------------------------------------------------------------------- /third_party/delve/dwarf/op/opcodes.table: -------------------------------------------------------------------------------- 1 | // This file is used by _scripts/gen-opcodes.go to generate 2 | // pkg/dwarf/op/opcodes.go 3 | // Lines starting with // are comments and will be discarded. 4 | // Non empty lines contain the following tab separated fields: 5 | // 6 | // 7 | // 8 | // With the last column, , being optional. 9 | // 10 | // The arguments field should contain a string with one character for each 11 | // argument of the opcode: 12 | // 13 | // s signed variable length integer 14 | // u unsigned variable length integer 15 | // 1 one byte unsigned integer 16 | // 2 two bytes unsigned integer 17 | // 4 four bytes unsigned integer 18 | // 8 eight bytes unsigned integer 19 | // B an unsigned variable length integer 'n' followed by n a block of n bytes 20 | 21 | 22 | DW_OP_addr 0x03 "8" addr 23 | DW_OP_deref 0x06 "" deref 24 | DW_OP_const1u 0x08 "1" constnu 25 | DW_OP_const1s 0x09 "1" constns 26 | DW_OP_const2u 0x0a "2" constnu 27 | DW_OP_const2s 0x0b "2" constns 28 | DW_OP_const4u 0x0c "4" constnu 29 | DW_OP_const4s 0x0d "4" constns 30 | DW_OP_const8u 0x0e "8" constnu 31 | DW_OP_const8s 0x0f "8" constns 32 | DW_OP_constu 0x10 "u" constu 33 | DW_OP_consts 0x11 "s" consts 34 | DW_OP_dup 0x12 "" dup 35 | DW_OP_drop 0x13 "" drop 36 | DW_OP_over 0x14 "" pick 37 | DW_OP_pick 0x15 "" pick 38 | DW_OP_swap 0x16 "" swap 39 | DW_OP_rot 0x17 "" rot 40 | DW_OP_xderef 0x18 "" deref 41 | DW_OP_abs 0x19 "" unaryop 42 | DW_OP_and 0x1a "" binaryop 43 | DW_OP_div 0x1b "" binaryop 44 | DW_OP_minus 0x1c "" binaryop 45 | DW_OP_mod 0x1d "" binaryop 46 | DW_OP_mul 0x1e "" binaryop 47 | DW_OP_neg 0x1f "" unaryop 48 | DW_OP_not 0x20 "" unaryop 49 | DW_OP_or 0x21 "" binaryop 50 | DW_OP_plus 0x22 "" binaryop 51 | DW_OP_plus_uconst 0x23 "u" plusuconsts 52 | DW_OP_shl 0x24 "" binaryop 53 | DW_OP_shr 0x25 "" binaryop 54 | DW_OP_shra 0x26 "" binaryop 55 | DW_OP_xor 0x27 "" binaryop 56 | DW_OP_bra 0x28 "2" bra 57 | DW_OP_eq 0x29 "" binaryop 58 | DW_OP_ge 0x2a "" binaryop 59 | DW_OP_gt 0x2b "" binaryop 60 | DW_OP_le 0x2c "" binaryop 61 | DW_OP_lt 0x2d "" binaryop 62 | DW_OP_ne 0x2e "" binaryop 63 | DW_OP_skip 0x2f "2" skip 64 | DW_OP_lit0 0x30 "" literal 65 | DW_OP_lit1 0x31 "" literal 66 | DW_OP_lit2 0x32 "" literal 67 | DW_OP_lit3 0x33 "" literal 68 | DW_OP_lit4 0x34 "" literal 69 | DW_OP_lit5 0x35 "" literal 70 | DW_OP_lit6 0x36 "" literal 71 | DW_OP_lit7 0x37 "" literal 72 | DW_OP_lit8 0x38 "" literal 73 | DW_OP_lit9 0x39 "" literal 74 | DW_OP_lit10 0x3a "" literal 75 | DW_OP_lit11 0x3b "" literal 76 | DW_OP_lit12 0x3c "" literal 77 | DW_OP_lit13 0x3d "" literal 78 | DW_OP_lit14 0x3e "" literal 79 | DW_OP_lit15 0x3f "" literal 80 | DW_OP_lit16 0x40 "" literal 81 | DW_OP_lit17 0x41 "" literal 82 | DW_OP_lit18 0x42 "" literal 83 | DW_OP_lit19 0x43 "" literal 84 | DW_OP_lit20 0x44 "" literal 85 | DW_OP_lit21 0x45 "" literal 86 | DW_OP_lit22 0x46 "" literal 87 | DW_OP_lit23 0x47 "" literal 88 | DW_OP_lit24 0x48 "" literal 89 | DW_OP_lit25 0x49 "" literal 90 | DW_OP_lit26 0x4a "" literal 91 | DW_OP_lit27 0x4b "" literal 92 | DW_OP_lit28 0x4c "" literal 93 | DW_OP_lit29 0x4d "" literal 94 | DW_OP_lit30 0x4e "" literal 95 | DW_OP_lit31 0x4f "" literal 96 | DW_OP_reg0 0x50 "" register 97 | DW_OP_reg1 0x51 "" register 98 | DW_OP_reg2 0x52 "" register 99 | DW_OP_reg3 0x53 "" register 100 | DW_OP_reg4 0x54 "" register 101 | DW_OP_reg5 0x55 "" register 102 | DW_OP_reg6 0x56 "" register 103 | DW_OP_reg7 0x57 "" register 104 | DW_OP_reg8 0x58 "" register 105 | DW_OP_reg9 0x59 "" register 106 | DW_OP_reg10 0x5a "" register 107 | DW_OP_reg11 0x5b "" register 108 | DW_OP_reg12 0x5c "" register 109 | DW_OP_reg13 0x5d "" register 110 | DW_OP_reg14 0x5e "" register 111 | DW_OP_reg15 0x5f "" register 112 | DW_OP_reg16 0x60 "" register 113 | DW_OP_reg17 0x61 "" register 114 | DW_OP_reg18 0x62 "" register 115 | DW_OP_reg19 0x63 "" register 116 | DW_OP_reg20 0x64 "" register 117 | DW_OP_reg21 0x65 "" register 118 | DW_OP_reg22 0x66 "" register 119 | DW_OP_reg23 0x67 "" register 120 | DW_OP_reg24 0x68 "" register 121 | DW_OP_reg25 0x69 "" register 122 | DW_OP_reg26 0x6a "" register 123 | DW_OP_reg27 0x6b "" register 124 | DW_OP_reg28 0x6c "" register 125 | DW_OP_reg29 0x6d "" register 126 | DW_OP_reg30 0x6e "" register 127 | DW_OP_reg31 0x6f "" register 128 | DW_OP_breg0 0x70 "s" bregister 129 | DW_OP_breg1 0x71 "s" bregister 130 | DW_OP_breg2 0x72 "s" bregister 131 | DW_OP_breg3 0x73 "s" bregister 132 | DW_OP_breg4 0x74 "s" bregister 133 | DW_OP_breg5 0x75 "s" bregister 134 | DW_OP_breg6 0x76 "s" bregister 135 | DW_OP_breg7 0x77 "s" bregister 136 | DW_OP_breg8 0x78 "s" bregister 137 | DW_OP_breg9 0x79 "s" bregister 138 | DW_OP_breg10 0x7a "s" bregister 139 | DW_OP_breg11 0x7b "s" bregister 140 | DW_OP_breg12 0x7c "s" bregister 141 | DW_OP_breg13 0x7d "s" bregister 142 | DW_OP_breg14 0x7e "s" bregister 143 | DW_OP_breg15 0x7f "s" bregister 144 | DW_OP_breg16 0x80 "s" bregister 145 | DW_OP_breg17 0x81 "s" bregister 146 | DW_OP_breg18 0x82 "s" bregister 147 | DW_OP_breg19 0x83 "s" bregister 148 | DW_OP_breg20 0x84 "s" bregister 149 | DW_OP_breg21 0x85 "s" bregister 150 | DW_OP_breg22 0x86 "s" bregister 151 | DW_OP_breg23 0x87 "s" bregister 152 | DW_OP_breg24 0x88 "s" bregister 153 | DW_OP_breg25 0x89 "s" bregister 154 | DW_OP_breg26 0x8a "s" bregister 155 | DW_OP_breg27 0x8b "s" bregister 156 | DW_OP_breg28 0x8c "s" bregister 157 | DW_OP_breg29 0x8d "s" bregister 158 | DW_OP_breg30 0x8e "s" bregister 159 | DW_OP_breg31 0x8f "s" bregister 160 | DW_OP_regx 0x90 "s" register 161 | DW_OP_fbreg 0x91 "s" framebase 162 | DW_OP_bregx 0x92 "us" bregister 163 | DW_OP_piece 0x93 "u" piece 164 | DW_OP_deref_size 0x94 "1" deref 165 | DW_OP_xderef_size 0x95 "1" deref 166 | DW_OP_nop 0x96 "" 167 | DW_OP_push_object_address 0x97 "" 168 | DW_OP_call2 0x98 "2" 169 | DW_OP_call4 0x99 "4" 170 | DW_OP_call_ref 0x9a "4" 171 | DW_OP_form_tls_address 0x9b "" 172 | DW_OP_call_frame_cfa 0x9c "" callframecfa 173 | DW_OP_bit_piece 0x9d "uu" 174 | DW_OP_implicit_value 0x9e "B" implicitvalue 175 | DW_OP_stack_value 0x9f "" stackvalue 176 | -------------------------------------------------------------------------------- /third_party/delve/dwarf/op/regs.go: -------------------------------------------------------------------------------- 1 | package op 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | ) 7 | 8 | // DwarfRegisters holds the value of stack program registers. 9 | type DwarfRegisters struct { 10 | StaticBase uint64 11 | 12 | CFA int64 13 | FrameBase int64 14 | ObjBase int64 15 | regs []*DwarfRegister 16 | 17 | ByteOrder binary.ByteOrder 18 | PCRegNum uint64 19 | SPRegNum uint64 20 | BPRegNum uint64 21 | LRRegNum uint64 22 | ChangeFunc RegisterChangeFunc 23 | 24 | FloatLoadError error // error produced when loading floating point registers 25 | loadMoreCallback func() 26 | } 27 | 28 | type DwarfRegister struct { 29 | Uint64Val uint64 30 | Bytes []byte 31 | } 32 | 33 | type RegisterChangeFunc func(regNum uint64, reg *DwarfRegister) error 34 | 35 | // NewDwarfRegisters returns a new DwarfRegisters object. 36 | func NewDwarfRegisters(staticBase uint64, regs []*DwarfRegister, byteOrder binary.ByteOrder, pcRegNum, spRegNum, bpRegNum, lrRegNum uint64) *DwarfRegisters { 37 | return &DwarfRegisters{ 38 | StaticBase: staticBase, 39 | regs: regs, 40 | ByteOrder: byteOrder, 41 | PCRegNum: pcRegNum, 42 | SPRegNum: spRegNum, 43 | BPRegNum: bpRegNum, 44 | LRRegNum: lrRegNum, 45 | } 46 | } 47 | 48 | // SetLoadMoreCallback sets a callback function that will be called the 49 | // first time the user of regs tries to access an undefined register. 50 | func (regs *DwarfRegisters) SetLoadMoreCallback(fn func()) { 51 | regs.loadMoreCallback = fn 52 | } 53 | 54 | // CurrentSize returns the current number of known registers. This number might be 55 | // wrong if loadMoreCallback has been set. 56 | func (regs *DwarfRegisters) CurrentSize() int { 57 | return len(regs.regs) 58 | } 59 | 60 | // Uint64Val returns the uint64 value of register idx. 61 | func (regs *DwarfRegisters) Uint64Val(idx uint64) uint64 { 62 | reg := regs.Reg(idx) 63 | if reg == nil { 64 | return 0 65 | } 66 | return regs.regs[idx].Uint64Val 67 | } 68 | 69 | // Bytes returns the bytes value of register idx, nil if the register is not 70 | // defined. 71 | func (regs *DwarfRegisters) Bytes(idx uint64) []byte { 72 | reg := regs.Reg(idx) 73 | if reg == nil { 74 | return nil 75 | } 76 | if reg.Bytes == nil { 77 | var buf bytes.Buffer 78 | binary.Write(&buf, regs.ByteOrder, reg.Uint64Val) 79 | reg.Bytes = buf.Bytes() 80 | } 81 | return reg.Bytes 82 | } 83 | 84 | func (regs *DwarfRegisters) loadMore() { 85 | if regs.loadMoreCallback == nil { 86 | return 87 | } 88 | regs.loadMoreCallback() 89 | regs.loadMoreCallback = nil 90 | } 91 | 92 | // Reg returns register idx or nil if the register is not defined. 93 | func (regs *DwarfRegisters) Reg(idx uint64) *DwarfRegister { 94 | if idx >= uint64(len(regs.regs)) { 95 | regs.loadMore() 96 | if idx >= uint64(len(regs.regs)) { 97 | return nil 98 | } 99 | } 100 | if regs.regs[idx] == nil { 101 | regs.loadMore() 102 | } 103 | return regs.regs[idx] 104 | } 105 | 106 | func (regs *DwarfRegisters) PC() uint64 { 107 | return regs.Uint64Val(regs.PCRegNum) 108 | } 109 | 110 | func (regs *DwarfRegisters) SP() uint64 { 111 | return regs.Uint64Val(regs.SPRegNum) 112 | } 113 | 114 | func (regs *DwarfRegisters) BP() uint64 { 115 | return regs.Uint64Val(regs.BPRegNum) 116 | } 117 | 118 | // AddReg adds register idx to regs. 119 | func (regs *DwarfRegisters) AddReg(idx uint64, reg *DwarfRegister) { 120 | if idx >= uint64(len(regs.regs)) { 121 | newRegs := make([]*DwarfRegister, idx+1) 122 | copy(newRegs, regs.regs) 123 | regs.regs = newRegs 124 | } 125 | regs.regs[idx] = reg 126 | } 127 | 128 | // ClearRegisters clears all registers. 129 | func (regs *DwarfRegisters) ClearRegisters() { 130 | regs.loadMoreCallback = nil 131 | for regnum := range regs.regs { 132 | regs.regs[regnum] = nil 133 | } 134 | } 135 | 136 | func DwarfRegisterFromUint64(v uint64) *DwarfRegister { 137 | return &DwarfRegister{Uint64Val: v} 138 | } 139 | 140 | func DwarfRegisterFromBytes(bytes []byte) *DwarfRegister { 141 | var v uint64 142 | switch len(bytes) { 143 | case 1: 144 | v = uint64(bytes[0]) 145 | case 2: 146 | x := binary.LittleEndian.Uint16(bytes) 147 | v = uint64(x) 148 | case 4: 149 | x := binary.LittleEndian.Uint32(bytes) 150 | v = uint64(x) 151 | default: 152 | if len(bytes) >= 8 { 153 | v = binary.LittleEndian.Uint64(bytes[:8]) 154 | } 155 | } 156 | return &DwarfRegister{Uint64Val: v, Bytes: bytes} 157 | } 158 | 159 | // FillBytes fills the Bytes slice of reg using Uint64Val. 160 | func (reg *DwarfRegister) FillBytes() { 161 | if reg.Bytes != nil { 162 | return 163 | } 164 | reg.Bytes = make([]byte, 8) 165 | binary.LittleEndian.PutUint64(reg.Bytes, reg.Uint64Val) 166 | } 167 | 168 | // Overwrite takes the contents of reg and overwrites them with the contents 169 | // of reg2 in little-endian order, returning a new register. The new register 170 | // will always contain the complete contents of both registers, so if reg2 is 171 | // larger than reg, the final register will be reg2's size. 172 | func (reg *DwarfRegister) Overwrite(reg2 *DwarfRegister) *DwarfRegister { 173 | reg.FillBytes() 174 | reg2.FillBytes() 175 | width := len(reg.Bytes) 176 | if len(reg2.Bytes) > len(reg.Bytes) { 177 | width = len(reg2.Bytes) 178 | } 179 | b := make([]byte, width) 180 | copy(b, reg.Bytes) 181 | copy(b, reg2.Bytes) 182 | return DwarfRegisterFromBytes(b) 183 | } 184 | -------------------------------------------------------------------------------- /third_party/delve/dwarf/parseutil.go: -------------------------------------------------------------------------------- 1 | package dwarf 2 | 3 | import ( 4 | "bytes" 5 | "debug/dwarf" 6 | "encoding/binary" 7 | "fmt" 8 | "io" 9 | ) 10 | 11 | // ReadString reads a null-terminated string from data. 12 | func ReadString(data *bytes.Buffer) (string, error) { 13 | str, err := data.ReadString(0x0) 14 | if err != nil { 15 | return "", err 16 | } 17 | 18 | return str[:len(str)-1], nil 19 | } 20 | 21 | // ReadUintRaw reads an integer of ptrSize bytes, with the specified byte order, from reader. 22 | func ReadUintRaw(reader io.Reader, order binary.ByteOrder, ptrSize int) (uint64, error) { 23 | switch ptrSize { 24 | case 2: 25 | var n uint16 26 | if err := binary.Read(reader, order, &n); err != nil { 27 | return 0, err 28 | } 29 | return uint64(n), nil 30 | case 4: 31 | var n uint32 32 | if err := binary.Read(reader, order, &n); err != nil { 33 | return 0, err 34 | } 35 | return uint64(n), nil 36 | case 8: 37 | var n uint64 38 | if err := binary.Read(reader, order, &n); err != nil { 39 | return 0, err 40 | } 41 | return n, nil 42 | } 43 | return 0, fmt.Errorf("pointer size %d not supported", ptrSize) 44 | } 45 | 46 | // WriteUint writes an integer of ptrSize bytes to writer, in the specified byte order. 47 | func WriteUint(writer io.Writer, order binary.ByteOrder, ptrSize int, data uint64) error { 48 | switch ptrSize { 49 | case 4: 50 | return binary.Write(writer, order, uint32(data)) 51 | case 8: 52 | return binary.Write(writer, order, data) 53 | } 54 | return fmt.Errorf("pointer size %d not supported", ptrSize) 55 | } 56 | 57 | // ReadDwarfLengthVersion reads a DWARF length field followed by a version field 58 | func ReadDwarfLengthVersion(data []byte) (length uint64, dwarf64 bool, version uint8, byteOrder binary.ByteOrder) { 59 | if len(data) < 4 { 60 | return 0, false, 0, binary.LittleEndian 61 | } 62 | 63 | lengthfield := binary.LittleEndian.Uint32(data) 64 | voff := 4 65 | if lengthfield == ^uint32(0) { 66 | dwarf64 = true 67 | voff = 12 68 | } 69 | 70 | if voff+1 >= len(data) { 71 | return 0, false, 0, binary.LittleEndian 72 | } 73 | 74 | byteOrder = binary.LittleEndian 75 | x, y := data[voff], data[voff+1] 76 | switch { 77 | default: 78 | fallthrough 79 | case x == 0 && y == 0: 80 | version = 0 81 | byteOrder = binary.LittleEndian 82 | case x == 0: 83 | version = y 84 | byteOrder = binary.BigEndian 85 | case y == 0: 86 | version = x 87 | byteOrder = binary.LittleEndian 88 | } 89 | 90 | if dwarf64 { 91 | length = byteOrder.Uint64(data[4:]) 92 | } else { 93 | length = uint64(byteOrder.Uint32(data)) 94 | } 95 | 96 | return length, dwarf64, version, byteOrder 97 | } 98 | 99 | const ( 100 | _DW_UT_compile = 0x1 + iota 101 | _DW_UT_type 102 | _DW_UT_partial 103 | _DW_UT_skeleton 104 | _DW_UT_split_compile 105 | _DW_UT_split_type 106 | ) 107 | 108 | // ReadUnitVersions reads the DWARF version of each unit in a debug_info section and returns them as a map. 109 | func ReadUnitVersions(data []byte) map[dwarf.Offset]uint8 { 110 | r := make(map[dwarf.Offset]uint8) 111 | off := dwarf.Offset(0) 112 | for len(data) > 0 { 113 | length, dwarf64, version, _ := ReadDwarfLengthVersion(data) 114 | 115 | data = data[4:] 116 | off += 4 117 | secoffsz := 4 118 | if dwarf64 { 119 | off += 8 120 | secoffsz = 8 121 | data = data[8:] 122 | } 123 | 124 | var headerSize int 125 | 126 | switch version { 127 | case 2, 3, 4: 128 | headerSize = 3 + secoffsz 129 | default: // 5 and later? 130 | unitType := data[2] 131 | 132 | switch unitType { 133 | case _DW_UT_compile, _DW_UT_partial: 134 | headerSize = 4 + secoffsz 135 | 136 | case _DW_UT_skeleton, _DW_UT_split_compile: 137 | headerSize = 4 + secoffsz + 8 138 | 139 | case _DW_UT_type, _DW_UT_split_type: 140 | headerSize = 4 + secoffsz + 8 + secoffsz 141 | } 142 | } 143 | 144 | r[off+dwarf.Offset(headerSize)] = version 145 | 146 | data = data[length:] // skip contents 147 | off += dwarf.Offset(length) 148 | } 149 | return r 150 | } 151 | -------------------------------------------------------------------------------- /third_party/delve/dwarf/regnum/amd64.go: -------------------------------------------------------------------------------- 1 | package regnum 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | // The mapping between hardware registers and DWARF registers is specified 9 | // in the System V ABI AMD64 Architecture Processor Supplement v. 1.0 page 61, 10 | // figure 3.36 11 | // https://gitlab.com/x86-psABIs/x86-64-ABI/-/tree/master 12 | 13 | const ( 14 | AMD64_Rax = 0 15 | AMD64_Rdx = 1 16 | AMD64_Rcx = 2 17 | AMD64_Rbx = 3 18 | AMD64_Rsi = 4 19 | AMD64_Rdi = 5 20 | AMD64_Rbp = 6 21 | AMD64_Rsp = 7 22 | AMD64_R8 = 8 23 | AMD64_R9 = 9 24 | AMD64_R10 = 10 25 | AMD64_R11 = 11 26 | AMD64_R12 = 12 27 | AMD64_R13 = 13 28 | AMD64_R14 = 14 29 | AMD64_R15 = 15 30 | AMD64_Rip = 16 31 | AMD64_XMM0 = 17 // XMM1 through XMM15 follow 32 | AMD64_ST0 = 33 // ST(1) through ST(7) follow 33 | AMD64_Rflags = 49 34 | AMD64_Es = 50 35 | AMD64_Cs = 51 36 | AMD64_Ss = 52 37 | AMD64_Ds = 53 38 | AMD64_Fs = 54 39 | AMD64_Gs = 55 40 | AMD64_Fs_base = 58 41 | AMD64_Gs_base = 59 42 | AMD64_MXCSR = 64 43 | AMD64_CW = 65 44 | AMD64_SW = 66 45 | AMD64_XMM16 = 67 // XMM17 through XMM31 follow 46 | AMD64_K0 = 118 // k1 through k7 follow 47 | ) 48 | 49 | var amd64DwarfToName = map[uint64]string{ 50 | AMD64_Rax: "Rax", 51 | AMD64_Rdx: "Rdx", 52 | AMD64_Rcx: "Rcx", 53 | AMD64_Rbx: "Rbx", 54 | AMD64_Rsi: "Rsi", 55 | AMD64_Rdi: "Rdi", 56 | AMD64_Rbp: "Rbp", 57 | AMD64_Rsp: "Rsp", 58 | AMD64_R8: "R8", 59 | AMD64_R9: "R9", 60 | AMD64_R10: "R10", 61 | AMD64_R11: "R11", 62 | AMD64_R12: "R12", 63 | AMD64_R13: "R13", 64 | AMD64_R14: "R14", 65 | AMD64_R15: "R15", 66 | AMD64_Rip: "Rip", 67 | AMD64_XMM0: "XMM0", 68 | AMD64_XMM0 + 1: "XMM1", 69 | AMD64_XMM0 + 2: "XMM2", 70 | AMD64_XMM0 + 3: "XMM3", 71 | AMD64_XMM0 + 4: "XMM4", 72 | AMD64_XMM0 + 5: "XMM5", 73 | AMD64_XMM0 + 6: "XMM6", 74 | AMD64_XMM0 + 7: "XMM7", 75 | AMD64_XMM0 + 8: "XMM8", 76 | AMD64_XMM0 + 9: "XMM9", 77 | AMD64_XMM0 + 10: "XMM10", 78 | AMD64_XMM0 + 11: "XMM11", 79 | AMD64_XMM0 + 12: "XMM12", 80 | AMD64_XMM0 + 13: "XMM13", 81 | AMD64_XMM0 + 14: "XMM14", 82 | AMD64_XMM0 + 15: "XMM15", 83 | AMD64_ST0: "ST(0)", 84 | AMD64_ST0 + 1: "ST(1)", 85 | AMD64_ST0 + 2: "ST(2)", 86 | AMD64_ST0 + 3: "ST(3)", 87 | AMD64_ST0 + 4: "ST(4)", 88 | AMD64_ST0 + 5: "ST(5)", 89 | AMD64_ST0 + 6: "ST(6)", 90 | AMD64_ST0 + 7: "ST(7)", 91 | AMD64_Rflags: "Rflags", 92 | AMD64_Es: "Es", 93 | AMD64_Cs: "Cs", 94 | AMD64_Ss: "Ss", 95 | AMD64_Ds: "Ds", 96 | AMD64_Fs: "Fs", 97 | AMD64_Gs: "Gs", 98 | AMD64_Fs_base: "Fs_base", 99 | AMD64_Gs_base: "Gs_base", 100 | AMD64_MXCSR: "MXCSR", 101 | AMD64_CW: "CW", 102 | AMD64_SW: "SW", 103 | AMD64_XMM16: "XMM16", 104 | AMD64_XMM16 + 1: "XMM17", 105 | AMD64_XMM16 + 2: "XMM18", 106 | AMD64_XMM16 + 3: "XMM19", 107 | AMD64_XMM16 + 4: "XMM20", 108 | AMD64_XMM16 + 5: "XMM21", 109 | AMD64_XMM16 + 6: "XMM22", 110 | AMD64_XMM16 + 7: "XMM23", 111 | AMD64_XMM16 + 8: "XMM24", 112 | AMD64_XMM16 + 9: "XMM25", 113 | AMD64_XMM16 + 10: "XMM26", 114 | AMD64_XMM16 + 11: "XMM27", 115 | AMD64_XMM16 + 12: "XMM28", 116 | AMD64_XMM16 + 13: "XMM29", 117 | AMD64_XMM16 + 14: "XMM30", 118 | AMD64_XMM16 + 15: "XMM31", 119 | AMD64_K0: "K0", 120 | AMD64_K0 + 1: "K1", 121 | AMD64_K0 + 2: "K2", 122 | AMD64_K0 + 3: "K3", 123 | AMD64_K0 + 4: "K4", 124 | AMD64_K0 + 5: "K5", 125 | AMD64_K0 + 6: "K6", 126 | AMD64_K0 + 7: "K7", 127 | } 128 | 129 | var AMD64NameToDwarf = func() map[string]int { 130 | r := make(map[string]int) 131 | for regNum, regName := range amd64DwarfToName { 132 | r[strings.ToLower(regName)] = int(regNum) 133 | } 134 | r["eflags"] = 49 135 | r["st0"] = 33 136 | r["st1"] = 34 137 | r["st2"] = 35 138 | r["st3"] = 36 139 | r["st4"] = 37 140 | r["st5"] = 38 141 | r["st6"] = 39 142 | r["st7"] = 40 143 | return r 144 | }() 145 | 146 | func AMD64MaxRegNum() uint64 { 147 | max := uint64(AMD64_Rip) 148 | for i := range amd64DwarfToName { 149 | if i > max { 150 | max = i 151 | } 152 | } 153 | return max 154 | } 155 | 156 | func AMD64ToName(num uint64) string { 157 | name, ok := amd64DwarfToName[num] 158 | if ok { 159 | return name 160 | } 161 | return fmt.Sprintf("unknown%d", num) 162 | } 163 | -------------------------------------------------------------------------------- /third_party/delve/dwarf/regnum/arm64.go: -------------------------------------------------------------------------------- 1 | package regnum 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | // The mapping between hardware registers and DWARF registers is specified 8 | // in the DWARF for the DWARF for the Arm® 64-bit Architecture (AArch64), 9 | // Section 4, Table 1 10 | // https://github.com/ARM-software/abi-aa/blob/main/aadwarf64/aadwarf64.rst#4arm-specific-dwarf-definitions 11 | 12 | const ( 13 | ARM64_X0 = 0 // X1 through X30 follow 14 | ARM64_BP = 29 // also X29 15 | ARM64_LR = 30 // also X30 16 | ARM64_SP = 31 17 | ARM64_PC = 32 18 | ARM64_V0 = 64 // V1 through V31 follow 19 | _ARM64_MaxRegNum = ARM64_V0 + 31 20 | ) 21 | 22 | func ARM64ToName(num uint64) string { 23 | switch { 24 | case num <= 30: 25 | return fmt.Sprintf("X%d", num) 26 | case num == ARM64_SP: 27 | return "SP" 28 | case num == ARM64_PC: 29 | return "PC" 30 | case num >= ARM64_V0 && num <= 95: 31 | return fmt.Sprintf("V%d", num-64) 32 | default: 33 | return fmt.Sprintf("unknown%d", num) 34 | } 35 | } 36 | 37 | func ARM64MaxRegNum() uint64 { 38 | return _ARM64_MaxRegNum 39 | } 40 | 41 | var ARM64NameToDwarf = func() map[string]int { 42 | r := make(map[string]int) 43 | for i := 0; i <= 32; i++ { 44 | r[fmt.Sprintf("x%d", i)] = ARM64_X0 + i 45 | } 46 | r["fp"] = 29 47 | r["lr"] = 30 48 | r["sp"] = 31 49 | r["pc"] = 32 50 | 51 | for i := 0; i <= 31; i++ { 52 | r[fmt.Sprintf("v%d", i)] = ARM64_V0 + i 53 | } 54 | 55 | return r 56 | }() 57 | -------------------------------------------------------------------------------- /third_party/delve/dwarf/regnum/i386.go: -------------------------------------------------------------------------------- 1 | package regnum 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | // The mapping between hardware registers and DWARF registers is specified 9 | // in the System V ABI Intel386 Architecture Processor Supplement page 25, 10 | // table 2.14 11 | // https://www.uclibc.org/docs/psABI-i386.pdf 12 | 13 | const ( 14 | I386_Eax = 0 15 | I386_Ecx = 1 16 | I386_Edx = 2 17 | I386_Ebx = 3 18 | I386_Esp = 4 19 | I386_Ebp = 5 20 | I386_Esi = 6 21 | I386_Edi = 7 22 | I386_Eip = 8 23 | I386_Eflags = 9 24 | I386_ST0 = 11 // ST(1) through ST(7) follow 25 | I386_XMM0 = 21 // XMM1 through XMM7 follow 26 | I386_Es = 40 27 | I386_Cs = 41 28 | I386_Ss = 42 29 | I386_Ds = 43 30 | I386_Fs = 44 31 | I386_Gs = 45 32 | ) 33 | 34 | var i386DwarfToName = map[int]string{ 35 | I386_Eax: "Eax", 36 | I386_Ecx: "Ecx", 37 | I386_Edx: "Edx", 38 | I386_Ebx: "Ebx", 39 | I386_Esp: "Esp", 40 | I386_Ebp: "Ebp", 41 | I386_Esi: "Esi", 42 | I386_Edi: "Edi", 43 | I386_Eip: "Eip", 44 | I386_Eflags: "Eflags", 45 | I386_ST0: "ST(0)", 46 | I386_ST0 + 1: "ST(1)", 47 | I386_ST0 + 2: "ST(2)", 48 | I386_ST0 + 3: "ST(3)", 49 | I386_ST0 + 4: "ST(4)", 50 | I386_ST0 + 5: "ST(5)", 51 | I386_ST0 + 6: "ST(6)", 52 | I386_ST0 + 7: "ST(7)", 53 | I386_XMM0: "XMM0", 54 | I386_XMM0 + 1: "XMM1", 55 | I386_XMM0 + 2: "XMM2", 56 | I386_XMM0 + 3: "XMM3", 57 | I386_XMM0 + 4: "XMM4", 58 | I386_XMM0 + 5: "XMM5", 59 | I386_XMM0 + 6: "XMM6", 60 | I386_XMM0 + 7: "XMM7", 61 | I386_Es: "Es", 62 | I386_Cs: "Cs", 63 | I386_Ss: "Ss", 64 | I386_Ds: "Ds", 65 | I386_Fs: "Fs", 66 | I386_Gs: "Gs", 67 | } 68 | 69 | var I386NameToDwarf = func() map[string]int { 70 | r := make(map[string]int) 71 | for regNum, regName := range i386DwarfToName { 72 | r[strings.ToLower(regName)] = regNum 73 | } 74 | r["eflags"] = 9 75 | r["st0"] = 11 76 | r["st1"] = 12 77 | r["st2"] = 13 78 | r["st3"] = 14 79 | r["st4"] = 15 80 | r["st5"] = 16 81 | r["st6"] = 17 82 | r["st7"] = 18 83 | return r 84 | }() 85 | 86 | func I386MaxRegNum() int { 87 | max := int(I386_Eip) 88 | for i := range i386DwarfToName { 89 | if i > max { 90 | max = i 91 | } 92 | } 93 | return max 94 | } 95 | 96 | func I386ToName(num uint64) string { 97 | name, ok := i386DwarfToName[int(num)] 98 | if ok { 99 | return name 100 | } 101 | return fmt.Sprintf("unknown%d", num) 102 | } 103 | -------------------------------------------------------------------------------- /third_party/delve/dwarf/regnum/ppc64le.go: -------------------------------------------------------------------------------- 1 | package regnum 2 | 3 | import "fmt" 4 | 5 | // The mapping between hardware registers and DWARF registers is specified 6 | // in the 64-Bit ELF V2 ABI Specification of the Power Architecture in section 7 | // 2.4 DWARF Definition 8 | // https://openpowerfoundation.org/specifications/64bitelfabi/ 9 | 10 | const ( 11 | // General Purpose Registers: from R0 to R31 12 | PPC64LE_FIRST_GPR = 0 13 | PPC64LE_R0 = PPC64LE_FIRST_GPR 14 | PPC64LE_LAST_GPR = 31 15 | // Floating point registers: from F0 to F31 16 | PPC64LE_FIRST_FPR = 32 17 | PPC64LE_F0 = PPC64LE_FIRST_FPR 18 | PPC64LE_LAST_FPR = 63 19 | // Vector (Altivec/VMX) registers: from V0 to V31 20 | PPC64LE_FIRST_VMX = 77 21 | PPC64LE_V0 = PPC64LE_FIRST_VMX 22 | PPC64LE_LAST_VMX = 108 23 | // Vector Scalar (VSX) registers: from VS32 to VS63 24 | // On ppc64le these are mapped to F0 to F31 25 | PPC64LE_FIRST_VSX = 32 26 | PPC64LE_VS0 = PPC64LE_FIRST_VSX 27 | PPC64LE_LAST_VSX = 63 28 | // Condition Registers: from CR0 to CR7 29 | PPC64LE_CR0 = 0 30 | // Special registers 31 | PPC64LE_SP = 1 // Stack frame pointer: Gpr[1] 32 | PPC64LE_PC = 12 // The documentation refers to this as the CIA (Current Instruction Address) 33 | PPC64LE_LR = 65 // Link register 34 | ) 35 | 36 | func PPC64LEToName(num uint64) string { 37 | switch { 38 | case num == PPC64LE_SP: 39 | return "SP" 40 | case num == PPC64LE_PC: 41 | return "PC" 42 | case num == PPC64LE_LR: 43 | return "LR" 44 | case isGPR(num): 45 | return fmt.Sprintf("r%d", int(num-PPC64LE_FIRST_GPR)) 46 | case isFPR(num): 47 | return fmt.Sprintf("f%d", int(num-PPC64LE_FIRST_FPR)) 48 | case isVMX(num): 49 | return fmt.Sprintf("v%d", int(num-PPC64LE_FIRST_VMX)) 50 | case isVSX(num): 51 | return fmt.Sprintf("vs%d", int(num-PPC64LE_FIRST_VSX)) 52 | default: 53 | return fmt.Sprintf("unknown%d", num) 54 | } 55 | } 56 | 57 | // PPC64LEMaxRegNum is 172 registers in total, across 4 categories: 58 | // General Purpose Registers or GPR (32 GPR + 9 special registers) 59 | // Floating Point Registers or FPR (32 FPR + 1 special register) 60 | // Altivec/VMX Registers or VMX (32 VMX + 2 special registers) 61 | // VSX Registers or VSX (64 VSX) 62 | // Documentation: https://lldb.llvm.org/cpp_reference/RegisterContextPOSIX__ppc64le_8cpp_source.html 63 | func PPC64LEMaxRegNum() uint64 { 64 | return 172 65 | } 66 | 67 | func isGPR(num uint64) bool { 68 | return num < PPC64LE_LAST_GPR 69 | } 70 | 71 | func isFPR(num uint64) bool { 72 | return num >= PPC64LE_FIRST_FPR && num <= PPC64LE_LAST_FPR 73 | } 74 | 75 | func isVMX(num uint64) bool { 76 | return num >= PPC64LE_FIRST_VMX && num <= PPC64LE_LAST_VMX 77 | } 78 | 79 | func isVSX(num uint64) bool { 80 | return num >= PPC64LE_FIRST_VSX && num <= PPC64LE_LAST_VSX 81 | } 82 | 83 | var PPC64LENameToDwarf = func() map[string]int { 84 | r := make(map[string]int) 85 | 86 | r["nip"] = PPC64LE_PC 87 | r["sp"] = PPC64LE_SP 88 | r["bp"] = PPC64LE_SP 89 | r["link"] = PPC64LE_LR 90 | 91 | // General Purpose Registers: from R0 to R31 92 | for i := 0; i <= 31; i++ { 93 | r[fmt.Sprintf("r%d", i)] = PPC64LE_R0 + i 94 | } 95 | 96 | // Floating point registers: from F0 to F31 97 | for i := 0; i <= 31; i++ { 98 | r[fmt.Sprintf("f%d", i)] = PPC64LE_F0 + i 99 | } 100 | 101 | // Vector (Altivec/VMX) registers: from V0 to V31 102 | for i := 0; i <= 31; i++ { 103 | r[fmt.Sprintf("v%d", i)] = PPC64LE_V0 + i 104 | } 105 | 106 | // Vector Scalar (VSX) registers: from VS0 to VS63 107 | for i := 0; i <= 63; i++ { 108 | r[fmt.Sprintf("vs%d", i)] = PPC64LE_VS0 + i 109 | } 110 | 111 | // Condition Registers: from CR0 to CR7 112 | for i := 0; i <= 7; i++ { 113 | r[fmt.Sprintf("cr%d", i)] = PPC64LE_CR0 + i 114 | } 115 | return r 116 | }() 117 | -------------------------------------------------------------------------------- /third_party/delve/dwarf/regnum/riscv64.go: -------------------------------------------------------------------------------- 1 | package regnum 2 | 3 | import "fmt" 4 | 5 | // The mapping between hardware registers and DWARF registers, See 6 | // https://github.com/riscv-non-isa/riscv-elf-psabi-doc/blob/master/riscv-dwarf.adoc 7 | 8 | const ( 9 | // Integer Registers 10 | RISCV64_X0 = 0 11 | // Link Register 12 | RISCV64_LR = 1 13 | // Stack Pointer 14 | RISCV64_SP = 2 15 | RISCV64_GP = 3 16 | RISCV64_TP = 4 17 | RISCV64_T0 = 5 18 | RISCV64_T1 = 6 19 | RISCV64_T2 = 7 20 | RISCV64_S0 = 8 21 | // Frame Pointer 22 | RISCV64_FP = RISCV64_S0 23 | RISCV64_S1 = 9 24 | RISCV64_A0 = 10 25 | RISCV64_A1 = 11 26 | RISCV64_A2 = 12 27 | RISCV64_A3 = 13 28 | RISCV64_A4 = 14 29 | RISCV64_A5 = 15 30 | RISCV64_A6 = 16 31 | RISCV64_A7 = 17 32 | RISCV64_S2 = 18 33 | RISCV64_S3 = 19 34 | RISCV64_S4 = 20 35 | RISCV64_S5 = 21 36 | RISCV64_S6 = 22 37 | RISCV64_S7 = 23 38 | RISCV64_S8 = 24 39 | RISCV64_S9 = 25 40 | RISCV64_S10 = 26 41 | // G Register 42 | RISCV64_S11 = 27 43 | RISCV64_T3 = 28 44 | RISCV64_T4 = 29 45 | RISCV64_T5 = 30 46 | RISCV64_T6 = 31 47 | 48 | RISCV64_X31 = RISCV64_T6 49 | 50 | // Floating-point Registers 51 | RISCV64_F0 = 32 52 | RISCV64_F31 = 63 53 | 54 | // Not defined in DWARF specification 55 | RISCV64_PC = 65 56 | 57 | _RISC64_MaxRegNum = RISCV64_PC 58 | ) 59 | 60 | func RISCV64ToName(num uint64) string { 61 | switch { 62 | case num <= RISCV64_X31: 63 | return fmt.Sprintf("X%d", num) 64 | 65 | case num >= RISCV64_F0 && num <= RISCV64_F31: 66 | return fmt.Sprintf("F%d", num) 67 | 68 | case num == RISCV64_PC: 69 | return "PC" 70 | 71 | default: 72 | return fmt.Sprintf("Unknown%d", num) 73 | } 74 | } 75 | 76 | func RISCV64MaxRegNum() uint64 { 77 | return _RISC64_MaxRegNum 78 | } 79 | 80 | var RISCV64NameToDwarf = func() map[string]int { 81 | r := make(map[string]int) 82 | for i := 0; i <= 31; i++ { 83 | r[fmt.Sprintf("x%d", i)] = RISCV64_X0 + i 84 | } 85 | r["pc"] = RISCV64_PC 86 | 87 | for i := 0; i <= 31; i++ { 88 | r[fmt.Sprintf("f%d", i)] = RISCV64_F0 + i 89 | } 90 | 91 | return r 92 | }() 93 | --------------------------------------------------------------------------------