Skip to content

Commit

Permalink
proc: catch panics when reading debug info for stripped executables (g…
Browse files Browse the repository at this point in the history
…o-delve#3678)

Adds a recover to the functions reading debug info from the go runtime
data structures for stripped executables.

This function is best-effort and can sometimes fail in weird ways,
instead of crashing recover any panic and turn them into errors.

Fixes go-delve#3650
  • Loading branch information
aarzilli authored Mar 4, 2024
1 parent fdb732b commit 40b58f9
Showing 1 changed file with 128 additions and 108 deletions.
236 changes: 128 additions & 108 deletions pkg/proc/bininfo.go
Original file line number Diff line number Diff line change
Expand Up @@ -1455,63 +1455,10 @@ func loadBinaryInfoElf(bi *BinaryInfo, image *Image, path string, addr uint64, w
if len(bi.Images) <= 1 {
fmt.Fprintln(os.Stderr, "Warning: no debug info found, some functionality will be missing such as stack traces and variable evaluation.")
}
cu := &compileUnit{}
cu.image = image
symTable, symTabAddr, err := readPcLnTableElf(elfFile, path)
err := loadBinaryInfoGoRuntimeElf(bi, image, path, elfFile)
if err != nil {
return fmt.Errorf("could not read debug info (%v) and could not read go symbol table (%v)", dwerr, err)
}
image.symTable = symTable
noPtrSectionData, err := elfFile.Section(".noptrdata").Data()
if err != nil {
return err
}
md, err := parseModuleData(noPtrSectionData, symTabAddr)
if err != nil {
return err
}
roDataAddr := elfFile.Section(".rodata").Addr
goFuncVal, err := findGoFuncVal(md, roDataAddr, bi.Arch.ptrSize)
if err != nil {
return err
}
prog := gosym.ProgContaining(elfFile, goFuncVal)
inlFuncs := make(map[string]*Function)
for _, f := range image.symTable.Funcs {
fnEntry := f.Entry + image.StaticBase
if prog != nil {
inlCalls, err := image.symTable.GetInlineTree(&f, goFuncVal, prog.Vaddr, prog.ReaderAt)
if err != nil {
return err
}
for _, inlfn := range inlCalls {
newInlinedCall := InlinedCall{cu: cu, LowPC: fnEntry + uint64(inlfn.ParentPC)}
if fn, ok := inlFuncs[inlfn.Name]; ok {
fn.InlinedCalls = append(fn.InlinedCalls, newInlinedCall)
continue
}
inlFuncs[inlfn.Name] = &Function{
Name: inlfn.Name,
Entry: 0, End: 0,
cu: cu,
InlinedCalls: []InlinedCall{
newInlinedCall,
},
}
}
}
fn := Function{Name: f.Name, Entry: fnEntry, End: f.End + image.StaticBase, cu: cu}
bi.Functions = append(bi.Functions, fn)
}
for i := range inlFuncs {
bi.Functions = append(bi.Functions, *inlFuncs[i])
}
sort.Sort(functionsDebugInfoByEntry(bi.Functions))
for f := range image.symTable.Files {
bi.Sources = append(bi.Sources, f)
}
sort.Strings(bi.Sources)
bi.Sources = uniq(bi.Sources)
return nil
}
image.sepDebugCloser = sepFile
Expand Down Expand Up @@ -1917,63 +1864,10 @@ func loadBinaryInfoMacho(bi *BinaryInfo, image *Image, path string, entryPoint u
if len(bi.Images) <= 1 {
fmt.Fprintln(os.Stderr, "Warning: no debug info found, some functionality will be missing such as stack traces and variable evaluation.")
}
symTable, symTabAddr, err := readPcLnTableMacho(exe, path)
err := loadBinaryInfoGoRuntimeMacho(bi, image, path, exe)
if err != nil {
return fmt.Errorf("could not read debug info (%v) and could not read go symbol table (%v)", dwerr, err)
}
image.symTable = symTable
cu := &compileUnit{}
cu.image = image
noPtrSectionData, err := exe.Section("__noptrdata").Data()
if err != nil {
return err
}
md, err := parseModuleData(noPtrSectionData, symTabAddr)
if err != nil {
return err
}
roDataAddr := exe.Section("__rodata").Addr
goFuncVal, err := findGoFuncVal(md, roDataAddr, bi.Arch.ptrSize)
if err != nil {
return err
}
seg := gosym.SegmentContaining(exe, goFuncVal)
inlFuncs := make(map[string]*Function)
for _, f := range image.symTable.Funcs {
fnEntry := f.Entry + image.StaticBase
if seg != nil {
inlCalls, err := image.symTable.GetInlineTree(&f, goFuncVal, seg.Addr, seg.ReaderAt)
if err != nil {
return err
}
for _, inlfn := range inlCalls {
newInlinedCall := InlinedCall{cu: cu, LowPC: fnEntry + uint64(inlfn.ParentPC)}
if fn, ok := inlFuncs[inlfn.Name]; ok {
fn.InlinedCalls = append(fn.InlinedCalls, newInlinedCall)
continue
}
inlFuncs[inlfn.Name] = &Function{
Name: inlfn.Name,
Entry: 0, End: 0,
cu: cu,
InlinedCalls: []InlinedCall{
newInlinedCall,
},
}
}
}
fn := Function{Name: f.Name, Entry: fnEntry, End: f.End + image.StaticBase, cu: cu}
bi.Functions = append(bi.Functions, fn)
}
for i := range inlFuncs {
bi.Functions = append(bi.Functions, *inlFuncs[i])
}
sort.Sort(functionsDebugInfoByEntry(bi.Functions))
for f := range image.symTable.Files {
bi.Sources = append(bi.Sources, f)
}
sort.Strings(bi.Sources)
bi.Sources = uniq(bi.Sources)
return nil
}
debugInfoBytes, err := godwarf.GetDebugSectionMacho(exe, "info")
Expand Down Expand Up @@ -2127,6 +2021,132 @@ func (bi *BinaryInfo) macOSDebugFrameBugWorkaround() {
}
}

