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
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
|
// Package ldd provides a robust parser for ldd(1) output, and a convenience
// function for running ldd(1) in a strict sandbox.
//
// Note: despite the additional hardening, great care must be taken when using
// ldd(1). As a general rule, you must never run ldd(1) against a file that you
// do not wish to execute within the same context.
package ldd
import (
"bufio"
"bytes"
"errors"
"fmt"
"io"
"strconv"
"strings"
"hakurei.app/check"
)
var (
// ErrUnexpectedNewline is returned when encountering an unexpected empty line.
ErrUnexpectedNewline = errors.New("unexpected newline")
// ErrUnexpectedSeparator is returned when encountering an unexpected separator segment.
ErrUnexpectedSeparator = errors.New("unexpected separator")
// ErrBadLocationFormat is returned for an incorrectly formatted [Entry.Location] segment.
ErrBadLocationFormat = errors.New("bad location format")
)
// EntryUnexpectedSegmentsError is returned when encountering a line containing
// unexpected number of segments.
type EntryUnexpectedSegmentsError string
func (e EntryUnexpectedSegmentsError) Error() string {
return fmt.Sprintf("unexpected segments in entry %q", string(e))
}
// An Entry represents one line of ldd(1) output.
type Entry struct {
// File name of required object.
Name string `json:"name"`
// Absolute pathname of matched object. Only populated for the long variant.
Path *check.Absolute `json:"path,omitempty"`
// Address at which the object is loaded.
Location uint64 `json:"location"`
}
const (
// entrySegmentIndexName is the index of the segment holding [Entry.Name].
entrySegmentIndexName = 0
// entrySegmentIndexPath is the index of the segment holding [Entry.Path],
// present only for a line describing a fully populated [Entry].
entrySegmentIndexPath = 2
// entrySegmentIndexSeparator is the index of the segment containing the
// magic bytes entrySegmentFullSeparator, present only for a line describing
// a fully populated [Entry].
entrySegmentIndexSeparator = 1
// entrySegmentIndexLocation is the index of the segment holding
// [Entry.Location] for a line describing a fully populated [Entry].
entrySegmentIndexLocation = 3
// entrySegmentIndexLocationShort is the index of the segment holding
// [Entry.Location] for a line describing only [Entry.Name].
entrySegmentIndexLocationShort = 1
// entrySegmentSep is the byte separating segments in an [Entry] line.
entrySegmentSep = ' '
// entrySegmentFullSeparator is the exact contents of the segment at index
// entrySegmentIndexSeparator.
entrySegmentFullSeparator = "=>"
// entrySegmentLocationLengthMin is the minimum possible length of a segment
// corresponding to [Entry.Location].
entrySegmentLocationLengthMin = 4
// entrySegmentLocationPrefix are magic bytes prefixing a segment
// corresponding to [Entry.Location].
entrySegmentLocationPrefix = "(0x"
// entrySegmentLocationSuffix is the magic byte suffixing a segment
// corresponding to [Entry.Location].
entrySegmentLocationSuffix = ')'
)
// decodeLocationSegment decodes and saves the segment corresponding to [Entry.Location].
func (e *Entry) decodeLocationSegment(segment []byte) (err error) {
if len(segment) < entrySegmentLocationLengthMin ||
segment[len(segment)-1] != entrySegmentLocationSuffix ||
string(segment[:len(entrySegmentLocationPrefix)]) != entrySegmentLocationPrefix {
return ErrBadLocationFormat
}
e.Location, err = strconv.ParseUint(string(segment[3:len(segment)-1]), 16, 64)
return
}
// UnmarshalText parses a line of ldd(1) output and saves it to [Entry].
func (e *Entry) UnmarshalText(data []byte) error {
var (
segments = bytes.SplitN(data, []byte{entrySegmentSep}, 5)
// segment to pass to decodeLocationSegment
iL int
)
switch len(segments) {
case 2: // /lib/ld-musl-x86_64.so.1 (0x7f04d14ef000)
iL = entrySegmentIndexLocationShort
e.Name = string(bytes.TrimSpace(segments[entrySegmentIndexName]))
case 4: // libc.musl-x86_64.so.1 => /lib/ld-musl-x86_64.so.1 (0x7f04d14ef000)
iL = entrySegmentIndexLocation
if string(segments[entrySegmentIndexSeparator]) != entrySegmentFullSeparator {
return ErrUnexpectedSeparator
}
if a, err := check.NewAbs(string(segments[entrySegmentIndexPath])); err != nil {
return err
} else {
e.Path = a
}
e.Name = string(bytes.TrimSpace(segments[entrySegmentIndexName]))
default:
return EntryUnexpectedSegmentsError(data)
}
return e.decodeLocationSegment(segments[iL])
}
// String returns the musl ldd(1) representation of [Entry].
func (e *Entry) String() string {
// nameInvalid is used for a zero-length e.Name
const nameInvalid = "invalid"
var buf strings.Builder
// libzstd.so.1 => /usr/lib/libzstd.so.1 (0x7ff71bfd2000)
l := len(e.Name) + 1
if e.Name == "" {
l += len(nameInvalid)
}
if e.Path != nil {
l += len(entrySegmentFullSeparator) + 1 + len(e.Path.String()) + 1
}
l += len(entrySegmentLocationPrefix) + 1<<4 + 1
buf.Grow(l)
if e.Name != "" {
buf.WriteString(e.Name + " ")
} else {
buf.WriteString(nameInvalid + " ")
}
if e.Path != nil {
buf.WriteString(entrySegmentFullSeparator + " " + e.Path.String() + " ")
}
buf.WriteString(entrySegmentLocationPrefix +
strconv.FormatUint(e.Location, 16) +
string(entrySegmentLocationSuffix))
return buf.String()
}
// Path returns a deduplicated slice of absolute directory paths in entries.
func Path(entries []*Entry) []*check.Absolute {
p := make([]*check.Absolute, 0, len(entries)*2)
for _, entry := range entries {
if entry.Path != nil {
p = append(p, entry.Path.Dir())
}
if a, err := check.NewAbs(entry.Name); err == nil {
p = append(p, a.Dir())
}
}
check.SortAbs(p)
return check.CompactAbs(p)
}
// A Decoder reads and decodes [Entry] values from an input stream.
//
// The zero value is not safe for use.
type Decoder struct {
s *bufio.Scanner
// Whether the current line is not the first line.
notFirst bool
// Whether s has no more tokens.
depleted bool
// Holds onto the first error encountered while parsing.
err error
}
// NewDecoder returns a new decoder that reads from r.
//
// The decoder introduces its own buffering and may read data from r beyond the
// [Entry] values requested.
func NewDecoder(r io.Reader) *Decoder { return &Decoder{s: bufio.NewScanner(r)} }
// Scan advances the [Decoder] to the next [Entry] and stores the result in the
// value pointed to by v.
func (d *Decoder) Scan(v *Entry) bool {
if d.s == nil || d.err != nil || d.depleted {
return false
}
if !d.s.Scan() {
d.depleted = true
return false
}
data := d.s.Bytes()
if len(data) == 0 {
if d.notFirst {
if d.s.Scan() && d.err == nil {
d.err = ErrUnexpectedNewline
}
// trailing newline is allowed (glibc)
return false
}
// leading newline is allowed (musl)
d.notFirst = true
return d.Scan(v)
}
d.notFirst = true
d.err = v.UnmarshalText(data)
return d.err == nil
}
// Err returns the first non-EOF error that was encountered by the underlying
// [bufio.Scanner] or [Entry].
func (d *Decoder) Err() error {
if d.err != nil || d.s == nil {
return d.err
}
return d.s.Err()
}
// Decode reads from the input stream until there are no more entries and
// returns the results in a slice.
func (d *Decoder) Decode() ([]*Entry, error) {
var entries []*Entry
e := new(Entry)
for d.Scan(e) {
entries = append(entries, e)
e = new(Entry)
}
return entries, d.Err()
}
// Parse returns a slice of addresses to [Entry] decoded from p.
func Parse(p []byte) ([]*Entry, error) {
return NewDecoder(bytes.NewReader(p)).Decode()
}
|