/
process.go
239 lines (210 loc) · 6.05 KB
/
process.go
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
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Apache License 2.0.
* See the file "LICENSE" for details.
*/
package process
import (
"bufio"
"bytes"
"debug/elf"
"errors"
"fmt"
"io"
"os"
"strings"
"github.com/elastic/otel-profiling-agent/libpf"
"github.com/elastic/otel-profiling-agent/libpf/pfelf"
"github.com/elastic/otel-profiling-agent/libpf/remotememory"
"github.com/elastic/otel-profiling-agent/libpf/stringutil"
)
// systemProcess provides an implementation of the Process interface for a
// process that is currently running on this machine.
type systemProcess struct {
pid libpf.PID
remoteMemory remotememory.RemoteMemory
fileToMapping map[string]*Mapping
}
var _ Process = &systemProcess{}
// New returns an object with Process interface accessing it
func New(pid libpf.PID) Process {
return &systemProcess{
pid: pid,
remoteMemory: remotememory.NewProcessVirtualMemory(pid),
}
}
func (sp *systemProcess) PID() libpf.PID {
return sp.pid
}
func (sp *systemProcess) GetMachineData() MachineData {
return MachineData{Machine: currentMachine}
}
func trimMappingPath(path string) string {
// Trim the deleted indication from the path.
// See path_with_deleted in linux/fs/d_path.c
path = strings.TrimSuffix(path, " (deleted)")
if path == "/dev/zero" {
// Some JIT engines map JIT area from /dev/zero
// make it anonymous.
return ""
}
return path
}
func parseMappings(mapsFile io.Reader) ([]Mapping, error) {
mappings := make([]Mapping, 0)
scanner := bufio.NewScanner(mapsFile)
buf := make([]byte, 512)
scanner.Buffer(buf, 8192)
for scanner.Scan() {
var fields [6]string
var addrs [2]string
var devs [2]string
line := stringutil.ByteSlice2String(scanner.Bytes())
if stringutil.FieldsN(line, fields[:]) < 5 {
continue
}
if stringutil.SplitN(fields[0], "-", addrs[:]) < 2 {
continue
}
mapsFlags := fields[1]
if len(mapsFlags) < 3 {
continue
}
flags := elf.ProgFlag(0)
if mapsFlags[0] == 'r' {
flags |= elf.PF_R
}
if mapsFlags[1] == 'w' {
flags |= elf.PF_W
}
if mapsFlags[2] == 'x' {
flags |= elf.PF_X
}
// Ignore non-executable mappings
if flags&elf.PF_X == 0 {
continue
}
inode := libpf.DecToUint64(fields[4])
path := fields[5]
if stringutil.SplitN(fields[3], ":", devs[:]) < 2 {
continue
}
device := libpf.HexToUint64(devs[0])<<8 + libpf.HexToUint64(devs[1])
if inode == 0 {
if path == "[vdso]" {
// Map to something filename looking with synthesized inode
path = vdsoPathName
device = 0
inode = vdsoInode
} else if path != "" {
// Ignore [vsyscall] and similar executable kernel
// pages we don't care about
continue
}
} else {
path = trimMappingPath(path)
path = strings.Clone(path)
}
vaddr := libpf.HexToUint64(addrs[0])
mappings = append(mappings, Mapping{
Vaddr: vaddr,
Length: libpf.HexToUint64(addrs[1]) - vaddr,
Flags: flags,
FileOffset: libpf.HexToUint64(fields[2]),
Device: device,
Inode: inode,
Path: path,
})
}
return mappings, scanner.Err()
}
// GetMappings will process the mappings file from proc. Additionally,
// a reverse map from mapping filename to a Mapping node is built to allow
// OpenELF opening ELF files using the corresponding proc map_files entry.
// WARNING: This implementation does not support calling GetMappings
// concurrently with itself, or with OpenELF.
func (sp *systemProcess) GetMappings() ([]Mapping, error) {
mapsFile, err := os.Open(fmt.Sprintf("/proc/%d/maps", sp.pid))
if err != nil {
return nil, err
}
defer mapsFile.Close()
mappings, err := parseMappings(mapsFile)
if err == nil {
fileToMapping := make(map[string]*Mapping)
for idx := range mappings {
m := &mappings[idx]
if m.Inode != 0 {
fileToMapping[m.Path] = m
}
}
sp.fileToMapping = fileToMapping
}
return mappings, err
}
func (sp *systemProcess) GetThreads() ([]ThreadInfo, error) {
return nil, errors.New("not implemented")
}
func (sp *systemProcess) Close() error {
return nil
}
func (sp *systemProcess) GetRemoteMemory() remotememory.RemoteMemory {
return sp.remoteMemory
}
func (sp *systemProcess) extractMapping(m *Mapping) (*bytes.Reader, error) {
data := make([]byte, m.Length)
_, err := sp.remoteMemory.ReadAt(data, int64(m.Vaddr))
if err != nil {
return nil, fmt.Errorf("unable to extract mapping at %#x from PID %d",
m.Vaddr, sp.pid)
}
return bytes.NewReader(data), nil
}
func (sp *systemProcess) OpenMappingFile(m *Mapping) (ReadAtCloser, error) {
filename := sp.GetMappingFile(m)
if filename == "" {
return nil, fmt.Errorf("no backing file for anonymous memory")
}
return os.Open(filename)
}
func (sp *systemProcess) GetMappingFile(m *Mapping) string {
if m.IsAnonymous() {
return ""
}
return fmt.Sprintf("/proc/%v/map_files/%x-%x", sp.pid, m.Vaddr, m.Vaddr+m.Length)
}
// vdsoFileID caches the VDSO FileID. This assumes there is single instance of
// VDSO for the system.
var vdsoFileID libpf.FileID = libpf.UnsymbolizedFileID
func (sp *systemProcess) CalculateMappingFileID(m *Mapping) (libpf.FileID, error) {
if m.IsVDSO() {
if vdsoFileID != libpf.UnsymbolizedFileID {
return vdsoFileID, nil
}
vdso, err := sp.extractMapping(m)
if err != nil {
return libpf.FileID{}, fmt.Errorf("failed to extract VDSO: %v", err)
}
vdsoFileID, err = pfelf.CalculateIDFromReader(vdso)
return vdsoFileID, err
}
return pfelf.CalculateID(sp.GetMappingFile(m))
}
func (sp *systemProcess) OpenELF(file string) (*pfelf.File, error) {
// First attempt to open via map_files as it can open deleted files.
if m, ok := sp.fileToMapping[file]; ok {
if m.IsVDSO() {
vdso, err := sp.extractMapping(m)
if err != nil {
return nil, fmt.Errorf("failed to extract VDSO: %v", err)
}
return pfelf.NewFile(vdso, 0, false)
}
ef, err := pfelf.Open(sp.GetMappingFile(m))
if err == nil {
return ef, nil
}
}
// Fall back to opening the file using the process specific root
return pfelf.Open(fmt.Sprintf("/proc/%v/root/%s", sp.pid, file))
}