diff options
| author | Katie Hockman <katie@golang.org> | 2020-12-14 10:03:05 -0500 |
|---|---|---|
| committer | Katie Hockman <katie@golang.org> | 2020-12-14 10:06:13 -0500 |
| commit | 0345ede87ee12698988973884cfc0fd3d499dffd (patch) | |
| tree | 7123cff141ee5661208d2f5f437b8f5252ac7f6a /src/debug | |
| parent | 4651d6b267818b0e0d128a5443289717c4bb8cbc (diff) | |
| parent | 0a02371b0576964e81c3b40d328db9a3ef3b031b (diff) | |
| download | go-0345ede87ee12698988973884cfc0fd3d499dffd.tar.xz | |
[dev.fuzz] all: merge master into dev.fuzz
Change-Id: I5d8c8329ccc9d747bd81ade6b1cb7cb8ae2e94b2
Diffstat (limited to 'src/debug')
| -rw-r--r-- | src/debug/dwarf/const.go | 12 | ||||
| -rw-r--r-- | src/debug/dwarf/dwarf5ranges_test.go | 36 | ||||
| -rw-r--r-- | src/debug/dwarf/entry.go | 255 | ||||
| -rw-r--r-- | src/debug/dwarf/entry_test.go | 42 | ||||
| -rw-r--r-- | src/debug/dwarf/line.go | 6 | ||||
| -rw-r--r-- | src/debug/dwarf/line_test.go | 8 | ||||
| -rw-r--r-- | src/debug/dwarf/open.go | 94 | ||||
| -rw-r--r-- | src/debug/dwarf/testdata/debug_rnglists | bin | 0 -> 23 bytes | |||
| -rw-r--r-- | src/debug/elf/elf.go | 223 | ||||
| -rw-r--r-- | src/debug/elf/file_test.go | 2 | ||||
| -rw-r--r-- | src/debug/gosym/pclntab.go | 95 | ||||
| -rw-r--r-- | src/debug/gosym/pclntab_test.go | 8 | ||||
| -rw-r--r-- | src/debug/pe/file_test.go | 13 |
13 files changed, 670 insertions, 124 deletions
diff --git a/src/debug/dwarf/const.go b/src/debug/dwarf/const.go index b11bf90c37..c60709199b 100644 --- a/src/debug/dwarf/const.go +++ b/src/debug/dwarf/const.go @@ -461,3 +461,15 @@ const ( utSplitCompile = 0x05 utSplitType = 0x06 ) + +// Opcodes for DWARFv5 debug_rnglists section. +const ( + rleEndOfList = 0x0 + rleBaseAddressx = 0x1 + rleStartxEndx = 0x2 + rleStartxLength = 0x3 + rleOffsetPair = 0x4 + rleBaseAddress = 0x5 + rleStartEnd = 0x6 + rleStartLength = 0x7 +) diff --git a/src/debug/dwarf/dwarf5ranges_test.go b/src/debug/dwarf/dwarf5ranges_test.go new file mode 100644 index 0000000000..0ff1a55bc9 --- /dev/null +++ b/src/debug/dwarf/dwarf5ranges_test.go @@ -0,0 +1,36 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package dwarf + +import ( + "encoding/binary" + "os" + "reflect" + "testing" +) + +func TestDwarf5Ranges(t *testing.T) { + rngLists, err := os.ReadFile("testdata/debug_rnglists") + if err != nil { + t.Fatalf("could not read test data: %v", err) + } + + d := &Data{} + d.order = binary.LittleEndian + if err := d.AddSection(".debug_rnglists", rngLists); err != nil { + t.Fatal(err) + } + ret, err := d.dwarf5Ranges(nil, 0x5fbd, 0xc, [][2]uint64{}) + if err != nil { + t.Fatalf("could not read rnglist: %v", err) + } + t.Logf("%#v", ret) + + tgt := [][2]uint64{{0x0000000000006712, 0x000000000000679f}, {0x00000000000067af}, {0x00000000000067b3}} + + if reflect.DeepEqual(ret, tgt) { + t.Errorf("expected %#v got %#x", tgt, ret) + } +} diff --git a/src/debug/dwarf/entry.go b/src/debug/dwarf/entry.go index 01f2190db7..bc05d7ef31 100644 --- a/src/debug/dwarf/entry.go +++ b/src/debug/dwarf/entry.go @@ -453,38 +453,28 @@ func (b *buf) entry(cu *Entry, atab abbrevTable, ubase Offset, vers int) *Entry case formAddrx4: off = uint64(b.uint32()) } - if len(b.dwarf.addr) == 0 { + if b.dwarf.addr == nil { b.error("DW_FORM_addrx with no .debug_addr section") } if b.err != nil { return nil } - addrsize := b.format.addrsize() - if addrsize == 0 { - b.error("unknown address size for DW_FORM_addrx") - } - off *= uint64(addrsize) // We have to adjust by the offset of the // compilation unit. This won't work if the // program uses Reader.Seek to skip over the // unit. Not much we can do about that. + var addrBase int64 if cu != nil { - cuOff, ok := cu.Val(AttrAddrBase).(int64) - if ok { - off += uint64(cuOff) - } - } - - if uint64(int(off)) != off { - b.error("DW_FORM_addrx offset out of range") + addrBase, _ = cu.Val(AttrAddrBase).(int64) } - b1 := makeBuf(b.dwarf, b.format, "addr", 0, b.dwarf.addr) - b1.skip(int(off)) - val = b1.addr() - if b1.err != nil { - b.err = b1.err + var err error + val, err = b.dwarf.debugAddr(uint64(addrBase), off) + if err != nil { + if b.err == nil { + b.err = err + } return nil } @@ -717,6 +707,7 @@ type Reader struct { d *Data err error unit int + lastUnit bool // set if last entry returned by Next is TagCompileUnit/TagPartialUnit lastChildren bool // .Children of last entry returned by Next lastSibling Offset // .Val(AttrSibling) of last entry returned by Next cu *Entry // current compilation unit @@ -774,13 +765,18 @@ func (r *Reader) Seek(off Offset) { // maybeNextUnit advances to the next unit if this one is finished. func (r *Reader) maybeNextUnit() { for len(r.b.data) == 0 && r.unit+1 < len(r.d.unit) { - r.unit++ - u := &r.d.unit[r.unit] - r.b = makeBuf(r.d, u, "info", u.off, u.data) - r.cu = nil + r.nextUnit() } } +// nextUnit advances to the next unit. +func (r *Reader) nextUnit() { + r.unit++ + u := &r.d.unit[r.unit] + r.b = makeBuf(r.d, u, "info", u.off, u.data) + r.cu = nil +} + // Next reads the next entry from the encoded entry stream. // It returns nil, nil when it reaches the end of the section. // It returns an error if the current offset is invalid or the data at the @@ -799,12 +795,14 @@ func (r *Reader) Next() (*Entry, error) { r.err = r.b.err return nil, r.err } + r.lastUnit = false if e != nil { r.lastChildren = e.Children if r.lastChildren { r.lastSibling, _ = e.Val(AttrSibling).(Offset) } if e.Tag == TagCompileUnit || e.Tag == TagPartialUnit { + r.lastUnit = true r.cu = e } } else { @@ -830,6 +828,11 @@ func (r *Reader) SkipChildren() { return } + if r.lastUnit && r.unit+1 < len(r.d.unit) { + r.nextUnit() + return + } + for { e, err := r.Next() if err != nil || e == nil || e.Tag == 0 { @@ -922,53 +925,187 @@ func (d *Data) Ranges(e *Entry) ([][2]uint64, error) { ret = append(ret, [2]uint64{low, high}) } - ranges, rangesOK := e.Val(AttrRanges).(int64) - if rangesOK && d.ranges != nil { - // The initial base address is the lowpc attribute - // of the enclosing compilation unit. - // Although DWARF specifies the lowpc attribute, - // comments in gdb/dwarf2read.c say that some versions - // of GCC use the entrypc attribute, so we check that too. - var cu *Entry - if e.Tag == TagCompileUnit { - cu = e - } else { - i := d.offsetToUnit(e.Offset) - if i == -1 { - return nil, errors.New("no unit for entry") + var u *unit + if uidx := d.offsetToUnit(e.Offset); uidx >= 0 && uidx < len(d.unit) { + u = &d.unit[uidx] + } + + if u != nil && u.vers >= 5 && d.rngLists != nil { + // DWARF version 5 and later + field := e.AttrField(AttrRanges) + if field == nil { + return ret, nil + } + switch field.Class { + case ClassRangeListPtr: + ranges, rangesOK := field.Val.(int64) + if !rangesOK { + return ret, nil } - u := &d.unit[i] - b := makeBuf(d, u, "info", u.off, u.data) - cu = b.entry(nil, u.atable, u.base, u.vers) - if b.err != nil { - return nil, b.err + cu, base, err := d.baseAddressForEntry(e) + if err != nil { + return nil, err } + return d.dwarf5Ranges(cu, base, ranges, ret) + + case ClassRngList: + // TODO: support DW_FORM_rnglistx + return ret, nil + + default: + return ret, nil } + } - var base uint64 - if cuEntry, cuEntryOK := cu.Val(AttrEntrypc).(uint64); cuEntryOK { - base = cuEntry - } else if cuLow, cuLowOK := cu.Val(AttrLowpc).(uint64); cuLowOK { - base = cuLow + // DWARF version 2 through 4 + ranges, rangesOK := e.Val(AttrRanges).(int64) + if rangesOK && d.ranges != nil { + _, base, err := d.baseAddressForEntry(e) + if err != nil { + return nil, err } + return d.dwarf2Ranges(u, base, ranges, ret) + } - u := &d.unit[d.offsetToUnit(e.Offset)] - buf := makeBuf(d, u, "ranges", Offset(ranges), d.ranges[ranges:]) - for len(buf.data) > 0 { - low = buf.addr() - high = buf.addr() + return ret, nil +} - if low == 0 && high == 0 { - break +// baseAddressForEntry returns the initial base address to be used when +// looking up the range list of entry e. +// DWARF specifies that this should be the lowpc attribute of the enclosing +// compilation unit, however comments in gdb/dwarf2read.c say that some +// versions of GCC use the entrypc attribute, so we check that too. +func (d *Data) baseAddressForEntry(e *Entry) (*Entry, uint64, error) { + var cu *Entry + if e.Tag == TagCompileUnit { + cu = e + } else { + i := d.offsetToUnit(e.Offset) + if i == -1 { + return nil, 0, errors.New("no unit for entry") + } + u := &d.unit[i] + b := makeBuf(d, u, "info", u.off, u.data) + cu = b.entry(nil, u.atable, u.base, u.vers) + if b.err != nil { + return nil, 0, b.err + } + } + + if cuEntry, cuEntryOK := cu.Val(AttrEntrypc).(uint64); cuEntryOK { + return cu, cuEntry, nil + } else if cuLow, cuLowOK := cu.Val(AttrLowpc).(uint64); cuLowOK { + return cu, cuLow, nil + } + + return cu, 0, nil +} + +func (d *Data) dwarf2Ranges(u *unit, base uint64, ranges int64, ret [][2]uint64) ([][2]uint64, error) { + buf := makeBuf(d, u, "ranges", Offset(ranges), d.ranges[ranges:]) + for len(buf.data) > 0 { + low := buf.addr() + high := buf.addr() + + if low == 0 && high == 0 { + break + } + + if low == ^uint64(0)>>uint((8-u.addrsize())*8) { + base = high + } else { + ret = append(ret, [2]uint64{base + low, base + high}) + } + } + + return ret, nil +} + +// dwarf5Ranges interpets a debug_rnglists sequence, see DWARFv5 section +// 2.17.3 (page 53). +func (d *Data) dwarf5Ranges(cu *Entry, base uint64, ranges int64, ret [][2]uint64) ([][2]uint64, error) { + var addrBase int64 + if cu != nil { + addrBase, _ = cu.Val(AttrAddrBase).(int64) + } + + buf := makeBuf(d, d.rngLists, "rnglists", 0, d.rngLists.data) + buf.skip(int(ranges)) + for { + opcode := buf.uint8() + switch opcode { + case rleEndOfList: + if buf.err != nil { + return nil, buf.err } + return ret, nil - if low == ^uint64(0)>>uint((8-u.addrsize())*8) { - base = high - } else { - ret = append(ret, [2]uint64{base + low, base + high}) + case rleBaseAddressx: + baseIdx := buf.uint() + var err error + base, err = d.debugAddr(uint64(addrBase), baseIdx) + if err != nil { + return nil, err + } + + case rleStartxEndx: + startIdx := buf.uint() + endIdx := buf.uint() + + start, err := d.debugAddr(uint64(addrBase), startIdx) + if err != nil { + return nil, err } + end, err := d.debugAddr(uint64(addrBase), endIdx) + if err != nil { + return nil, err + } + ret = append(ret, [2]uint64{start, end}) + + case rleStartxLength: + startIdx := buf.uint() + len := buf.uint() + start, err := d.debugAddr(uint64(addrBase), startIdx) + if err != nil { + return nil, err + } + ret = append(ret, [2]uint64{start, start + len}) + + case rleOffsetPair: + off1 := buf.uint() + off2 := buf.uint() + ret = append(ret, [2]uint64{base + off1, base + off2}) + + case rleBaseAddress: + base = buf.addr() + + case rleStartEnd: + start := buf.addr() + end := buf.addr() + ret = append(ret, [2]uint64{start, end}) + + case rleStartLength: + start := buf.addr() + len := buf.uint() + ret = append(ret, [2]uint64{start, start + len}) } } +} - return ret, nil +// debugAddr returns the address at idx in debug_addr +func (d *Data) debugAddr(addrBase, idx uint64) (uint64, error) { + off := idx*uint64(d.addr.addrsize()) + addrBase + + if uint64(int(off)) != off { + return 0, errors.New("offset out of range") + } + + b := makeBuf(d, d.addr, "addr", 0, d.addr.data) + b.skip(int(off)) + val := b.addr() + if b.err != nil { + return 0, b.err + } + + return val, nil } diff --git a/src/debug/dwarf/entry_test.go b/src/debug/dwarf/entry_test.go index 4c9aad21f3..2e6ee048aa 100644 --- a/src/debug/dwarf/entry_test.go +++ b/src/debug/dwarf/entry_test.go @@ -7,6 +7,7 @@ package dwarf_test import ( . "debug/dwarf" "encoding/binary" + "path/filepath" "reflect" "testing" ) @@ -209,3 +210,44 @@ func Test64Bit(t *testing.T) { } } } + +func TestUnitIteration(t *testing.T) { + // Iterate over all ELF test files we have and ensure that + // we get the same set of compilation units skipping (method 0) + // and not skipping (method 1) CU children. + files, err := filepath.Glob(filepath.Join("testdata", "*.elf")) + if err != nil { + t.Fatal(err) + } + for _, file := range files { + t.Run(file, func(t *testing.T) { + d := elfData(t, file) + var units [2][]interface{} + for method := range units { + for r := d.Reader(); ; { + ent, err := r.Next() + if err != nil { + t.Fatal(err) + } + if ent == nil { + break + } + if ent.Tag == TagCompileUnit { + units[method] = append(units[method], ent.Val(AttrName)) + } + if method == 0 { + if ent.Tag != TagCompileUnit { + t.Fatalf("found unexpected tag %v on top level", ent.Tag) + } + r.SkipChildren() + } + } + } + t.Logf("skipping CUs: %v", units[0]) + t.Logf("not-skipping CUs: %v", units[1]) + if !reflect.DeepEqual(units[0], units[1]) { + t.Fatal("set of CUs differ") + } + }) + } +} diff --git a/src/debug/dwarf/line.go b/src/debug/dwarf/line.go index 7692f05552..c4937ca7dd 100644 --- a/src/debug/dwarf/line.go +++ b/src/debug/dwarf/line.go @@ -814,7 +814,11 @@ func pathJoin(dirname, filename string) string { // Drives are the same. Ignore drive on filename. } if !(strings.HasSuffix(dirname, "/") || strings.HasSuffix(dirname, `\`)) && dirname != "" { - dirname += `\` + sep := `\` + if strings.HasPrefix(dirname, "/") { + sep = `/` + } + dirname += sep } return drive + dirname + filename } diff --git a/src/debug/dwarf/line_test.go b/src/debug/dwarf/line_test.go index 1fd9b19b03..b13818e8b5 100644 --- a/src/debug/dwarf/line_test.go +++ b/src/debug/dwarf/line_test.go @@ -341,6 +341,14 @@ var joinTests = []joinTest{ {`\\host\share\`, `foo\bar`, `\\host\share\foo\bar`}, {`//host/share/`, `foo/bar`, `//host/share/foo/bar`}, + // Note: the Go compiler currently emits DWARF line table paths + // with '/' instead of '\' (see issues #19784, #36495). These + // tests are to cover cases that might come up for Windows Go + // binaries. + {`c:/workdir/go/src/x`, `y.go`, `c:/workdir/go/src/x/y.go`}, + {`d:/some/thing/`, `b.go`, `d:/some/thing/b.go`}, + {`e:\blah\`, `foo.c`, `e:\blah\foo.c`}, + // The following are "best effort". We shouldn't see relative // base directories in DWARF, but these test that pathJoin // doesn't fail miserably if it sees one. diff --git a/src/debug/dwarf/open.go b/src/debug/dwarf/open.go index 72ee64d558..617b8c56dd 100644 --- a/src/debug/dwarf/open.go +++ b/src/debug/dwarf/open.go @@ -7,7 +7,10 @@ // http://dwarfstd.org/doc/dwarf-2.0.0.pdf package dwarf -import "encoding/binary" +import ( + "encoding/binary" + "errors" +) // Data represents the DWARF debugging information // loaded from an executable file (for example, an ELF or Mach-O executable). @@ -23,9 +26,10 @@ type Data struct { str []byte // New sections added in DWARF 5. - addr []byte + addr *debugAddr lineStr []byte strOffsets []byte + rngLists *rngLists // parsed data abbrevCache map[uint64]abbrevTable @@ -36,6 +40,23 @@ type Data struct { unit []unit } +// rngLists represents the contents of a debug_rnglists section (DWARFv5). +type rngLists struct { + is64 bool + asize uint8 + data []byte + ver uint16 +} + +// debugAddr represents the contents of a debug_addr section (DWARFv5). +type debugAddr struct { + is64 bool + asize uint8 + data []byte +} + +var errSegmentSelector = errors.New("non-zero segment_selector size not supported") + // New returns a new Data object initialized from the given parameters. // Rather than calling this function directly, clients should typically use // the DWARF method of the File type of the appropriate package debug/elf, @@ -108,14 +129,79 @@ func (d *Data) AddTypes(name string, types []byte) error { // so forth. This approach is used for new DWARF sections added in // DWARF 5 and later. func (d *Data) AddSection(name string, contents []byte) error { + var err error switch name { case ".debug_addr": - d.addr = contents + d.addr, err = d.parseAddrHeader(contents) case ".debug_line_str": d.lineStr = contents case ".debug_str_offsets": d.strOffsets = contents + case ".debug_rnglists": + d.rngLists, err = d.parseRngListsHeader(contents) } // Just ignore names that we don't yet support. - return nil + return err +} + +// parseRngListsHeader reads the header of a debug_rnglists section, see +// DWARFv5 section 7.28 (page 242). +func (d *Data) parseRngListsHeader(bytes []byte) (*rngLists, error) { + rngLists := &rngLists{data: bytes} + + buf := makeBuf(d, unknownFormat{}, "rnglists", 0, bytes) + _, rngLists.is64 = buf.unitLength() + + rngLists.ver = buf.uint16() // version + + rngLists.asize = buf.uint8() + segsize := buf.uint8() + if segsize != 0 { + return nil, errSegmentSelector + } + + // Header fields not read: offset_entry_count, offset table + + return rngLists, nil +} + +func (rngLists *rngLists) version() int { + return int(rngLists.ver) +} + +func (rngLists *rngLists) dwarf64() (bool, bool) { + return rngLists.is64, true +} + +func (rngLists *rngLists) addrsize() int { + return int(rngLists.asize) +} + +// parseAddrHeader reads the header of a debug_addr section, see DWARFv5 +// section 7.27 (page 241). +func (d *Data) parseAddrHeader(bytes []byte) (*debugAddr, error) { + addr := &debugAddr{data: bytes} + + buf := makeBuf(d, unknownFormat{}, "addr", 0, bytes) + _, addr.is64 = buf.unitLength() + + addr.asize = buf.uint8() + segsize := buf.uint8() + if segsize != 0 { + return nil, errSegmentSelector + } + + return addr, nil +} + +func (addr *debugAddr) version() int { + return 5 +} + +func (addr *debugAddr) dwarf64() (bool, bool) { + return addr.is64, true +} + +func (addr *debugAddr) addrsize() int { + return int(addr.asize) } diff --git a/src/debug/dwarf/testdata/debug_rnglists b/src/debug/dwarf/testdata/debug_rnglists Binary files differnew file mode 100644 index 0000000000..985ec6c9f2 --- /dev/null +++ b/src/debug/dwarf/testdata/debug_rnglists diff --git a/src/debug/elf/elf.go b/src/debug/elf/elf.go index 96a67ce732..2b777eabac 100644 --- a/src/debug/elf/elf.go +++ b/src/debug/elf/elf.go @@ -745,18 +745,51 @@ func (i CompressionType) GoString() string { return stringName(uint32(i), compre type ProgType int const ( - PT_NULL ProgType = 0 /* Unused entry. */ - PT_LOAD ProgType = 1 /* Loadable segment. */ - PT_DYNAMIC ProgType = 2 /* Dynamic linking information segment. */ - PT_INTERP ProgType = 3 /* Pathname of interpreter. */ - PT_NOTE ProgType = 4 /* Auxiliary information. */ - PT_SHLIB ProgType = 5 /* Reserved (not used). */ - PT_PHDR ProgType = 6 /* Location of program header itself. */ - PT_TLS ProgType = 7 /* Thread local storage segment */ - PT_LOOS ProgType = 0x60000000 /* First OS-specific. */ - PT_HIOS ProgType = 0x6fffffff /* Last OS-specific. */ - PT_LOPROC ProgType = 0x70000000 /* First processor-specific type. */ - PT_HIPROC ProgType = 0x7fffffff /* Last processor-specific type. */ + PT_NULL ProgType = 0 /* Unused entry. */ + PT_LOAD ProgType = 1 /* Loadable segment. */ + PT_DYNAMIC ProgType = 2 /* Dynamic linking information segment. */ + PT_INTERP ProgType = 3 /* Pathname of interpreter. */ + PT_NOTE ProgType = 4 /* Auxiliary information. */ + PT_SHLIB ProgType = 5 /* Reserved (not used). */ + PT_PHDR ProgType = 6 /* Location of program header itself. */ + PT_TLS ProgType = 7 /* Thread local storage segment */ + + PT_LOOS ProgType = 0x60000000 /* First OS-specific. */ + + PT_GNU_EH_FRAME ProgType = 0x6474e550 /* Frame unwind information */ + PT_GNU_STACK ProgType = 0x6474e551 /* Stack flags */ + PT_GNU_RELRO ProgType = 0x6474e552 /* Read only after relocs */ + PT_GNU_PROPERTY ProgType = 0x6474e553 /* GNU property */ + PT_GNU_MBIND_LO ProgType = 0x6474e555 /* Mbind segments start */ + PT_GNU_MBIND_HI ProgType = 0x6474f554 /* Mbind segments finish */ + + PT_PAX_FLAGS ProgType = 0x65041580 /* PAX flags */ + + PT_OPENBSD_RANDOMIZE ProgType = 0x65a3dbe6 /* Random data */ + PT_OPENBSD_WXNEEDED ProgType = 0x65a3dbe7 /* W^X violations */ + PT_OPENBSD_BOOTDATA ProgType = 0x65a41be6 /* Boot arguments */ + + PT_SUNW_EH_FRAME ProgType = 0x6474e550 /* Frame unwind information */ + PT_SUNWSTACK ProgType = 0x6ffffffb /* Stack segment */ + + PT_HIOS ProgType = 0x6fffffff /* Last OS-specific. */ + + PT_LOPROC ProgType = 0x70000000 /* First processor-specific type. */ + + PT_ARM_ARCHEXT ProgType = 0x70000000 /* Architecture compatibility */ + PT_ARM_EXIDX ProgType = 0x70000001 /* Exception unwind tables */ + + PT_AARCH64_ARCHEXT ProgType = 0x70000000 /* Architecture compatibility */ + PT_AARCH64_UNWIND ProgType = 0x70000001 /* Exception unwind tables */ + + PT_MIPS_REGINFO ProgType = 0x70000000 /* Register usage */ + PT_MIPS_RTPROC ProgType = 0x70000001 /* Runtime procedures */ + PT_MIPS_OPTIONS ProgType = 0x70000002 /* Options */ + PT_MIPS_ABIFLAGS ProgType = 0x70000003 /* ABI flags */ + + PT_S390_PGSTE ProgType = 0x70000000 /* 4k page table size */ + + PT_HIPROC ProgType = 0x7fffffff /* Last processor-specific type. */ ) var ptStrings = []intName{ @@ -769,8 +802,19 @@ var ptStrings = []intName{ {6, "PT_PHDR"}, {7, "PT_TLS"}, {0x60000000, "PT_LOOS"}, + {0x6474e550, "PT_GNU_EH_FRAME"}, + {0x6474e551, "PT_GNU_STACK"}, + {0x6474e552, "PT_GNU_RELRO"}, + {0x6474e553, "PT_GNU_PROPERTY"}, + {0x65041580, "PT_PAX_FLAGS"}, + {0x65a3dbe6, "PT_OPENBSD_RANDOMIZE"}, + {0x65a3dbe7, "PT_OPENBSD_WXNEEDED"}, + {0x65a41be6, "PT_OPENBSD_BOOTDATA"}, + {0x6ffffffb, "PT_SUNWSTACK"}, {0x6fffffff, "PT_HIOS"}, {0x70000000, "PT_LOPROC"}, + // We don't list the processor-dependent ProgTypes, + // as the values overlap. {0x7fffffff, "PT_HIPROC"}, } @@ -837,15 +881,114 @@ const ( the interpretation of the d_un union as follows: even == 'd_ptr', even == 'd_val' or none */ - DT_PREINIT_ARRAY DynTag = 32 /* Address of the array of pointers to pre-initialization functions. */ - DT_PREINIT_ARRAYSZ DynTag = 33 /* Size in bytes of the array of pre-initialization functions. */ - DT_LOOS DynTag = 0x6000000d /* First OS-specific */ - DT_HIOS DynTag = 0x6ffff000 /* Last OS-specific */ - DT_VERSYM DynTag = 0x6ffffff0 - DT_VERNEED DynTag = 0x6ffffffe - DT_VERNEEDNUM DynTag = 0x6fffffff - DT_LOPROC DynTag = 0x70000000 /* First processor-specific type. */ - DT_HIPROC DynTag = 0x7fffffff /* Last processor-specific type. */ + DT_PREINIT_ARRAY DynTag = 32 /* Address of the array of pointers to pre-initialization functions. */ + DT_PREINIT_ARRAYSZ DynTag = 33 /* Size in bytes of the array of pre-initialization functions. */ + DT_SYMTAB_SHNDX DynTag = 34 /* Address of SHT_SYMTAB_SHNDX section. */ + + DT_LOOS DynTag = 0x6000000d /* First OS-specific */ + DT_HIOS DynTag = 0x6ffff000 /* Last OS-specific */ + + DT_VALRNGLO DynTag = 0x6ffffd00 + DT_GNU_PRELINKED DynTag = 0x6ffffdf5 + DT_GNU_CONFLICTSZ DynTag = 0x6ffffdf6 + DT_GNU_LIBLISTSZ DynTag = 0x6ffffdf7 + DT_CHECKSUM DynTag = 0x6ffffdf8 + DT_PLTPADSZ DynTag = 0x6ffffdf9 + DT_MOVEENT DynTag = 0x6ffffdfa + DT_MOVESZ DynTag = 0x6ffffdfb + DT_FEATURE DynTag = 0x6ffffdfc + DT_POSFLAG_1 DynTag = 0x6ffffdfd + DT_SYMINSZ DynTag = 0x6ffffdfe + DT_SYMINENT DynTag = 0x6ffffdff + DT_VALRNGHI DynTag = 0x6ffffdff + + DT_ADDRRNGLO DynTag = 0x6ffffe00 + DT_GNU_HASH DynTag = 0x6ffffef5 + DT_TLSDESC_PLT DynTag = 0x6ffffef6 + DT_TLSDESC_GOT DynTag = 0x6ffffef7 + DT_GNU_CONFLICT DynTag = 0x6ffffef8 + DT_GNU_LIBLIST DynTag = 0x6ffffef9 + DT_CONFIG DynTag = 0x6ffffefa + DT_DEPAUDIT DynTag = 0x6ffffefb + DT_AUDIT DynTag = 0x6ffffefc + DT_PLTPAD DynTag = 0x6ffffefd + DT_MOVETAB DynTag = 0x6ffffefe + DT_SYMINFO DynTag = 0x6ffffeff + DT_ADDRRNGHI DynTag = 0x6ffffeff + + DT_VERSYM DynTag = 0x6ffffff0 + DT_RELACOUNT DynTag = 0x6ffffff9 + DT_RELCOUNT DynTag = 0x6ffffffa + DT_FLAGS_1 DynTag = 0x6ffffffb + DT_VERDEF DynTag = 0x6ffffffc + DT_VERDEFNUM DynTag = 0x6ffffffd + DT_VERNEED DynTag = 0x6ffffffe + DT_VERNEEDNUM DynTag = 0x6fffffff + + DT_LOPROC DynTag = 0x70000000 /* First processor-specific type. */ + + DT_MIPS_RLD_VERSION DynTag = 0x70000001 + DT_MIPS_TIME_STAMP DynTag = 0x70000002 + DT_MIPS_ICHECKSUM DynTag = 0x70000003 + DT_MIPS_IVERSION DynTag = 0x70000004 + DT_MIPS_FLAGS DynTag = 0x70000005 + DT_MIPS_BASE_ADDRESS DynTag = 0x70000006 + DT_MIPS_MSYM DynTag = 0x70000007 + DT_MIPS_CONFLICT DynTag = 0x70000008 + DT_MIPS_LIBLIST DynTag = 0x70000009 + DT_MIPS_LOCAL_GOTNO DynTag = 0x7000000a + DT_MIPS_CONFLICTNO DynTag = 0x7000000b + DT_MIPS_LIBLISTNO DynTag = 0x70000010 + DT_MIPS_SYMTABNO DynTag = 0x70000011 + DT_MIPS_UNREFEXTNO DynTag = 0x70000012 + DT_MIPS_GOTSYM DynTag = 0x70000013 + DT_MIPS_HIPAGENO DynTag = 0x70000014 + DT_MIPS_RLD_MAP DynTag = 0x70000016 + DT_MIPS_DELTA_CLASS DynTag = 0x70000017 + DT_MIPS_DELTA_CLASS_NO DynTag = 0x70000018 + DT_MIPS_DELTA_INSTANCE DynTag = 0x70000019 + DT_MIPS_DELTA_INSTANCE_NO DynTag = 0x7000001a + DT_MIPS_DELTA_RELOC DynTag = 0x7000001b + DT_MIPS_DELTA_RELOC_NO DynTag = 0x7000001c + DT_MIPS_DELTA_SYM DynTag = 0x7000001d + DT_MIPS_DELTA_SYM_NO DynTag = 0x7000001e + DT_MIPS_DELTA_CLASSSYM DynTag = 0x70000020 + DT_MIPS_DELTA_CLASSSYM_NO DynTag = 0x70000021 + DT_MIPS_CXX_FLAGS DynTag = 0x70000022 + DT_MIPS_PIXIE_INIT DynTag = 0x70000023 + DT_MIPS_SYMBOL_LIB DynTag = 0x70000024 + DT_MIPS_LOCALPAGE_GOTIDX DynTag = 0x70000025 + DT_MIPS_LOCAL_GOTIDX DynTag = 0x70000026 + DT_MIPS_HIDDEN_GOTIDX DynTag = 0x70000027 + DT_MIPS_PROTECTED_GOTIDX DynTag = 0x70000028 + DT_MIPS_OPTIONS DynTag = 0x70000029 + DT_MIPS_INTERFACE DynTag = 0x7000002a + DT_MIPS_DYNSTR_ALIGN DynTag = 0x7000002b + DT_MIPS_INTERFACE_SIZE DynTag = 0x7000002c + DT_MIPS_RLD_TEXT_RESOLVE_ADDR DynTag = 0x7000002d + DT_MIPS_PERF_SUFFIX DynTag = 0x7000002e + DT_MIPS_COMPACT_SIZE DynTag = 0x7000002f + DT_MIPS_GP_VALUE DynTag = 0x70000030 + DT_MIPS_AUX_DYNAMIC DynTag = 0x70000031 + DT_MIPS_PLTGOT DynTag = 0x70000032 + DT_MIPS_RWPLT DynTag = 0x70000034 + DT_MIPS_RLD_MAP_REL DynTag = 0x70000035 + + DT_PPC_GOT DynTag = 0x70000000 + DT_PPC_OPT DynTag = 0x70000001 + + DT_PPC64_GLINK DynTag = 0x70000000 + DT_PPC64_OPD DynTag = 0x70000001 + DT_PPC64_OPDSZ DynTag = 0x70000002 + DT_PPC64_OPT DynTag = 0x70000003 + + DT_SPARC_REGISTER DynTag = 0x70000001 + + DT_AUXILIARY DynTag = 0x7ffffffd + DT_USED DynTag = 0x7ffffffe + DT_FILTER DynTag = 0x7fffffff + + DT_HIPROC DynTag = 0x7fffffff /* Last processor-specific type. */ ) var dtStrings = []intName{ @@ -883,13 +1026,49 @@ var dtStrings = []intName{ {32, "DT_ENCODING"}, {32, "DT_PREINIT_ARRAY"}, {33, "DT_PREINIT_ARRAYSZ"}, + {34, "DT_SYMTAB_SHNDX"}, {0x6000000d, "DT_LOOS"}, {0x6ffff000, "DT_HIOS"}, + {0x6ffffd00, "DT_VALRNGLO"}, + {0x6ffffdf5, "DT_GNU_PRELINKED"}, + {0x6ffffdf6, "DT_GNU_CONFLICTSZ"}, + {0x6ffffdf7, "DT_GNU_LIBLISTSZ"}, + {0x6ffffdf8, "DT_CHECKSUM"}, + {0x6ffffdf9, "DT_PLTPADSZ"}, + {0x6ffffdfa, "DT_MOVEENT"}, + {0x6ffffdfb, "DT_MOVESZ"}, + {0x6ffffdfc, "DT_FEATURE"}, + {0x6ffffdfd, "DT_POSFLAG_1"}, + {0x6ffffdfe, "DT_SYMINSZ"}, + {0x6ffffdff, "DT_SYMINENT"}, + {0x6ffffdff, "DT_VALRNGHI"}, + {0x6ffffe00, "DT_ADDRRNGLO"}, + {0x6ffffef5, "DT_GNU_HASH"}, + {0x6ffffef6, "DT_TLSDESC_PLT"}, + {0x6ffffef7, "DT_TLSDESC_GOT"}, + {0x6ffffef8, "DT_GNU_CONFLICT"}, + {0x6ffffef9, "DT_GNU_LIBLIST"}, + {0x6ffffefa, "DT_CONFIG"}, + {0x6ffffefb, "DT_DEPAUDIT"}, + {0x6ffffefc, "DT_AUDIT"}, + {0x6ffffefd, "DT_PLTPAD"}, + {0x6ffffefe, "DT_MOVETAB"}, + {0x6ffffeff, "DT_SYMINFO"}, + {0x6ffffeff, "DT_ADDRRNGHI"}, {0x6ffffff0, "DT_VERSYM"}, + {0x6ffffff9, "DT_RELACOUNT"}, + {0x6ffffffa, "DT_RELCOUNT"}, + {0x6ffffffb, "DT_FLAGS_1"}, + {0x6ffffffc, "DT_VERDEF"}, + {0x6ffffffd, "DT_VERDEFNUM"}, {0x6ffffffe, "DT_VERNEED"}, {0x6fffffff, "DT_VERNEEDNUM"}, {0x70000000, "DT_LOPROC"}, - {0x7fffffff, "DT_HIPROC"}, + // We don't list the processor-dependent DynTags, + // as the values overlap. + {0x7ffffffd, "DT_AUXILIARY"}, + {0x7ffffffe, "DT_USED"}, + {0x7fffffff, "DT_FILTER"}, } func (i DynTag) String() string { return stringName(uint32(i), dtStrings, false) } diff --git a/src/debug/elf/file_test.go b/src/debug/elf/file_test.go index 24948e696a..4c6fdeece9 100644 --- a/src/debug/elf/file_test.go +++ b/src/debug/elf/file_test.go @@ -899,7 +899,7 @@ func TestCompressedSection(t *testing.T) { func TestNoSectionOverlaps(t *testing.T) { // Ensure cmd/link outputs sections without overlaps. switch runtime.GOOS { - case "aix", "android", "darwin", "js", "plan9", "windows": + case "aix", "android", "darwin", "ios", "js", "plan9", "windows": t.Skipf("cmd/link doesn't produce ELF binaries on %s", runtime.GOOS) } _ = net.ResolveIPAddr // force dynamic linkage diff --git a/src/debug/gosym/pclntab.go b/src/debug/gosym/pclntab.go index e5c50520fc..a72f9847d7 100644 --- a/src/debug/gosym/pclntab.go +++ b/src/debug/gosym/pclntab.go @@ -53,14 +53,19 @@ type LineTable struct { quantum uint32 ptrsize uint32 funcnametab []byte + cutab []byte funcdata []byte functab []byte nfunctab uint32 filetab []byte + pctab []byte // points to the pctables. nfiletab uint32 - fileMap map[string]uint32 funcNames map[uint32]string // cache the function names strings map[uint32]string // interned substrings of Data, keyed by offset + // fileMap varies depending on the version of the object file. + // For ver12, it maps the name to the index in the file table. + // For ver116, it maps the name to the offset in filetab. + fileMap map[string]uint32 } // NOTE(rsc): This is wrong for GOARCH=arm, which uses a quantum of 4, @@ -223,22 +228,26 @@ func (t *LineTable) parsePclnTab() { switch possibleVersion { case ver116: t.nfunctab = uint32(t.uintptr(t.Data[8:])) - offset := t.uintptr(t.Data[8+t.ptrsize:]) + t.nfiletab = uint32(t.uintptr(t.Data[8+t.ptrsize:])) + offset := t.uintptr(t.Data[8+2*t.ptrsize:]) t.funcnametab = t.Data[offset:] - offset = t.uintptr(t.Data[8+2*t.ptrsize:]) + offset = t.uintptr(t.Data[8+3*t.ptrsize:]) + t.cutab = t.Data[offset:] + offset = t.uintptr(t.Data[8+4*t.ptrsize:]) + t.filetab = t.Data[offset:] + offset = t.uintptr(t.Data[8+5*t.ptrsize:]) + t.pctab = t.Data[offset:] + offset = t.uintptr(t.Data[8+6*t.ptrsize:]) t.funcdata = t.Data[offset:] t.functab = t.Data[offset:] functabsize := t.nfunctab*2*t.ptrsize + t.ptrsize - fileoff := t.binary.Uint32(t.functab[functabsize:]) - t.filetab = t.functab[fileoff:] t.functab = t.functab[:functabsize] - t.nfiletab = t.binary.Uint32(t.filetab) - t.filetab = t.filetab[:t.nfiletab*4] case ver12: t.nfunctab = uint32(t.uintptr(t.Data[8:])) t.funcdata = t.Data t.funcnametab = t.Data t.functab = t.Data[8+t.ptrsize:] + t.pctab = t.Data functabsize := t.nfunctab*2*t.ptrsize + t.ptrsize fileoff := t.binary.Uint32(t.functab[functabsize:]) t.functab = t.functab[:functabsize] @@ -330,17 +339,22 @@ func (t *LineTable) funcName(off uint32) string { return s } -// string returns a Go string found at off. -func (t *LineTable) string(off uint32) string { +// stringFrom returns a Go string found at off from a position. +func (t *LineTable) stringFrom(arr []byte, off uint32) string { if s, ok := t.strings[off]; ok { return s } - i := bytes.IndexByte(t.funcdata[off:], 0) - s := string(t.funcdata[off : off+uint32(i)]) + i := bytes.IndexByte(arr[off:], 0) + s := string(arr[off : off+uint32(i)]) t.strings[off] = s return s } +// string returns a Go string found at off. +func (t *LineTable) string(off uint32) string { + return t.stringFrom(t.funcdata, off) +} + // step advances to the next pc, value pair in the encoded table. func (t *LineTable) step(p *[]byte, pc *uint64, val *int32, first bool) bool { uvdelta := t.readvarint(p) @@ -363,7 +377,7 @@ func (t *LineTable) step(p *[]byte, pc *uint64, val *int32, first bool) bool { // off is the offset to the beginning of the pc-value table, // and entry is the start PC for the corresponding function. func (t *LineTable) pcvalue(off uint32, entry, targetpc uint64) int32 { - p := t.funcdata[off:] + p := t.pctab[off:] val := int32(-1) pc := entry @@ -381,21 +395,25 @@ func (t *LineTable) pcvalue(off uint32, entry, targetpc uint64) int32 { // to file number. Since most functions come from a single file, these // are usually short and quick to scan. If a file match is found, then the // code goes to the expense of looking for a simultaneous line number match. -func (t *LineTable) findFileLine(entry uint64, filetab, linetab uint32, filenum, line int32) uint64 { +func (t *LineTable) findFileLine(entry uint64, filetab, linetab uint32, filenum, line int32, cutab []byte) uint64 { if filetab == 0 || linetab == 0 { return 0 } - fp := t.funcdata[filetab:] - fl := t.funcdata[linetab:] + fp := t.pctab[filetab:] + fl := t.pctab[linetab:] fileVal := int32(-1) filePC := entry lineVal := int32(-1) linePC := entry fileStartPC := filePC for t.step(&fp, &filePC, &fileVal, filePC == entry) { - if fileVal == filenum && fileStartPC < filePC { - // fileVal is in effect starting at fileStartPC up to + fileIndex := fileVal + if t.version == ver116 { + fileIndex = int32(t.binary.Uint32(cutab[fileVal*4:])) + } + if fileIndex == filenum && fileStartPC < filePC { + // fileIndex is in effect starting at fileStartPC up to // but not including filePC, and it's the file we want. // Run the PC table looking for a matching line number // or until we reach filePC. @@ -450,13 +468,24 @@ func (t *LineTable) go12PCToFile(pc uint64) (file string) { entry := t.uintptr(f) filetab := t.binary.Uint32(f[t.ptrsize+4*4:]) fno := t.pcvalue(filetab, entry, pc) - if fno <= 0 { + if t.version == ver12 { + if fno <= 0 { + return "" + } + return t.string(t.binary.Uint32(t.filetab[4*fno:])) + } + // Go ≥ 1.16 + if fno < 0 { // 0 is valid for ≥ 1.16 return "" } - return t.string(t.binary.Uint32(t.filetab[4*fno:])) + cuoff := t.binary.Uint32(f[t.ptrsize+7*4:]) + if fnoff := t.binary.Uint32(t.cutab[(cuoff+uint32(fno))*4:]); fnoff != ^uint32(0) { + return t.stringFrom(t.filetab, fnoff) + } + return "" } -// go12LineToPC maps a (file, line) pair to a program counter for the Go 1.2 pcln table. +// go12LineToPC maps a (file, line) pair to a program counter for the Go 1.2/1.16 pcln table. func (t *LineTable) go12LineToPC(file string, line int) (pc uint64) { defer func() { if recover() != nil { @@ -465,20 +494,25 @@ func (t *LineTable) go12LineToPC(file string, line int) (pc uint64) { }() t.initFileMap() - filenum := t.fileMap[file] - if filenum == 0 { + filenum, ok := t.fileMap[file] + if !ok { return 0 } // Scan all functions. // If this turns out to be a bottleneck, we could build a map[int32][]int32 // mapping file number to a list of functions with code from that file. + var cutab []byte for i := uint32(0); i < t.nfunctab; i++ { f := t.funcdata[t.uintptr(t.functab[2*t.ptrsize*i+t.ptrsize:]):] entry := t.uintptr(f) filetab := t.binary.Uint32(f[t.ptrsize+4*4:]) linetab := t.binary.Uint32(f[t.ptrsize+5*4:]) - pc := t.findFileLine(entry, filetab, linetab, int32(filenum), int32(line)) + if t.version == ver116 { + cuoff := t.binary.Uint32(f[t.ptrsize+7*4:]) * 4 + cutab = t.cutab[cuoff:] + } + pc := t.findFileLine(entry, filetab, linetab, int32(filenum), int32(line), cutab) if pc != 0 { return pc } @@ -496,9 +530,18 @@ func (t *LineTable) initFileMap() { } m := make(map[string]uint32) - for i := uint32(1); i < t.nfiletab; i++ { - s := t.string(t.binary.Uint32(t.filetab[4*i:])) - m[s] = i + if t.version == ver12 { + for i := uint32(1); i < t.nfiletab; i++ { + s := t.string(t.binary.Uint32(t.filetab[4*i:])) + m[s] = i + } + } else { + var pos uint32 + for i := uint32(0); i < t.nfiletab; i++ { + s := t.stringFrom(t.filetab, pos) + m[s] = pos + pos += uint32(len(s) + 1) + } } t.fileMap = m } diff --git a/src/debug/gosym/pclntab_test.go b/src/debug/gosym/pclntab_test.go index 33772c7813..7347139b5d 100644 --- a/src/debug/gosym/pclntab_test.go +++ b/src/debug/gosym/pclntab_test.go @@ -9,7 +9,7 @@ import ( "compress/gzip" "debug/elf" "internal/testenv" - "io/ioutil" + "io" "os" "os/exec" "path/filepath" @@ -30,7 +30,7 @@ func dotest(t *testing.T) { t.Skipf("skipping on non-AMD64 system %s", runtime.GOARCH) } var err error - pclineTempDir, err = ioutil.TempDir("", "pclinetest") + pclineTempDir, err = os.MkdirTemp("", "pclinetest") if err != nil { t.Fatal(err) } @@ -277,7 +277,7 @@ func TestPCLine(t *testing.T) { // } // [END] func Test115PclnParsing(t *testing.T) { - zippedDat, err := ioutil.ReadFile("testdata/pcln115.gz") + zippedDat, err := os.ReadFile("testdata/pcln115.gz") if err != nil { t.Fatal(err) } @@ -287,7 +287,7 @@ func Test115PclnParsing(t *testing.T) { t.Fatal(err) } var dat []byte - dat, err = ioutil.ReadAll(gzReader) + dat, err = io.ReadAll(gzReader) if err != nil { t.Fatal(err) } diff --git a/src/debug/pe/file_test.go b/src/debug/pe/file_test.go index d96cd30904..58deff1450 100644 --- a/src/debug/pe/file_test.go +++ b/src/debug/pe/file_test.go @@ -8,7 +8,6 @@ import ( "bytes" "debug/dwarf" "internal/testenv" - "io/ioutil" "os" "os/exec" "path/filepath" @@ -354,7 +353,7 @@ func testDWARF(t *testing.T, linktype int) { } testenv.MustHaveGoRun(t) - tmpdir, err := ioutil.TempDir("", "TestDWARF") + tmpdir, err := os.MkdirTemp("", "TestDWARF") if err != nil { t.Fatal(err) } @@ -473,7 +472,7 @@ func TestBSSHasZeros(t *testing.T) { t.Skip("skipping test: gcc is missing") } - tmpdir, err := ioutil.TempDir("", "TestBSSHasZeros") + tmpdir, err := os.MkdirTemp("", "TestBSSHasZeros") if err != nil { t.Fatal(err) } @@ -492,7 +491,7 @@ main(void) return 0; } ` - err = ioutil.WriteFile(srcpath, []byte(src), 0644) + err = os.WriteFile(srcpath, []byte(src), 0644) if err != nil { t.Fatal(err) } @@ -597,14 +596,14 @@ func TestBuildingWindowsGUI(t *testing.T) { if runtime.GOOS != "windows" { t.Skip("skipping windows only test") } - tmpdir, err := ioutil.TempDir("", "TestBuildingWindowsGUI") + tmpdir, err := os.MkdirTemp("", "TestBuildingWindowsGUI") if err != nil { t.Fatal(err) } defer os.RemoveAll(tmpdir) src := filepath.Join(tmpdir, "a.go") - err = ioutil.WriteFile(src, []byte(`package main; func main() {}`), 0644) + err = os.WriteFile(src, []byte(`package main; func main() {}`), 0644) if err != nil { t.Fatal(err) } @@ -684,7 +683,7 @@ func TestInvalidOptionalHeaderMagic(t *testing.T) { func TestImportedSymbolsNoPanicMissingOptionalHeader(t *testing.T) { // https://golang.org/issue/30250 // ImportedSymbols shouldn't panic if optional headers is missing - data, err := ioutil.ReadFile("testdata/gcc-amd64-mingw-obj") + data, err := os.ReadFile("testdata/gcc-amd64-mingw-obj") if err != nil { t.Fatal(err) } |
