debug/dwarf: don't try to parse addr/rnglists header

In an executable, the debug_addr and debug_rnglists sections are
assembled by concatenating the input sections, and each input section
has a header, and each header may have different attributes. So just
parsing the single header isn't right.  Parsing the header is not
necessary to handle offsets into these sections which is all we do.

Looking at the header is also problematic because GCC with
-gsplit-dwarf when using DWARF versions 2 through 4 emits a
.debug_addr section, but it has no header.  The header was only added
for DWARF 5. So we can't parse the header at all for that case, and we
can't even detect that case in general.

This CL also fixes SeekPC with addrx and strx formats, by not using
the wrong compilation unit to find the address or string base.
To make that work when parsing the compilation unit itself, we add
support for delay the resolution of those values until we know the base.

New test binaries built with

gcc -gdwarf-5 -no-pie debug/dwarf/testdata/line[12].c
(gcc (Debian 10.2.0-15) 10.2.0)

clang -gdwarf-5 -no-pie debug/dwarf/testdata/line[12].c
(clang version 9.0.1-14)

Change-Id: I66783e0eded629bf80c467767f781164d344a54d
Reviewed-on: https://go-review.googlesource.com/c/go/+/277233
Trust: Ian Lance Taylor <iant@golang.org>
Reviewed-by: Than McIntosh <thanm@google.com>
This commit is contained in:
Ian Lance Taylor 2020-12-11 15:15:43 -08:00
parent be10af7c4e
commit 828746ec57
6 changed files with 165 additions and 152 deletions

View File

