
With the glibc loader the link map entry for the static executable has an empty name field and address equal to 0x0. This case was already handled by the check in bininfo.go AddImage for names to be valid paths. With the musl loader however the first entry, corresponding to the static executable, has a valid path with address equal to 0x0, since we record a real address for the image corresponding to the static executable this results in having two entries for the executable when musl is used to link go programs. Change the code scanning the debug link map so that the first entry is skipped if it has address equal to zero. Fixes #3617
193 lines
4.4 KiB
Go
193 lines
4.4 KiB
Go
package linutil
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
|
|
"github.com/go-delve/delve/pkg/proc"
|
|
)
|
|
|
|
const (
|
|
maxNumLibraries = 1000000 // maximum number of loaded libraries, to avoid loading forever on corrupted memory
|
|
maxLibraryPathLength = 1000000 // maximum length for the path of a library, to avoid loading forever on corrupted memory
|
|
)
|
|
|
|
var ErrTooManyLibraries = errors.New("number of loaded libraries exceeds maximum")
|
|
|
|
const (
|
|
_DT_NULL = 0 // DT_NULL as defined by SysV ABI specification
|
|
_DT_DEBUG = 21 // DT_DEBUG as defined by SysV ABI specification
|
|
)
|
|
|
|
// readUintRaw reads an integer of ptrSize bytes, with the specified byte order, from reader.
|
|
func readUintRaw(reader io.Reader, order binary.ByteOrder, ptrSize int) (uint64, error) {
|
|
switch ptrSize {
|
|
case 4:
|
|
var n uint32
|
|
if err := binary.Read(reader, order, &n); err != nil {
|
|
return 0, err
|
|
}
|
|
return uint64(n), nil
|
|
case 8:
|
|
var n uint64
|
|
if err := binary.Read(reader, order, &n); err != nil {
|
|
return 0, err
|
|
}
|
|
return n, nil
|
|
}
|
|
return 0, fmt.Errorf("not supported ptr size %d", ptrSize)
|
|
}
|
|
|
|
// dynamicSearchDebug searches for the DT_DEBUG entry in the .dynamic section
|
|
func dynamicSearchDebug(p proc.Process) (uint64, error) {
|
|
bi := p.BinInfo()
|
|
mem := p.Memory()
|
|
|
|
dynbuf := make([]byte, bi.ElfDynamicSection.Size)
|
|
_, err := mem.ReadMemory(dynbuf, bi.ElfDynamicSection.Addr)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
rd := bytes.NewReader(dynbuf)
|
|
|
|
for {
|
|
var tag, val uint64
|
|
if tag, err = readUintRaw(rd, binary.LittleEndian, p.BinInfo().Arch.PtrSize()); err != nil {
|
|
return 0, err
|
|
}
|
|
if val, err = readUintRaw(rd, binary.LittleEndian, p.BinInfo().Arch.PtrSize()); err != nil {
|
|
return 0, err
|
|
}
|
|
switch tag {
|
|
case _DT_NULL:
|
|
return 0, nil
|
|
case _DT_DEBUG:
|
|
return val, nil
|
|
}
|
|
}
|
|
}
|
|
|
|
func readPtr(p proc.Process, addr uint64) (uint64, error) {
|
|
ptrbuf := make([]byte, p.BinInfo().Arch.PtrSize())
|
|
_, err := p.Memory().ReadMemory(ptrbuf, addr)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return readUintRaw(bytes.NewReader(ptrbuf), binary.LittleEndian, p.BinInfo().Arch.PtrSize())
|
|
}
|
|
|
|
type linkMap struct {
|
|
addr uint64
|
|
name string
|
|
ld uint64
|
|
next, prev uint64
|
|
}
|
|
|
|
func readLinkMapNode(p proc.Process, r_map uint64) (*linkMap, error) {
|
|
bi := p.BinInfo()
|
|
|
|
var lm linkMap
|
|
var ptrs [5]uint64
|
|
for i := range ptrs {
|
|
var err error
|
|
ptrs[i], err = readPtr(p, r_map+uint64(bi.Arch.PtrSize()*i))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
lm.addr = ptrs[0]
|
|
var err error
|
|
lm.name, err = readCString(p, ptrs[1])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
lm.ld = ptrs[2]
|
|
lm.next = ptrs[3]
|
|
lm.prev = ptrs[4]
|
|
return &lm, nil
|
|
}
|
|
|
|
func readCString(p proc.Process, addr uint64) (string, error) {
|
|
if addr == 0 {
|
|
return "", nil
|
|
}
|
|
mem := p.Memory()
|
|
buf := make([]byte, 1)
|
|
r := []byte{}
|
|
for {
|
|
if len(r) > maxLibraryPathLength {
|
|
return "", fmt.Errorf("error reading libraries: string too long (%d)", len(r))
|
|
}
|
|
_, err := mem.ReadMemory(buf, addr)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if buf[0] == 0 {
|
|
break
|
|
}
|
|
r = append(r, buf[0])
|
|
addr++
|
|
}
|
|
return string(r), nil
|
|
}
|
|
|
|
// ElfUpdateSharedObjects reads the list of dynamic libraries loaded by the
|
|
// dynamic linker from the .dynamic section and uses it to update p.BinInfo().
|
|
// See the SysV ABI for a description of how the .dynamic section works:
|
|
// http://www.sco.com/developers/gabi/latest/contents.html
|
|
func ElfUpdateSharedObjects(p proc.Process) error {
|
|
bi := p.BinInfo()
|
|
if bi.ElfDynamicSection.Addr == 0 {
|
|
// no dynamic section, therefore nothing to do here
|
|
return nil
|
|
}
|
|
debugAddr, err := dynamicSearchDebug(p)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if debugAddr == 0 {
|
|
// no DT_DEBUG entry
|
|
return nil
|
|
}
|
|
|
|
// Offsets of the fields of the r_debug and link_map structs,
|
|
// see /usr/include/elf/link.h for a full description of those structs.
|
|
debugMapOffset := uint64(p.BinInfo().Arch.PtrSize())
|
|
|
|
r_map, err := readPtr(p, debugAddr+debugMapOffset)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
libs := []string{}
|
|
|
|
first := true
|
|
|
|
for {
|
|
if r_map == 0 {
|
|
break
|
|
}
|
|
if len(libs) > maxNumLibraries {
|
|
return ErrTooManyLibraries
|
|
}
|
|
lm, err := readLinkMapNode(p, r_map)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !first || lm.addr != 0 {
|
|
// First entry is the executable, we don't need to add it, and doing so
|
|
// can cause duplicate entries due to base address mismatches.
|
|
bi.AddImage(lm.name, lm.addr)
|
|
}
|
|
libs = append(libs, lm.name)
|
|
first = false
|
|
r_map = lm.next
|
|
}
|
|
|
|
return nil
|
|
}
|