File: dynamic.go

package info (click to toggle)
delve 1.24.0-4
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 14,092 kB
  • sloc: ansic: 111,943; sh: 169; asm: 141; makefile: 43; python: 23
file content (192 lines) | stat: -rw-r--r-- 4,518 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
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
}