github.com/tidwall/go@v0.0.0-20170415222209-6694a6888b7d/src/cmd/internal/goobj/read.go (about) 1 // Copyright 2013 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 goobj implements reading of Go object files and archives. 6 // 7 // TODO(rsc): Decide where this package should live. (golang.org/issue/6932) 8 // TODO(rsc): Decide the appropriate integer types for various fields. 9 // TODO(rsc): Write tests. (File format still up in the air a little.) 10 package goobj 11 12 import ( 13 "bufio" 14 "bytes" 15 "cmd/internal/obj" 16 "errors" 17 "fmt" 18 "io" 19 "strconv" 20 "strings" 21 ) 22 23 // A SymKind describes the kind of memory represented by a symbol. 24 type SymKind int 25 26 // This list is taken from include/link.h. 27 28 // Defined SymKind values. 29 // TODO(rsc): Give idiomatic Go names. 30 // TODO(rsc): Reduce the number of symbol types in the object files. 31 const ( 32 // readonly, executable 33 STEXT = SymKind(obj.STEXT) 34 SELFRXSECT = SymKind(obj.SELFRXSECT) 35 36 // readonly, non-executable 37 STYPE = SymKind(obj.STYPE) 38 SSTRING = SymKind(obj.SSTRING) 39 SGOSTRING = SymKind(obj.SGOSTRING) 40 SGOFUNC = SymKind(obj.SGOFUNC) 41 SRODATA = SymKind(obj.SRODATA) 42 SFUNCTAB = SymKind(obj.SFUNCTAB) 43 STYPELINK = SymKind(obj.STYPELINK) 44 SITABLINK = SymKind(obj.SITABLINK) 45 SSYMTAB = SymKind(obj.SSYMTAB) // TODO: move to unmapped section 46 SPCLNTAB = SymKind(obj.SPCLNTAB) 47 SELFROSECT = SymKind(obj.SELFROSECT) 48 49 // writable, non-executable 50 SMACHOPLT = SymKind(obj.SMACHOPLT) 51 SELFSECT = SymKind(obj.SELFSECT) 52 SMACHO = SymKind(obj.SMACHO) // Mach-O __nl_symbol_ptr 53 SMACHOGOT = SymKind(obj.SMACHOGOT) 54 SWINDOWS = SymKind(obj.SWINDOWS) 55 SELFGOT = SymKind(obj.SELFGOT) 56 SNOPTRDATA = SymKind(obj.SNOPTRDATA) 57 SINITARR = SymKind(obj.SINITARR) 58 SDATA = SymKind(obj.SDATA) 59 SBSS = SymKind(obj.SBSS) 60 SNOPTRBSS = SymKind(obj.SNOPTRBSS) 61 STLSBSS = SymKind(obj.STLSBSS) 62 63 // not mapped 64 SXREF = SymKind(obj.SXREF) 65 SMACHOSYMSTR = SymKind(obj.SMACHOSYMSTR) 66 SMACHOSYMTAB = SymKind(obj.SMACHOSYMTAB) 67 SMACHOINDIRECTPLT = SymKind(obj.SMACHOINDIRECTPLT) 68 SMACHOINDIRECTGOT = SymKind(obj.SMACHOINDIRECTGOT) 69 SFILE = SymKind(obj.SFILE) 70 SFILEPATH = SymKind(obj.SFILEPATH) 71 SCONST = SymKind(obj.SCONST) 72 SDYNIMPORT = SymKind(obj.SDYNIMPORT) 73 SHOSTOBJ = SymKind(obj.SHOSTOBJ) 74 ) 75 76 var symKindStrings = []string{ 77 SBSS: "SBSS", 78 SCONST: "SCONST", 79 SDATA: "SDATA", 80 SDYNIMPORT: "SDYNIMPORT", 81 SELFROSECT: "SELFROSECT", 82 SELFRXSECT: "SELFRXSECT", 83 SELFSECT: "SELFSECT", 84 SFILE: "SFILE", 85 SFILEPATH: "SFILEPATH", 86 SFUNCTAB: "SFUNCTAB", 87 SGOFUNC: "SGOFUNC", 88 SGOSTRING: "SGOSTRING", 89 SHOSTOBJ: "SHOSTOBJ", 90 SINITARR: "SINITARR", 91 SMACHO: "SMACHO", 92 SMACHOGOT: "SMACHOGOT", 93 SMACHOINDIRECTGOT: "SMACHOINDIRECTGOT", 94 SMACHOINDIRECTPLT: "SMACHOINDIRECTPLT", 95 SMACHOPLT: "SMACHOPLT", 96 SMACHOSYMSTR: "SMACHOSYMSTR", 97 SMACHOSYMTAB: "SMACHOSYMTAB", 98 SNOPTRBSS: "SNOPTRBSS", 99 SNOPTRDATA: "SNOPTRDATA", 100 SPCLNTAB: "SPCLNTAB", 101 SRODATA: "SRODATA", 102 SSTRING: "SSTRING", 103 SSYMTAB: "SSYMTAB", 104 STEXT: "STEXT", 105 STLSBSS: "STLSBSS", 106 STYPE: "STYPE", 107 STYPELINK: "STYPELINK", 108 SITABLINK: "SITABLINK", 109 SWINDOWS: "SWINDOWS", 110 SXREF: "SXREF", 111 } 112 113 func (k SymKind) String() string { 114 if k < 0 || int(k) >= len(symKindStrings) { 115 return fmt.Sprintf("SymKind(%d)", k) 116 } 117 return symKindStrings[k] 118 } 119 120 // A Sym is a named symbol in an object file. 121 type Sym struct { 122 SymID // symbol identifier (name and version) 123 Kind SymKind // kind of symbol 124 DupOK bool // are duplicate definitions okay? 125 Size int // size of corresponding data 126 Type SymID // symbol for Go type information 127 Data Data // memory image of symbol 128 Reloc []Reloc // relocations to apply to Data 129 Func *Func // additional data for functions 130 } 131 132 // A SymID - the combination of Name and Version - uniquely identifies 133 // a symbol within a package. 134 type SymID struct { 135 // Name is the name of a symbol. 136 Name string 137 138 // Version is zero for symbols with global visibility. 139 // Symbols with only file visibility (such as file-level static 140 // declarations in C) have a non-zero version distinguishing 141 // a symbol in one file from a symbol of the same name 142 // in another file 143 Version int 144 } 145 146 func (s SymID) String() string { 147 if s.Version == 0 { 148 return s.Name 149 } 150 return fmt.Sprintf("%s<%d>", s.Name, s.Version) 151 } 152 153 // A Data is a reference to data stored in an object file. 154 // It records the offset and size of the data, so that a client can 155 // read the data only if necessary. 156 type Data struct { 157 Offset int64 158 Size int64 159 } 160 161 // A Reloc describes a relocation applied to a memory image to refer 162 // to an address within a particular symbol. 163 type Reloc struct { 164 // The bytes at [Offset, Offset+Size) within the containing Sym 165 // should be updated to refer to the address Add bytes after the start 166 // of the symbol Sym. 167 Offset int 168 Size int 169 Sym SymID 170 Add int 171 172 // The Type records the form of address expected in the bytes 173 // described by the previous fields: absolute, PC-relative, and so on. 174 // TODO(rsc): The interpretation of Type is not exposed by this package. 175 Type obj.RelocType 176 } 177 178 // A Var describes a variable in a function stack frame: a declared 179 // local variable, an input argument, or an output result. 180 type Var struct { 181 // The combination of Name, Kind, and Offset uniquely 182 // identifies a variable in a function stack frame. 183 // Using fewer of these - in particular, using only Name - does not. 184 Name string // Name of variable. 185 Kind int // TODO(rsc): Define meaning. 186 Offset int // Frame offset. TODO(rsc): Define meaning. 187 188 Type SymID // Go type for variable. 189 } 190 191 // Func contains additional per-symbol information specific to functions. 192 type Func struct { 193 Args int // size in bytes of argument frame: inputs and outputs 194 Frame int // size in bytes of local variable frame 195 Leaf bool // function omits save of link register (ARM) 196 NoSplit bool // function omits stack split prologue 197 Var []Var // detail about local variables 198 PCSP Data // PC → SP offset map 199 PCFile Data // PC → file number map (index into File) 200 PCLine Data // PC → line number map 201 PCInline Data // PC → inline tree index map 202 PCData []Data // PC → runtime support data map 203 FuncData []FuncData // non-PC-specific runtime support data 204 File []string // paths indexed by PCFile 205 InlTree []InlinedCall 206 } 207 208 // TODO: Add PCData []byte and PCDataIter (similar to liblink). 209 210 // A FuncData is a single function-specific data value. 211 type FuncData struct { 212 Sym SymID // symbol holding data 213 Offset int64 // offset into symbol for funcdata pointer 214 } 215 216 // An InlinedCall is a node in an InlTree. 217 // See cmd/internal/obj.InlTree for details. 218 type InlinedCall struct { 219 Parent int 220 File string 221 Line int 222 Func SymID 223 } 224 225 // A Package is a parsed Go object file or archive defining a Go package. 226 type Package struct { 227 ImportPath string // import path denoting this package 228 Imports []string // packages imported by this package 229 SymRefs []SymID // list of symbol names and versions referred to by this pack 230 Syms []*Sym // symbols defined by this package 231 MaxVersion int // maximum Version in any SymID in Syms 232 Arch string // architecture 233 } 234 235 var ( 236 archiveHeader = []byte("!<arch>\n") 237 archiveMagic = []byte("`\n") 238 goobjHeader = []byte("go objec") // truncated to size of archiveHeader 239 240 errCorruptArchive = errors.New("corrupt archive") 241 errTruncatedArchive = errors.New("truncated archive") 242 errCorruptObject = errors.New("corrupt object file") 243 errNotObject = errors.New("unrecognized object file format") 244 ) 245 246 // An objReader is an object file reader. 247 type objReader struct { 248 p *Package 249 b *bufio.Reader 250 f io.ReadSeeker 251 err error 252 offset int64 253 dataOffset int64 254 limit int64 255 tmp [256]byte 256 pkgprefix string 257 } 258 259 // importPathToPrefix returns the prefix that will be used in the 260 // final symbol table for the given import path. 261 // We escape '%', '"', all control characters and non-ASCII bytes, 262 // and any '.' after the final slash. 263 // 264 // See ../../../cmd/ld/lib.c:/^pathtoprefix and 265 // ../../../cmd/gc/subr.c:/^pathtoprefix. 266 func importPathToPrefix(s string) string { 267 // find index of last slash, if any, or else -1. 268 // used for determining whether an index is after the last slash. 269 slash := strings.LastIndex(s, "/") 270 271 // check for chars that need escaping 272 n := 0 273 for r := 0; r < len(s); r++ { 274 if c := s[r]; c <= ' ' || (c == '.' && r > slash) || c == '%' || c == '"' || c >= 0x7F { 275 n++ 276 } 277 } 278 279 // quick exit 280 if n == 0 { 281 return s 282 } 283 284 // escape 285 const hex = "0123456789abcdef" 286 p := make([]byte, 0, len(s)+2*n) 287 for r := 0; r < len(s); r++ { 288 if c := s[r]; c <= ' ' || (c == '.' && r > slash) || c == '%' || c == '"' || c >= 0x7F { 289 p = append(p, '%', hex[c>>4], hex[c&0xF]) 290 } else { 291 p = append(p, c) 292 } 293 } 294 295 return string(p) 296 } 297 298 // init initializes r to read package p from f. 299 func (r *objReader) init(f io.ReadSeeker, p *Package) { 300 r.f = f 301 r.p = p 302 r.offset, _ = f.Seek(0, io.SeekCurrent) 303 r.limit, _ = f.Seek(0, io.SeekEnd) 304 f.Seek(r.offset, io.SeekStart) 305 r.b = bufio.NewReader(f) 306 r.pkgprefix = importPathToPrefix(p.ImportPath) + "." 307 } 308 309 // error records that an error occurred. 310 // It returns only the first error, so that an error 311 // caused by an earlier error does not discard information 312 // about the earlier error. 313 func (r *objReader) error(err error) error { 314 if r.err == nil { 315 if err == io.EOF { 316 err = io.ErrUnexpectedEOF 317 } 318 r.err = err 319 } 320 // panic("corrupt") // useful for debugging 321 return r.err 322 } 323 324 // readByte reads and returns a byte from the input file. 325 // On I/O error or EOF, it records the error but returns byte 0. 326 // A sequence of 0 bytes will eventually terminate any 327 // parsing state in the object file. In particular, it ends the 328 // reading of a varint. 329 func (r *objReader) readByte() byte { 330 if r.err != nil { 331 return 0 332 } 333 if r.offset >= r.limit { 334 r.error(io.ErrUnexpectedEOF) 335 return 0 336 } 337 b, err := r.b.ReadByte() 338 if err != nil { 339 if err == io.EOF { 340 err = io.ErrUnexpectedEOF 341 } 342 r.error(err) 343 b = 0 344 } else { 345 r.offset++ 346 } 347 return b 348 } 349 350 // read reads exactly len(b) bytes from the input file. 351 // If an error occurs, read returns the error but also 352 // records it, so it is safe for callers to ignore the result 353 // as long as delaying the report is not a problem. 354 func (r *objReader) readFull(b []byte) error { 355 if r.err != nil { 356 return r.err 357 } 358 if r.offset+int64(len(b)) > r.limit { 359 return r.error(io.ErrUnexpectedEOF) 360 } 361 n, err := io.ReadFull(r.b, b) 362 r.offset += int64(n) 363 if err != nil { 364 return r.error(err) 365 } 366 return nil 367 } 368 369 // readInt reads a zigzag varint from the input file. 370 func (r *objReader) readInt() int { 371 var u uint64 372 373 for shift := uint(0); ; shift += 7 { 374 if shift >= 64 { 375 r.error(errCorruptObject) 376 return 0 377 } 378 c := r.readByte() 379 u |= uint64(c&0x7F) << shift 380 if c&0x80 == 0 { 381 break 382 } 383 } 384 385 v := int64(u>>1) ^ (int64(u) << 63 >> 63) 386 if int64(int(v)) != v { 387 r.error(errCorruptObject) // TODO 388 return 0 389 } 390 return int(v) 391 } 392 393 // readString reads a length-delimited string from the input file. 394 func (r *objReader) readString() string { 395 n := r.readInt() 396 buf := make([]byte, n) 397 r.readFull(buf) 398 return string(buf) 399 } 400 401 // readSymID reads a SymID from the input file. 402 func (r *objReader) readSymID() SymID { 403 i := r.readInt() 404 return r.p.SymRefs[i] 405 } 406 407 func (r *objReader) readRef() { 408 name, vers := r.readString(), r.readInt() 409 410 // In a symbol name in an object file, "". denotes the 411 // prefix for the package in which the object file has been found. 412 // Expand it. 413 name = strings.Replace(name, `"".`, r.pkgprefix, -1) 414 415 // An individual object file only records version 0 (extern) or 1 (static). 416 // To make static symbols unique across all files being read, we 417 // replace version 1 with the version corresponding to the current 418 // file number. The number is incremented on each call to parseObject. 419 if vers != 0 { 420 vers = r.p.MaxVersion 421 } 422 r.p.SymRefs = append(r.p.SymRefs, SymID{name, vers}) 423 } 424 425 // readData reads a data reference from the input file. 426 func (r *objReader) readData() Data { 427 n := r.readInt() 428 d := Data{Offset: r.dataOffset, Size: int64(n)} 429 r.dataOffset += int64(n) 430 return d 431 } 432 433 // skip skips n bytes in the input. 434 func (r *objReader) skip(n int64) { 435 if n < 0 { 436 r.error(fmt.Errorf("debug/goobj: internal error: misuse of skip")) 437 } 438 if n < int64(len(r.tmp)) { 439 // Since the data is so small, a just reading from the buffered 440 // reader is better than flushing the buffer and seeking. 441 r.readFull(r.tmp[:n]) 442 } else if n <= int64(r.b.Buffered()) { 443 // Even though the data is not small, it has already been read. 444 // Advance the buffer instead of seeking. 445 for n > int64(len(r.tmp)) { 446 r.readFull(r.tmp[:]) 447 n -= int64(len(r.tmp)) 448 } 449 r.readFull(r.tmp[:n]) 450 } else { 451 // Seek, giving up buffered data. 452 _, err := r.f.Seek(r.offset+n, io.SeekStart) 453 if err != nil { 454 r.error(err) 455 } 456 r.offset += n 457 r.b.Reset(r.f) 458 } 459 } 460 461 // Parse parses an object file or archive from r, 462 // assuming that its import path is pkgpath. 463 func Parse(r io.ReadSeeker, pkgpath string) (*Package, error) { 464 if pkgpath == "" { 465 pkgpath = `""` 466 } 467 p := new(Package) 468 p.ImportPath = pkgpath 469 470 var rd objReader 471 rd.init(r, p) 472 err := rd.readFull(rd.tmp[:8]) 473 if err != nil { 474 if err == io.EOF { 475 err = io.ErrUnexpectedEOF 476 } 477 return nil, err 478 } 479 480 switch { 481 default: 482 return nil, errNotObject 483 484 case bytes.Equal(rd.tmp[:8], archiveHeader): 485 if err := rd.parseArchive(); err != nil { 486 return nil, err 487 } 488 case bytes.Equal(rd.tmp[:8], goobjHeader): 489 if err := rd.parseObject(goobjHeader); err != nil { 490 return nil, err 491 } 492 } 493 494 return p, nil 495 } 496 497 // trimSpace removes trailing spaces from b and returns the corresponding string. 498 // This effectively parses the form used in archive headers. 499 func trimSpace(b []byte) string { 500 return string(bytes.TrimRight(b, " ")) 501 } 502 503 // parseArchive parses a Unix archive of Go object files. 504 // TODO(rsc): Need to skip non-Go object files. 505 // TODO(rsc): Maybe record table of contents in r.p so that 506 // linker can avoid having code to parse archives too. 507 func (r *objReader) parseArchive() error { 508 for r.offset < r.limit { 509 if err := r.readFull(r.tmp[:60]); err != nil { 510 return err 511 } 512 data := r.tmp[:60] 513 514 // Each file is preceded by this text header (slice indices in first column): 515 // 0:16 name 516 // 16:28 date 517 // 28:34 uid 518 // 34:40 gid 519 // 40:48 mode 520 // 48:58 size 521 // 58:60 magic - `\n 522 // We only care about name, size, and magic. 523 // The fields are space-padded on the right. 524 // The size is in decimal. 525 // The file data - size bytes - follows the header. 526 // Headers are 2-byte aligned, so if size is odd, an extra padding 527 // byte sits between the file data and the next header. 528 // The file data that follows is padded to an even number of bytes: 529 // if size is odd, an extra padding byte is inserted betw the next header. 530 if len(data) < 60 { 531 return errTruncatedArchive 532 } 533 if !bytes.Equal(data[58:60], archiveMagic) { 534 return errCorruptArchive 535 } 536 name := trimSpace(data[0:16]) 537 size, err := strconv.ParseInt(trimSpace(data[48:58]), 10, 64) 538 if err != nil { 539 return errCorruptArchive 540 } 541 data = data[60:] 542 fsize := size + size&1 543 if fsize < 0 || fsize < size { 544 return errCorruptArchive 545 } 546 switch name { 547 case "__.PKGDEF": 548 r.skip(size) 549 default: 550 oldLimit := r.limit 551 r.limit = r.offset + size 552 if err := r.parseObject(nil); err != nil { 553 return fmt.Errorf("parsing archive member %q: %v", name, err) 554 } 555 r.skip(r.limit - r.offset) 556 r.limit = oldLimit 557 } 558 if size&1 != 0 { 559 r.skip(1) 560 } 561 } 562 return nil 563 } 564 565 // parseObject parses a single Go object file. 566 // The prefix is the bytes already read from the file, 567 // typically in order to detect that this is an object file. 568 // The object file consists of a textual header ending in "\n!\n" 569 // and then the part we want to parse begins. 570 // The format of that part is defined in a comment at the top 571 // of src/liblink/objfile.c. 572 func (r *objReader) parseObject(prefix []byte) error { 573 r.p.MaxVersion++ 574 h := make([]byte, 0, 256) 575 h = append(h, prefix...) 576 var c1, c2, c3 byte 577 for { 578 c1, c2, c3 = c2, c3, r.readByte() 579 h = append(h, c3) 580 // The new export format can contain 0 bytes. 581 // Don't consider them errors, only look for r.err != nil. 582 if r.err != nil { 583 return errCorruptObject 584 } 585 if c1 == '\n' && c2 == '!' && c3 == '\n' { 586 break 587 } 588 } 589 590 hs := strings.Fields(string(h)) 591 if len(hs) >= 4 { 592 r.p.Arch = hs[3] 593 } 594 // TODO: extract OS + build ID if/when we need it 595 596 r.readFull(r.tmp[:8]) 597 if !bytes.Equal(r.tmp[:8], []byte("\x00\x00go19ld")) { 598 return r.error(errCorruptObject) 599 } 600 601 b := r.readByte() 602 if b != 1 { 603 return r.error(errCorruptObject) 604 } 605 606 // Direct package dependencies. 607 for { 608 s := r.readString() 609 if s == "" { 610 break 611 } 612 r.p.Imports = append(r.p.Imports, s) 613 } 614 615 r.p.SymRefs = []SymID{{"", 0}} 616 for { 617 if b := r.readByte(); b != 0xfe { 618 if b != 0xff { 619 return r.error(errCorruptObject) 620 } 621 break 622 } 623 624 r.readRef() 625 } 626 627 dataLength := r.readInt() 628 r.readInt() // n relocations - ignore 629 r.readInt() // n pcdata - ignore 630 r.readInt() // n autom - ignore 631 r.readInt() // n funcdata - ignore 632 r.readInt() // n files - ignore 633 634 r.dataOffset = r.offset 635 r.skip(int64(dataLength)) 636 637 // Symbols. 638 for { 639 if b := r.readByte(); b != 0xfe { 640 if b != 0xff { 641 return r.error(errCorruptObject) 642 } 643 break 644 } 645 646 typ := r.readInt() 647 s := &Sym{SymID: r.readSymID()} 648 r.p.Syms = append(r.p.Syms, s) 649 s.Kind = SymKind(typ) 650 flags := r.readInt() 651 s.DupOK = flags&1 != 0 652 s.Size = r.readInt() 653 s.Type = r.readSymID() 654 s.Data = r.readData() 655 s.Reloc = make([]Reloc, r.readInt()) 656 for i := range s.Reloc { 657 rel := &s.Reloc[i] 658 rel.Offset = r.readInt() 659 rel.Size = r.readInt() 660 rel.Type = obj.RelocType(r.readInt()) 661 rel.Add = r.readInt() 662 rel.Sym = r.readSymID() 663 } 664 665 if s.Kind == STEXT { 666 f := new(Func) 667 s.Func = f 668 f.Args = r.readInt() 669 f.Frame = r.readInt() 670 flags := r.readInt() 671 f.Leaf = flags&1 != 0 672 f.NoSplit = r.readInt() != 0 673 f.Var = make([]Var, r.readInt()) 674 for i := range f.Var { 675 v := &f.Var[i] 676 v.Name = r.readSymID().Name 677 v.Offset = r.readInt() 678 v.Kind = r.readInt() 679 v.Type = r.readSymID() 680 } 681 682 f.PCSP = r.readData() 683 f.PCFile = r.readData() 684 f.PCLine = r.readData() 685 f.PCInline = r.readData() 686 f.PCData = make([]Data, r.readInt()) 687 for i := range f.PCData { 688 f.PCData[i] = r.readData() 689 } 690 f.FuncData = make([]FuncData, r.readInt()) 691 for i := range f.FuncData { 692 f.FuncData[i].Sym = r.readSymID() 693 } 694 for i := range f.FuncData { 695 f.FuncData[i].Offset = int64(r.readInt()) // TODO 696 } 697 f.File = make([]string, r.readInt()) 698 for i := range f.File { 699 f.File[i] = r.readSymID().Name 700 } 701 f.InlTree = make([]InlinedCall, r.readInt()) 702 for i := range f.InlTree { 703 f.InlTree[i].Parent = r.readInt() 704 f.InlTree[i].File = r.readSymID().Name 705 f.InlTree[i].Line = r.readInt() 706 f.InlTree[i].Func = r.readSymID() 707 } 708 } 709 } 710 711 r.readFull(r.tmp[:7]) 712 if !bytes.Equal(r.tmp[:7], []byte("\xffgo19ld")) { 713 return r.error(errCorruptObject) 714 } 715 716 return nil 717 } 718 719 func (r *Reloc) String(insnOffset uint64) string { 720 delta := r.Offset - int(insnOffset) 721 s := fmt.Sprintf("[%d:%d]%s", delta, delta+r.Size, r.Type) 722 if r.Sym.Name != "" { 723 if r.Add != 0 { 724 return fmt.Sprintf("%s:%s+%d", s, r.Sym.Name, r.Add) 725 } 726 return fmt.Sprintf("%s:%s", s, r.Sym.Name) 727 } 728 if r.Add != 0 { 729 return fmt.Sprintf("%s:%d", s, r.Add) 730 } 731 return s 732 }