gitlab.com/Raven-IO/raven-delve@v1.22.4/pkg/internal/gosym/symtab.go (about) 1 // Copyright 2009 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 // Package gosym implements access to the Go symbol 5 // and line number tables embedded in Go binaries generated 6 // by the gc compilers. 7 package gosym 8 9 import ( 10 "bytes" 11 "debug/elf" 12 "debug/macho" 13 "encoding/binary" 14 "fmt" 15 "io" 16 "strconv" 17 "strings" 18 ) 19 20 // Sym represents a single symbol table entry. 21 type Sym struct { 22 Value uint64 23 Type byte 24 Name string 25 GoType uint64 26 // If this symbol is a function symbol, the corresponding Func 27 Func *Func 28 goVersion version 29 } 30 31 // Static reports whether this symbol is static (not visible outside its file). 32 func (s *Sym) Static() bool { return s.Type >= 'a' } 33 34 // nameWithoutInst returns s.Name if s.Name has no brackets (does not reference an 35 // instantiated type, function, or method). If s.Name contains brackets, then it 36 // returns s.Name with all the contents between (and including) the outermost left 37 // and right bracket removed. This is useful to ignore any extra slashes or dots 38 // inside the brackets from the string searches below, where needed. 39 func (s *Sym) nameWithoutInst() string { 40 start := strings.Index(s.Name, "[") 41 if start < 0 { 42 return s.Name 43 } 44 end := strings.LastIndex(s.Name, "]") 45 if end < 0 { 46 // Malformed name, should contain closing bracket too. 47 return s.Name 48 } 49 return s.Name[0:start] + s.Name[end+1:] 50 } 51 52 // PackageName returns the package part of the symbol name, 53 // or the empty string if there is none. 54 func (s *Sym) PackageName() string { 55 name := s.nameWithoutInst() 56 // Since go1.20, a prefix of "type:" and "go:" is a compiler-generated symbol, 57 // they do not belong to any package. 58 // 59 // See cmd/compile/internal/base/link.go:ReservedImports variable. 60 if s.goVersion >= ver120 && (strings.HasPrefix(name, "go:") || strings.HasPrefix(name, "type:")) { 61 return "" 62 } 63 // For go1.18 and below, the prefix are "type." and "go." instead. 64 if s.goVersion <= ver118 && (strings.HasPrefix(name, "go.") || strings.HasPrefix(name, "type.")) { 65 return "" 66 } 67 pathend := strings.LastIndex(name, "/") 68 if pathend < 0 { 69 pathend = 0 70 } 71 if i := strings.Index(name[pathend:], "."); i != -1 { 72 return name[:pathend+i] 73 } 74 return "" 75 } 76 77 // ReceiverName returns the receiver type name of this symbol, 78 // or the empty string if there is none. A receiver name is only detected in 79 // the case that s.Name is fully-specified with a package name. 80 func (s *Sym) ReceiverName() string { 81 name := s.nameWithoutInst() 82 // If we find a slash in name, it should precede any bracketed expression 83 // that was removed, so pathend will apply correctly to name and s.Name. 84 pathend := strings.LastIndex(name, "/") 85 if pathend < 0 { 86 pathend = 0 87 } 88 // Find the first dot after pathend (or from the beginning, if there was 89 // no slash in name). 90 l := strings.Index(name[pathend:], ".") 91 // Find the last dot after pathend (or the beginning). 92 r := strings.LastIndex(name[pathend:], ".") 93 if l == -1 || r == -1 || l == r { 94 // There is no receiver if we didn't find two distinct dots after pathend. 95 return "" 96 } 97 // Given there is a trailing '.' that is in name, find it now in s.Name. 98 // pathend+l should apply to s.Name, because it should be the dot in the 99 // package name. 100 r = strings.LastIndex(s.Name[pathend:], ".") 101 return s.Name[pathend+l+1 : pathend+r] 102 } 103 104 // BaseName returns the symbol name without the package or receiver name. 105 func (s *Sym) BaseName() string { 106 name := s.nameWithoutInst() 107 if i := strings.LastIndex(name, "."); i != -1 { 108 if s.Name != name { 109 brack := strings.Index(s.Name, "[") 110 if i > brack { 111 // BaseName is a method name after the brackets, so 112 // recalculate for s.Name. Otherwise, i applies 113 // correctly to s.Name, since it is before the 114 // brackets. 115 i = strings.LastIndex(s.Name, ".") 116 } 117 } 118 return s.Name[i+1:] 119 } 120 return s.Name 121 } 122 123 // A Func collects information about a single function. 124 type Func struct { 125 Entry uint64 126 *Sym 127 End uint64 128 Params []*Sym // nil for Go 1.3 and later binaries 129 Locals []*Sym // nil for Go 1.3 and later binaries 130 FrameSize int 131 LineTable *LineTable 132 Obj *Obj 133 // Addition: extra data to support inlining. 134 inlTree 135 } 136 137 func (T *Table) GetInlineTree(f *Func, goFuncVal, baseaddr uint64, progReader io.ReaderAt) ([]InlinedCall, error) { 138 //func (T *Table) GetInlineTree(f* Func, s *elf.Symbol, baseaddr uint64, progReader io.ReaderAt) ([]InlinedCall, error) { 139 //strver := fmt.Sprint(s.goVersion) 140 //goFuncValue := FuncSymName(strver) 141 //return T.go12line.InlineTree(f,goFuncValue,baseaddr,progReader) 142 return T.go12line.InlineTree(f, goFuncVal, baseaddr, progReader) 143 144 } 145 func ProgContaining(elfFile *elf.File, addr uint64) *elf.Prog { 146 for _, p := range elfFile.Progs { 147 if addr >= p.Vaddr && addr < p.Vaddr+p.Filesz { 148 return p 149 } 150 } 151 return nil 152 } 153 154 func SegmentContaining(exe *macho.File, addr uint64) *macho.Segment { 155 for _, l := range exe.Loads { 156 if s, ok := l.(*macho.Segment); ok && s.Addr <= addr && addr < s.Addr+s.Filesz { 157 return s 158 } 159 } 160 return nil 161 } 162 163 // An Obj represents a collection of functions in a symbol table. 164 // 165 // The exact method of division of a binary into separate Objs is an internal detail 166 // of the symbol table format. 167 // 168 // In early versions of Go each source file became a different Obj. 169 // 170 // In Go 1 and Go 1.1, each package produced one Obj for all Go sources 171 // and one Obj per C source file. 172 // 173 // In Go 1.2, there is a single Obj for the entire program. 174 type Obj struct { 175 // Funcs is a list of functions in the Obj. 176 Funcs []Func 177 // In Go 1.1 and earlier, Paths is a list of symbols corresponding 178 // to the source file names that produced the Obj. 179 // In Go 1.2, Paths is nil. 180 // Use the keys of Table.Files to obtain a list of source files. 181 Paths []Sym // meta 182 } 183 184 // Table represents a Go symbol table. It stores all of the 185 // symbols decoded from the program and provides methods to translate 186 // between symbols, names, and addresses. 187 type Table struct { 188 Syms []Sym // nil for Go 1.3 and later binaries 189 Funcs []Func 190 Files map[string]*Obj // for Go 1.2 and later all files map to one Obj 191 Objs []Obj // for Go 1.2 and later only one Obj in slice 192 go12line *LineTable // Go 1.2 line number table 193 } 194 type sym struct { 195 value uint64 196 gotype uint64 197 typ byte 198 name []byte 199 } 200 201 var ( 202 littleEndianSymtab = []byte{0xFD, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00} 203 bigEndianSymtab = []byte{0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00} 204 oldLittleEndianSymtab = []byte{0xFE, 0xFF, 0xFF, 0xFF, 0x00, 0x00} 205 ) 206 207 func walksymtab(data []byte, fn func(sym) error) error { 208 if len(data) == 0 { // missing symtab is okay 209 return nil 210 } 211 var order binary.ByteOrder = binary.BigEndian 212 newTable := false 213 switch { 214 case bytes.HasPrefix(data, oldLittleEndianSymtab): 215 // Same as Go 1.0, but little endian. 216 // Format was used during interim development between Go 1.0 and Go 1.1. 217 // Should not be widespread, but easy to support. 218 data = data[6:] 219 order = binary.LittleEndian 220 case bytes.HasPrefix(data, bigEndianSymtab): 221 newTable = true 222 case bytes.HasPrefix(data, littleEndianSymtab): 223 newTable = true 224 order = binary.LittleEndian 225 } 226 var ptrsz int 227 if newTable { 228 if len(data) < 8 { 229 //return &DecodingError{len(data), "unexpected EOF", nil} 230 return &DecodingError{len(data), "unexpected EOF", 0} 231 } 232 ptrsz = int(data[7]) 233 if ptrsz != 4 && ptrsz != 8 { 234 return &DecodingError{7, "invalid pointer size", ptrsz} 235 } 236 data = data[8:] 237 } 238 var s sym 239 p := data 240 for len(p) >= 4 { 241 var typ byte 242 if newTable { 243 // Symbol type, value, Go type. 244 typ = p[0] & 0x3F 245 wideValue := p[0]&0x40 != 0 246 goType := p[0]&0x80 != 0 247 if typ < 26 { 248 typ += 'A' 249 } else { 250 typ += 'a' - 26 251 } 252 s.typ = typ 253 p = p[1:] 254 if wideValue { 255 if len(p) < ptrsz { 256 //return &DecodingError{len(data), "unexpected EOF", nil} 257 return &DecodingError{len(data), "unexpected EOF", 0} 258 } 259 // fixed-width value 260 if ptrsz == 8 { 261 s.value = order.Uint64(p[0:8]) 262 p = p[8:] 263 } else { 264 s.value = uint64(order.Uint32(p[0:4])) 265 p = p[4:] 266 } 267 } else { 268 // varint value 269 s.value = 0 270 shift := uint(0) 271 for len(p) > 0 && p[0]&0x80 != 0 { 272 s.value |= uint64(p[0]&0x7F) << shift 273 shift += 7 274 p = p[1:] 275 } 276 if len(p) == 0 { 277 //return &DecodingError{len(data), "unexpected EOF", nil} 278 return &DecodingError{len(data), "unexpected EOF", 0} 279 } 280 s.value |= uint64(p[0]) << shift 281 p = p[1:] 282 } 283 if goType { 284 if len(p) < ptrsz { 285 //return &DecodingError{len(data), "unexpected EOF", nil} 286 return &DecodingError{len(data), "unexpected EOF", 0} 287 } 288 // fixed-width go type 289 if ptrsz == 8 { 290 s.gotype = order.Uint64(p[0:8]) 291 p = p[8:] 292 } else { 293 s.gotype = uint64(order.Uint32(p[0:4])) 294 p = p[4:] 295 } 296 } 297 } else { 298 // Value, symbol type. 299 s.value = uint64(order.Uint32(p[0:4])) 300 if len(p) < 5 { 301 //return &DecodingError{len(data), "unexpected EOF", nil} 302 return &DecodingError{len(data), "unexpected EOF", 0} 303 } 304 typ = p[4] 305 if typ&0x80 == 0 { 306 return &DecodingError{len(data) - len(p) + 4, "bad symbol type", int(typ)} 307 } 308 typ &^= 0x80 309 s.typ = typ 310 p = p[5:] 311 } 312 // Name. 313 var i int 314 var nnul int 315 for i = 0; i < len(p); i++ { 316 if p[i] == 0 { 317 nnul = 1 318 break 319 } 320 } 321 switch typ { 322 case 'z', 'Z': 323 p = p[i+nnul:] 324 for i = 0; i+2 <= len(p); i += 2 { 325 if p[i] == 0 && p[i+1] == 0 { 326 nnul = 2 327 break 328 } 329 } 330 } 331 if len(p) < i+nnul { 332 //return &DecodingError{len(data), "unexpected EOF", nil} 333 return &DecodingError{len(data), "unexpected EOF", 0} 334 } 335 s.name = p[0:i] 336 i += nnul 337 p = p[i:] 338 if !newTable { 339 if len(p) < 4 { 340 //return &DecodingError{len(data), "unexpected EOF", nil} 341 return &DecodingError{len(data), "unexpected EOF", 0} 342 } 343 // Go type. 344 s.gotype = uint64(order.Uint32(p[:4])) 345 p = p[4:] 346 } 347 _ = fn(s) 348 } 349 return nil 350 } 351 352 // NewTable decodes the Go symbol table (the ".gosymtab" section in ELF), 353 // returning an in-memory representation. 354 // Starting with Go 1.3, the Go symbol table no longer includes symbol data. 355 func NewTable(symtab []byte, pcln *LineTable) (*Table, error) { 356 var n int 357 err := walksymtab(symtab, func(s sym) error { 358 n++ 359 return nil 360 }) 361 if err != nil { 362 return nil, err 363 } 364 var t Table 365 if pcln.isGo12() { 366 t.go12line = pcln 367 } 368 fname := make(map[uint16]string) 369 t.Syms = make([]Sym, 0, n) 370 nf := 0 371 nz := 0 372 lasttyp := uint8(0) 373 err = walksymtab(symtab, func(s sym) error { 374 n := len(t.Syms) 375 t.Syms = t.Syms[0 : n+1] 376 ts := &t.Syms[n] 377 ts.Type = s.typ 378 ts.Value = s.value 379 ts.GoType = s.gotype 380 ts.goVersion = pcln.version 381 switch s.typ { 382 default: 383 // rewrite name to use . instead of ยท (c2 b7) 384 w := 0 385 b := s.name 386 for i := 0; i < len(b); i++ { 387 if b[i] == 0xc2 && i+1 < len(b) && b[i+1] == 0xb7 { 388 i++ 389 b[i] = '.' 390 } 391 b[w] = b[i] 392 w++ 393 } 394 ts.Name = string(s.name[0:w]) 395 case 'z', 'Z': 396 if lasttyp != 'z' && lasttyp != 'Z' { 397 nz++ 398 } 399 for i := 0; i < len(s.name); i += 2 { 400 eltIdx := binary.BigEndian.Uint16(s.name[i : i+2]) 401 elt, ok := fname[eltIdx] 402 if !ok { 403 return &DecodingError{-1, "bad filename code", int(eltIdx)} 404 } 405 if n := len(ts.Name); n > 0 && ts.Name[n-1] != '/' { 406 ts.Name += "/" 407 } 408 ts.Name += elt 409 } 410 } 411 switch s.typ { 412 case 'T', 't', 'L', 'l': 413 nf++ 414 case 'f': 415 fname[uint16(s.value)] = ts.Name 416 } 417 lasttyp = s.typ 418 return nil 419 }) 420 if err != nil { 421 return nil, err 422 } 423 t.Funcs = make([]Func, 0, nf) 424 t.Files = make(map[string]*Obj) 425 var obj *Obj 426 if t.go12line != nil { 427 // Put all functions into one Obj. 428 t.Objs = make([]Obj, 1) 429 obj = &t.Objs[0] 430 t.go12line.go12MapFiles(t.Files, obj) 431 } else { 432 t.Objs = make([]Obj, 0, nz) 433 } 434 // Count text symbols and attach frame sizes, parameters, and 435 // locals to them. Also, find object file boundaries. 436 lastf := 0 437 for i := 0; i < len(t.Syms); i++ { 438 sym := &t.Syms[i] 439 switch sym.Type { 440 case 'Z', 'z': // path symbol 441 if t.go12line != nil { 442 // Go 1.2 binaries have the file information elsewhere. Ignore. 443 break 444 } 445 // Finish the current object 446 if obj != nil { 447 obj.Funcs = t.Funcs[lastf:] 448 } 449 lastf = len(t.Funcs) 450 // Start new object 451 n := len(t.Objs) 452 t.Objs = t.Objs[0 : n+1] 453 obj = &t.Objs[n] 454 // Count & copy path symbols 455 var end int 456 for end = i + 1; end < len(t.Syms); end++ { 457 if c := t.Syms[end].Type; c != 'Z' && c != 'z' { 458 break 459 } 460 } 461 obj.Paths = t.Syms[i:end] 462 i = end - 1 // loop will i++ 463 // Record file names 464 depth := 0 465 for j := range obj.Paths { 466 s := &obj.Paths[j] 467 if s.Name == "" { 468 depth-- 469 } else { 470 if depth == 0 { 471 t.Files[s.Name] = obj 472 } 473 depth++ 474 } 475 } 476 case 'T', 't', 'L', 'l': // text symbol 477 if n := len(t.Funcs); n > 0 { 478 t.Funcs[n-1].End = sym.Value 479 } 480 if sym.Name == "runtime.etext" || sym.Name == "etext" { 481 continue 482 } 483 // Count parameter and local (auto) syms 484 var np, na int 485 var end int 486 countloop: 487 for end = i + 1; end < len(t.Syms); end++ { 488 switch t.Syms[end].Type { 489 case 'T', 't', 'L', 'l', 'Z', 'z': 490 break countloop 491 case 'p': 492 np++ 493 case 'a': 494 na++ 495 } 496 } 497 // Fill in the function symbol 498 n := len(t.Funcs) 499 t.Funcs = t.Funcs[0 : n+1] 500 fn := &t.Funcs[n] 501 sym.Func = fn 502 fn.Params = make([]*Sym, 0, np) 503 fn.Locals = make([]*Sym, 0, na) 504 fn.Sym = sym 505 fn.Entry = sym.Value 506 fn.Obj = obj 507 if t.go12line != nil { 508 // All functions share the same line table. 509 // It knows how to narrow down to a specific 510 // function quickly. 511 fn.LineTable = t.go12line 512 } else if pcln != nil { 513 fn.LineTable = pcln.slice(fn.Entry) 514 pcln = fn.LineTable 515 } 516 for j := i; j < end; j++ { 517 s := &t.Syms[j] 518 switch s.Type { 519 case 'm': 520 fn.FrameSize = int(s.Value) 521 case 'p': 522 n := len(fn.Params) 523 fn.Params = fn.Params[0 : n+1] 524 fn.Params[n] = s 525 case 'a': 526 n := len(fn.Locals) 527 fn.Locals = fn.Locals[0 : n+1] 528 fn.Locals[n] = s 529 } 530 } 531 i = end - 1 // loop will i++ 532 } 533 } 534 if t.go12line != nil && nf == 0 { 535 t.Funcs = t.go12line.go12Funcs() 536 } 537 if obj != nil { 538 obj.Funcs = t.Funcs[lastf:] 539 } 540 return &t, nil 541 } 542 543 // PCToFunc returns the function containing the program counter pc, 544 // or nil if there is no such function. 545 func (t *Table) PCToFunc(pc uint64) *Func { 546 funcs := t.Funcs 547 for len(funcs) > 0 { 548 m := len(funcs) / 2 549 fn := &funcs[m] 550 switch { 551 case pc < fn.Entry: 552 funcs = funcs[0:m] 553 case fn.Entry <= pc && pc < fn.End: 554 return fn 555 default: 556 funcs = funcs[m+1:] 557 } 558 } 559 return nil 560 } 561 562 // PCToLine looks up line number information for a program counter. 563 // If there is no information, it returns fn == nil. 564 func (t *Table) PCToLine(pc uint64) (file string, line int, fn *Func) { 565 if fn = t.PCToFunc(pc); fn == nil { 566 return 567 } 568 if t.go12line != nil { 569 file = t.go12line.go12PCToFile(pc) 570 line = t.go12line.go12PCToLine(pc) 571 } else { 572 file, line = fn.Obj.lineFromAline(fn.LineTable.PCToLine(pc)) 573 } 574 return 575 } 576 577 // LineToPC looks up the first program counter on the given line in 578 // the named file. It returns UnknownPathError or UnknownLineError if 579 // there is an error looking up this line. 580 func (t *Table) LineToPC(file string, line int) (pc uint64, fn *Func, err error) { 581 obj, ok := t.Files[file] 582 if !ok { 583 return 0, nil, UnknownFileError(file) 584 } 585 if t.go12line != nil { 586 pc := t.go12line.go12LineToPC(file, line) 587 if pc == 0 { 588 return 0, nil, &UnknownLineError{file, line} 589 } 590 return pc, t.PCToFunc(pc), nil 591 } 592 abs, err := obj.alineFromLine(file, line) 593 if err != nil { 594 return 595 } 596 for i := range obj.Funcs { 597 f := &obj.Funcs[i] 598 pc := f.LineTable.LineToPC(abs, f.End) 599 if pc != 0 { 600 return pc, f, nil 601 } 602 } 603 return 0, nil, &UnknownLineError{file, line} 604 } 605 606 // LookupSym returns the text, data, or bss symbol with the given name, 607 // or nil if no such symbol is found. 608 func (t *Table) LookupSym(name string) *Sym { 609 // TODO(austin) Maybe make a map 610 for i := range t.Syms { 611 s := &t.Syms[i] 612 switch s.Type { 613 case 'T', 't', 'L', 'l', 'D', 'd', 'B', 'b': 614 if s.Name == name { 615 return s 616 } 617 } 618 } 619 return nil 620 } 621 622 // LookupFunc returns the text, data, or bss symbol with the given name, 623 // or nil if no such symbol is found. 624 func (t *Table) LookupFunc(name string) *Func { 625 for i := range t.Funcs { 626 f := &t.Funcs[i] 627 if f.Sym.Name == name { 628 return f 629 } 630 } 631 return nil 632 } 633 634 // SymByAddr returns the text, data, or bss symbol starting at the given address. 635 func (t *Table) SymByAddr(addr uint64) *Sym { 636 for i := range t.Syms { 637 s := &t.Syms[i] 638 switch s.Type { 639 case 'T', 't', 'L', 'l', 'D', 'd', 'B', 'b': 640 if s.Value == addr { 641 return s 642 } 643 } 644 } 645 return nil 646 } 647 648 /* 649 * Object files 650 */ 651 // This is legacy code for Go 1.1 and earlier, which used the 652 // Plan 9 format for pc-line tables. This code was never quite 653 // correct. It's probably very close, and it's usually correct, but 654 // we never quite found all the corner cases. 655 // 656 // Go 1.2 and later use a simpler format, documented at golang.org/s/go12symtab. 657 func (o *Obj) lineFromAline(aline int) (string, int) { 658 type stackEnt struct { 659 path string 660 start int 661 offset int 662 prev *stackEnt 663 } 664 noPath := &stackEnt{"", 0, 0, nil} 665 tos := noPath 666 pathloop: 667 for _, s := range o.Paths { 668 val := int(s.Value) 669 switch { 670 case val > aline: 671 break pathloop 672 case val == 1: 673 // Start a new stack 674 tos = &stackEnt{s.Name, val, 0, noPath} 675 case s.Name == "": 676 // Pop 677 if tos == noPath { 678 return "<malformed symbol table>", 0 679 } 680 tos.prev.offset += val - tos.start 681 tos = tos.prev 682 default: 683 // Push 684 tos = &stackEnt{s.Name, val, 0, tos} 685 } 686 } 687 if tos == noPath { 688 return "", 0 689 } 690 return tos.path, aline - tos.start - tos.offset + 1 691 } 692 func (o *Obj) alineFromLine(path string, line int) (int, error) { 693 if line < 1 { 694 return 0, &UnknownLineError{path, line} 695 } 696 for i, s := range o.Paths { 697 // Find this path 698 if s.Name != path { 699 continue 700 } 701 // Find this line at this stack level 702 depth := 0 703 var incstart int 704 line += int(s.Value) 705 pathloop: 706 for _, s := range o.Paths[i:] { 707 val := int(s.Value) 708 switch { 709 case depth == 1 && val >= line: 710 return line - 1, nil 711 case s.Name == "": 712 depth-- 713 if depth == 0 { 714 break pathloop 715 } else if depth == 1 { 716 line += val - incstart 717 } 718 default: 719 if depth == 1 { 720 incstart = val 721 } 722 depth++ 723 } 724 } 725 return 0, &UnknownLineError{path, line} 726 } 727 return 0, UnknownFileError(path) 728 } 729 730 // UnknownFileError represents a failure to find the specific file in 731 // the symbol table. 732 type UnknownFileError string 733 734 func (e UnknownFileError) Error() string { return "unknown file: " + string(e) } 735 736 // UnknownLineError represents a failure to map a line to a program 737 // counter, either because the line is beyond the bounds of the file 738 // or because there is no code on the given line. 739 type UnknownLineError struct { 740 File string 741 Line int 742 } 743 744 func (e *UnknownLineError) Error() string { 745 return "no code at " + e.File + ":" + strconv.Itoa(e.Line) 746 } 747 748 // DecodingError represents an error during the decoding of 749 // the symbol table. 750 type DecodingError struct { 751 off int 752 msg string 753 val int 754 } 755 756 func (e *DecodingError) Error() string { 757 msg := e.msg 758 //if e.val != nil { 759 if e.val != 0 { 760 msg += fmt.Sprintf(" '%v'", e.val) 761 } 762 msg += fmt.Sprintf(" at byte %#x", e.off) 763 return msg 764 }