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:
|
|
// https://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
|
|
}
|