// GO RUNTIME INFO ////////////////////////////////////////////////////////////

// loadBinaryInfoGoRuntimeElf loads information from the Go runtime sections
// of an ELF binary, it is only called when debug info has been stripped.
func loadBinaryInfoGoRuntimeElf(bi *BinaryInfo, image *Image, path string, elfFile *elf.File) (err error) {
// This is a best-effort procedure, it can go wrong in unexpected ways, so
// recover all panics.
defer func() {
ierr := recover()
if ierr != nil {
err = fmt.Errorf("error loading binary info from Go runtime: %v", ierr)
}
}()

cu := &compileUnit{}
cu.image = image
symTable, symTabAddr, err := readPcLnTableElf(elfFile, path)
if err != nil {
return err
}
image.symTable = symTable
noPtrSectionData, err := elfFile.Section(".noptrdata").Data()
if err != nil {
return err
}
md, err := parseModuleData(noPtrSectionData, symTabAddr)
if err != nil {
return err
}
roDataAddr := elfFile.Section(".rodata").Addr
goFuncVal, err := findGoFuncVal(md, roDataAddr, bi.Arch.ptrSize)
if err != nil {
return err
}
prog := gosym.ProgContaining(elfFile, goFuncVal)
var progAddr uint64
var progReaderAt io.ReaderAt
if prog != nil {
progAddr = prog.Vaddr
progReaderAt = prog.ReaderAt
}
return loadBinaryInfoGoRuntimeCommon(bi, image, cu, goFuncVal, progAddr, progReaderAt)
}

// loadBinaryInfoGoRuntimeMacho loads information from the Go runtime sections
// of an Macho-o binary, it is only called when debug info has been stripped.
func loadBinaryInfoGoRuntimeMacho(bi *BinaryInfo, image *Image, path string, exe *macho.File) (err error) {
// This is a best-effort procedure, it can go wrong in unexpected ways, so
// recover all panics.
defer func() {
ierr := recover()
if ierr != nil {
err = fmt.Errorf("error loading binary info from Go runtime: %v", ierr)
}
}()

cu := &compileUnit{}
cu.image = image
symTable, symTabAddr, err := readPcLnTableMacho(exe, path)
if err != nil {
return err
}
image.symTable = symTable
noPtrSectionData, err := exe.Section("__noptrdata").Data()
if err != nil {
return err
}
md, err := parseModuleData(noPtrSectionData, symTabAddr)
if err != nil {
return err
}
roDataAddr := exe.Section("__rodata").Addr
goFuncVal, err := findGoFuncVal(md, roDataAddr, bi.Arch.ptrSize)
if err != nil {
return err
}
seg := gosym.SegmentContaining(exe, goFuncVal)
var segAddr uint64
var segReaderAt io.ReaderAt
if seg != nil {
segAddr = seg.Addr
segReaderAt = seg.ReaderAt
}
return loadBinaryInfoGoRuntimeCommon(bi, image, cu, goFuncVal, segAddr, segReaderAt)
}

func loadBinaryInfoGoRuntimeCommon(bi *BinaryInfo, image *Image, cu *compileUnit, goFuncVal uint64, goFuncSegAddr uint64, goFuncReader io.ReaderAt) error {
inlFuncs := make(map[string]*Function)
for _, f := range image.symTable.Funcs {
fnEntry := f.Entry + image.StaticBase
if goFuncReader != nil {
inlCalls, err := image.symTable.GetInlineTree(&f, goFuncVal, goFuncSegAddr, goFuncReader)
if err != nil {
return err
}
for _, inlfn := range inlCalls {
newInlinedCall := InlinedCall{cu: cu, LowPC: fnEntry + uint64(inlfn.ParentPC)}
if fn, ok := inlFuncs[inlfn.Name]; ok {
fn.InlinedCalls = append(fn.InlinedCalls, newInlinedCall)
continue
}
inlFuncs[inlfn.Name] = &Function{
Name: inlfn.Name,
Entry: 0, End: 0,
cu: cu,
InlinedCalls: []InlinedCall{
newInlinedCall,
},
}
}
}
fn := Function{Name: f.Name, Entry: fnEntry, End: f.End + image.StaticBase, cu: cu}
bi.Functions = append(bi.Functions, fn)
}
for i := range inlFuncs {
bi.Functions = append(bi.Functions, *inlFuncs[i])
}
sort.Sort(functionsDebugInfoByEntry(bi.Functions))
for f := range image.symTable.Files {
bi.Sources = append(bi.Sources, f)
}
sort.Strings(bi.Sources)
bi.Sources = uniq(bi.Sources)
return nil
}

// Do not call this function directly it isn't able to deal correctly with package paths
func (bi *BinaryInfo) findType(name string) (godwarf.Type, error) {
name = strings.ReplaceAll(name, "interface{", "interface {")
Expand Down

0 comments on commit 40b58f9

Please sign in to comment.