@ -22,7 +22,12 @@ func TestDwarf5Ranges(t *testing.T) {
if err := d.AddSection(".debug_rnglists", rngLists); err != nil { if err := d.AddSection(".debug_rnglists", rngLists); err != nil {
t.Fatal(err) t.Fatal(err)
} }
ret, err := d.dwarf5Ranges(nil, 0x5fbd, 0xc, [][2]uint64{}) u := &unit{
asize: 8,
vers: 5,
is64: true,
}
ret, err := d.dwarf5Ranges(u, nil, 0x5fbd, 0xc, [][2]uint64{})
if err != nil { if err != nil {
t.Fatalf("could not read rnglist: %v", err) t.Fatalf("could not read rnglist: %v", err)
} }

View File

@ -423,6 +423,47 @@ func (b *buf) entry(cu *Entry, atab abbrevTable, ubase Offset, vers int) *Entry
Children: a.children, Children: a.children,
Field: make([]Field, len(a.field)), Field: make([]Field, len(a.field)),
} }
// If we are currently parsing the compilation unit,
// we can't evaluate Addrx or Strx until we've seen the
// relevant base entry.
type delayed struct {
idx int
off uint64
fmt format
}
var delay []delayed
resolveStrx := func(strBase, off uint64) string {
off += strBase
if uint64(int(off)) != off {
b.error("DW_FORM_strx offset out of range")
}
b1 := makeBuf(b.dwarf, b.format, "str_offsets", 0, b.dwarf.strOffsets)
b1.skip(int(off))
is64, _ := b.format.dwarf64()
if is64 {
off = b1.uint64()
} else {
off = uint64(b1.uint32())
}
if b1.err != nil {
b.err = b1.err
return ""
}
if uint64(int(off)) != off {
b.error("DW_FORM_strx indirect offset out of range")
}
b1 = makeBuf(b.dwarf, b.format, "str", 0, b.dwarf.str)
b1.skip(int(off))
val := b1.string()
if b1.err != nil {
b.err = b1.err
}
return val
}
for i := range e.Field { for i := range e.Field {
e.Field[i].Attr = a.field[i].attr e.Field[i].Attr = a.field[i].attr
e.Field[i].Class = a.field[i].class e.Field[i].Class = a.field[i].class
@ -467,10 +508,13 @@ func (b *buf) entry(cu *Entry, atab abbrevTable, ubase Offset, vers int) *Entry
var addrBase int64 var addrBase int64
if cu != nil { if cu != nil {
addrBase, _ = cu.Val(AttrAddrBase).(int64) addrBase, _ = cu.Val(AttrAddrBase).(int64)
} else if a.tag == TagCompileUnit {
delay = append(delay, delayed{i, off, formAddrx})
break
} }
var err error var err error
val, err = b.dwarf.debugAddr(uint64(addrBase), off) val, err = b.dwarf.debugAddr(b.format, uint64(addrBase), off)
if err != nil { if err != nil {
if b.err == nil { if b.err == nil {
b.err = err b.err = err
@ -611,38 +655,16 @@ func (b *buf) entry(cu *Entry, atab abbrevTable, ubase Offset, vers int) *Entry
// compilation unit. This won't work if the // compilation unit. This won't work if the
// program uses Reader.Seek to skip over the // program uses Reader.Seek to skip over the
// unit. Not much we can do about that. // unit. Not much we can do about that.
var strBase int64
if cu != nil { if cu != nil {
cuOff, ok := cu.Val(AttrStrOffsetsBase).(int64) strBase, _ = cu.Val(AttrStrOffsetsBase).(int64)
if ok { } else if a.tag == TagCompileUnit {
off += uint64(cuOff) delay = append(delay, delayed{i, off, formStrx})
} break
} }
if uint64(int(off)) != off { val = resolveStrx(uint64(strBase), off)
b.error("DW_FORM_strx offset out of range")
}
b1 := makeBuf(b.dwarf, b.format, "str_offsets", 0, b.dwarf.strOffsets)
b1.skip(int(off))
if is64 {
off = b1.uint64()
} else {
off = uint64(b1.uint32())
}
if b1.err != nil {
b.err = b1.err
return nil
}
if uint64(int(off)) != off {
b.error("DW_FORM_strx indirect offset out of range")
}
b1 = makeBuf(b.dwarf, b.format, "str", 0, b.dwarf.str)
b1.skip(int(off))
val = b1.string()
if b1.err != nil {
b.err = b1.err
return nil
}
case formStrpSup: case formStrpSup:
is64, known := b.format.dwarf64() is64, known := b.format.dwarf64()
if !known { if !known {
@ -689,11 +711,32 @@ func (b *buf) entry(cu *Entry, atab abbrevTable, ubase Offset, vers int) *Entry
case formRnglistx: case formRnglistx:
val = b.uint() val = b.uint()
} }
e.Field[i].Val = val e.Field[i].Val = val
} }
if b.err != nil { if b.err != nil {
return nil return nil
} }
for _, del := range delay {
switch del.fmt {
case formAddrx:
addrBase, _ := e.Val(AttrAddrBase).(int64)
val, err := b.dwarf.debugAddr(b.format, uint64(addrBase), del.off)
if err != nil {
b.err = err
return nil
}
e.Field[del.idx].Val = val
case formStrx:
strBase, _ := e.Val(AttrStrOffsetsBase).(int64)
e.Field[del.idx].Val = resolveStrx(uint64(strBase), del.off)
if b.err != nil {
return nil
}
}
}
return e return e
} }
@ -877,6 +920,7 @@ func (r *Reader) SeekPC(pc uint64) (*Entry, error) {
r.err = nil r.err = nil
r.lastChildren = false r.lastChildren = false
r.unit = unit r.unit = unit
r.cu = nil
u := &r.d.unit[unit] u := &r.d.unit[unit]
r.b = makeBuf(r.d, u, "info", u.off, u.data) r.b = makeBuf(r.d, u, "info", u.off, u.data)
e, err := r.Next() e, err := r.Next()
@ -946,7 +990,7 @@ func (d *Data) Ranges(e *Entry) ([][2]uint64, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
return d.dwarf5Ranges(cu, base, ranges, ret) return d.dwarf5Ranges(u, cu, base, ranges, ret)
case ClassRngList: case ClassRngList:
// TODO: support DW_FORM_rnglistx // TODO: support DW_FORM_rnglistx
@ -1023,13 +1067,13 @@ func (d *Data) dwarf2Ranges(u *unit, base uint64, ranges int64, ret [][2]uint64)
// dwarf5Ranges interpets a debug_rnglists sequence, see DWARFv5 section // dwarf5Ranges interpets a debug_rnglists sequence, see DWARFv5 section
// 2.17.3 (page 53). // 2.17.3 (page 53).
func (d *Data) dwarf5Ranges(cu *Entry, base uint64, ranges int64, ret [][2]uint64) ([][2]uint64, error) { func (d *Data) dwarf5Ranges(u *unit, cu *Entry, base uint64, ranges int64, ret [][2]uint64) ([][2]uint64, error) {
var addrBase int64 var addrBase int64
if cu != nil { if cu != nil {
addrBase, _ = cu.Val(AttrAddrBase).(int64) addrBase, _ = cu.Val(AttrAddrBase).(int64)
} }
buf := makeBuf(d, d.rngLists, "rnglists", 0, d.rngLists.data) buf := makeBuf(d, u, "rnglists", 0, d.rngLists)
buf.skip(int(ranges)) buf.skip(int(ranges))
for { for {
opcode := buf.uint8() opcode := buf.uint8()
@ -1043,7 +1087,7 @@ func (d *Data) dwarf5Ranges(cu *Entry, base uint64, ranges int64, ret [][2]uint6
case rleBaseAddressx: case rleBaseAddressx:
baseIdx := buf.uint() baseIdx := buf.uint()
var err error var err error
base, err = d.debugAddr(uint64(addrBase), baseIdx) base, err = d.debugAddr(u, uint64(addrBase), baseIdx)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -1052,11 +1096,11 @@ func (d *Data) dwarf5Ranges(cu *Entry, base uint64, ranges int64, ret [][2]uint6
startIdx := buf.uint() startIdx := buf.uint()
endIdx := buf.uint() endIdx := buf.uint()
start, err := d.debugAddr(uint64(addrBase), startIdx) start, err := d.debugAddr(u, uint64(addrBase), startIdx)
if err != nil { if err != nil {
return nil, err return nil, err
} }
end, err := d.debugAddr(uint64(addrBase), endIdx) end, err := d.debugAddr(u, uint64(addrBase), endIdx)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -1065,7 +1109,7 @@ func (d *Data) dwarf5Ranges(cu *Entry, base uint64, ranges int64, ret [][2]uint6
case rleStartxLength: case rleStartxLength:
startIdx := buf.uint() startIdx := buf.uint()
len := buf.uint() len := buf.uint()
start, err := d.debugAddr(uint64(addrBase), startIdx) start, err := d.debugAddr(u, uint64(addrBase), startIdx)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -1093,19 +1137,18 @@ func (d *Data) dwarf5Ranges(cu *Entry, base uint64, ranges int64, ret [][2]uint6
} }
// debugAddr returns the address at idx in debug_addr // debugAddr returns the address at idx in debug_addr
func (d *Data) debugAddr(addrBase, idx uint64) (uint64, error) { func (d *Data) debugAddr(format dataFormat, addrBase, idx uint64) (uint64, error) {
off := idx*uint64(d.addr.addrsize()) + addrBase off := idx*uint64(format.addrsize()) + addrBase
if uint64(int(off)) != off { if uint64(int(off)) != off {
return 0, errors.New("offset out of range") return 0, errors.New("offset out of range")
} }
b := makeBuf(d, d.addr, "addr", 0, d.addr.data) b := makeBuf(d, format, "addr", 0, d.addr)
b.skip(int(off)) b.skip(int(off))
val := b.addr() val := b.addr()
if b.err != nil { if b.err != nil {
return 0, b.err return 0, b.err
} }
return val, nil return val, nil
} }

View File

@ -55,6 +55,20 @@ func TestReaderSeek(t *testing.T) {
{0x400611, nil}, {0x400611, nil},
} }
testRanges(t, "testdata/line-gcc.elf", want) testRanges(t, "testdata/line-gcc.elf", want)
want = []wantRange{
{0x401122, [][2]uint64{{0x401122, 0x401166}}},
{0x401165, [][2]uint64{{0x401122, 0x401166}}},
{0x401166, [][2]uint64{{0x401166, 0x401179}}},
}
testRanges(t, "testdata/line-gcc-dwarf5.elf", want)
want = []wantRange{
{0x401130, [][2]uint64{{0x401130, 0x40117e}}},
{0x40117d, [][2]uint64{{0x401130, 0x40117e}}},
{0x40117e, nil},
}
testRanges(t, "testdata/line-clang-dwarf5.elf", want)
} }
func TestRangesSection(t *testing.T) { func TestRangesSection(t *testing.T) {
@ -97,44 +111,72 @@ func testRanges(t *testing.T, name string, want []wantRange) {
} }
func TestReaderRanges(t *testing.T) { func TestReaderRanges(t *testing.T) {
d := elfData(t, "testdata/line-gcc.elf") type subprograms []struct {
subprograms := []struct {
name string name string
ranges [][2]uint64 ranges [][2]uint64
}
tests := []struct {
filename string
subprograms subprograms
}{ }{
{"f1", [][2]uint64{{0x40059d, 0x4005e7}}}, {
{"main", [][2]uint64{{0x4005e7, 0x400601}}}, "testdata/line-gcc.elf",
{"f2", [][2]uint64{{0x400601, 0x400611}}}, subprograms{
{"f1", [][2]uint64{{0x40059d, 0x4005e7}}},
{"main", [][2]uint64{{0x4005e7, 0x400601}}},
{"f2", [][2]uint64{{0x400601, 0x400611}}},
},
},
{
"testdata/line-gcc-dwarf5.elf",
subprograms{
{"main", [][2]uint64{{0x401147, 0x401166}}},
{"f1", [][2]uint64{{0x401122, 0x401147}}},
{"f2", [][2]uint64{{0x401166, 0x401179}}},
},
},
{
"testdata/line-clang-dwarf5.elf",
subprograms{
{"main", [][2]uint64{{0x401130, 0x401144}}},
{"f1", [][2]uint64{{0x401150, 0x40117e}}},
{"f2", [][2]uint64{{0x401180, 0x401197}}},
},
},
} }
r := d.Reader() for _, test := range tests {
i := 0 d := elfData(t, test.filename)
for entry, err := r.Next(); entry != nil && err == nil; entry, err = r.Next() { subprograms := test.subprograms
if entry.Tag != TagSubprogram {
continue r := d.Reader()
i := 0
for entry, err := r.Next(); entry != nil && err == nil; entry, err = r.Next() {
if entry.Tag != TagSubprogram {
continue
}
if i > len(subprograms) {
t.Fatalf("%s: too many subprograms (expected at most %d)", test.filename, i)
}
if got := entry.Val(AttrName).(string); got != subprograms[i].name {
t.Errorf("%s: subprogram %d name is %s, expected %s", test.filename, i, got, subprograms[i].name)
}
ranges, err := d.Ranges(entry)
if err != nil {
t.Errorf("%s: subprogram %d: %v", test.filename, i, err)
continue
}
if !reflect.DeepEqual(ranges, subprograms[i].ranges) {
t.Errorf("%s: subprogram %d ranges are %x, expected %x", test.filename, i, ranges, subprograms[i].ranges)
}
i++
} }
if i > len(subprograms) { if i < len(subprograms) {
t.Fatalf("too many subprograms (expected at most %d)", i) t.Errorf("%s: saw only %d subprograms, expected %d", test.filename, i, len(subprograms))
} }
if got := entry.Val(AttrName).(string); got != subprograms[i].name {
t.Errorf("subprogram %d name is %s, expected %s", i, got, subprograms[i].name)
}
ranges, err := d.Ranges(entry)
if err != nil {
t.Errorf("subprogram %d: %v", i, err)
continue
}
if !reflect.DeepEqual(ranges, subprograms[i].ranges) {
t.Errorf("subprogram %d ranges are %x, expected %x", i, ranges, subprograms[i].ranges)
}
i++
}
if i < len(subprograms) {
t.Errorf("saw only %d subprograms, expected %d", i, len(subprograms))
} }
} }

View File

@ -26,10 +26,10 @@ type Data struct {
str []byte str []byte
// New sections added in DWARF 5. // New sections added in DWARF 5.
addr *debugAddr addr []byte
lineStr []byte lineStr []byte
strOffsets []byte strOffsets []byte
rngLists *rngLists rngLists []byte
// parsed data // parsed data
abbrevCache map[uint64]abbrevTable abbrevCache map[uint64]abbrevTable
@ -40,21 +40,6 @@ type Data struct {
unit []unit 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") var errSegmentSelector = errors.New("non-zero segment_selector size not supported")
// New returns a new Data object initialized from the given parameters. // New returns a new Data object initialized from the given parameters.
@ -132,76 +117,14 @@ func (d *Data) AddSection(name string, contents []byte) error {
var err error var err error
switch name { switch name {
case ".debug_addr": case ".debug_addr":
d.addr, err = d.parseAddrHeader(contents) d.addr = contents
case ".debug_line_str": case ".debug_line_str":
d.lineStr = contents d.lineStr = contents
case ".debug_str_offsets": case ".debug_str_offsets":
d.strOffsets = contents d.strOffsets = contents
case ".debug_rnglists": case ".debug_rnglists":
d.rngLists, err = d.parseRngListsHeader(contents) d.rngLists = contents
} }
// Just ignore names that we don't yet support. // Just ignore names that we don't yet support.
return err 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)
}

Binary file not shown.

Binary file not shown.