libclues
Linux C++ Tracing Library
Loading...
Searching...
No Matches
utils.cxx
1// C++
2#include <iostream>
3#include <fstream>
4
5// clues
6#include <clues/logger.hxx>
7#include <clues/sysnrs/generic.hxx>
8#include <clues/utils.hxx>
9
10// generated
11#include <clues/errnodb.hxx>
12#include <clues/Tracee.hxx>
13
14// cosmos
15#include <cosmos/error/ApiError.hxx>
16#include <cosmos/error/RuntimeError.hxx>
17#include <cosmos/formatting.hxx>
18#include <cosmos/fs/DirStream.hxx>
19#include <cosmos/fs/File.hxx>
20#include <cosmos/fs/filesystem.hxx>
21#include <cosmos/io/ILogger.hxx>
22#include <cosmos/io/StreamAdaptor.hxx>
23#include <cosmos/proc/process.hxx>
24#include <cosmos/string.hxx>
25#include <cosmos/utils.hxx>
26
27namespace clues {
28
29const char* get_errno_label(const cosmos::Errno err) {
30 const auto num = cosmos::to_integral(err);
31 if (num < 0 || static_cast<size_t>(num) >= ERRNO_NAMES.size())
32 return "E<INVALID>";
33
34 return ERRNO_NAMES[num];
35}
36
37const char* get_kernel_errno_label(const KernelErrno err) {
38 switch (err) {
39 case KernelErrno::RESTART_SYS: return "RESTART_SYS";
40 case KernelErrno::RESTART_NOINTR: return "RESTART_NOINTR";
41 case KernelErrno::RESTART_NOHAND: return "RESTART_NOHAND";
42 case KernelErrno::RESTART_RESTARTBLOCK: return "RESTART_RESTARTBLOCK";
43 default: return "?INVALID?";
44 }
45}
46
47const char* get_ptrace_event_str(const cosmos::ptrace::Event event) {
48 using Event = cosmos::ptrace::Event;
49 switch (event) {
50 case Event::VFORK: return "VFORK";
51 case Event::FORK: return "FORK";
52 case Event::CLONE: return "CLONE";
53 case Event::VFORK_DONE: return "VFORK_DONE";
54 case Event::EXEC: return "EXEC";
55 case Event::EXIT: return "EXIT";
56 case Event::STOP: return "STOP";
57 case Event::SECCOMP: return "SECCOMP";
58 default: return "??? unknown ???";
59 }
60}
61
62std::set<cosmos::FileNum> get_currently_open_fds(const cosmos::ProcessID pid) {
63 std::set<cosmos::FileNum> ret;
64
65 try {
66 cosmos::DirStream dir{
67 cosmos::sprintf("/proc/%d/fd", cosmos::to_integral(pid))};
68
69 for (const auto &entry: dir) {
70 if (entry.isDotEntry())
71 continue;
72
73 try {
74 auto fd = std::stoi(entry.name());
75 ret.insert(cosmos::FileNum{fd});
76 } catch (...) {
77 // not a number? shouldn't happen.
78 }
79 }
80 } catch (const cosmos::ApiError &ex) {
81 // process disappeared? let the caller handle that.
82 throw;
83 }
84
85 return ret;
86}
87
88namespace {
89
90FDInfo::Type to_fd_info_type(const std::string &type) {
91 using Type = FDInfo::Type;
92 if (type == "pipe")
93 return Type::PIPE;
94 else if (type == "socket")
95 return Type::SOCKET;
96 else if (type == "eventfd")
97 return Type::EVENT_FD;
98 else if (type == "eventpoll")
99 return Type::EPOLL;
100 else if (type == "pidfd")
101 return Type::PID_FD;
102 else if (type == "signalfd")
103 return Type::SIGNAL_FD;
104 else if (type == "timerfd")
105 return Type::TIMER_FD;
106 else if (type == "inotify")
107 return Type::INOTIFY;
108 else if (type == "bpf-map")
109 return Type::BPF_MAP;
110 else if (type == "bpf-prog")
111 return Type::BPF_PROG;
112 else if (type == "perf_event")
113 return Type::PERF_EVENT;
114
115 // NOTE: there might exist various other, exotic anon_inodes
116 LOG_WARN("Unknown /proc/<pid>/fd file type '" << type << "' encountered");
117 return Type::UNKNOWN;
118}
119
121
125bool parse_type_info(const std::string &type_info, FDInfo &info) {
126
127 if (!type_info.empty() && type_info[0] == '/') {
128 // this means it is a regular path
129 info.path = type_info;
130 info.type = FDInfo::Type::FS_PATH;
131 // for regular paths the inode information is not readily
132 // available, and we don't want to `stat()` just for this now.
133 info.inode.reset();
134 return true;
135 }
136
137 // see `man proc_pid_fd` for details about what to expect in `type_info`
138 auto parts = cosmos::split(type_info, ":", cosmos::SplitFlags{}, 1);
139 if (parts.size() != 2) {
140 return false;
141 }
142
143 if (auto type = parts[0]; type == "anon_inode") {
144 // format is "anon_inode:[<type>]
145 // where the square brackets are not always present
146 auto real_type = cosmos::stripped(parts[1], "[]");
147 info.type = to_fd_info_type(real_type);
148 } else {
149 info.type = to_fd_info_type(type);
150 // format is "<type>:[<inode>]"
151 const auto inode_str = cosmos::stripped(parts[1], "[]");
152
153 try {
154 auto inode = std::stoul(inode_str);
155 info.inode = cosmos::Inode{inode};
156 } catch (...) {
157 // not a number? shouldn't happen.
158 LOG_WARN(__FUNCTION__ << ": non-numerical inode \""
159 << inode_str << "\"?!");
160 return false;
161 }
162 }
163
164 return true;
165}
166
168
174void parse_fd_info(const cosmos::ProcessID pid, FDInfo &info) {
175 cosmos::File fdinfo{
176 cosmos::sprintf("/proc/%d/fdinfo/%d",
177 cosmos::to_integral(pid),
178 cosmos::to_integral(info.fd)),
179 cosmos::OpenMode::READ_ONLY
180 };
181
182 cosmos::InputStreamAdaptor fdinfo_stream{fdinfo};
183
184 std::string line;
185 while (std::getline(fdinfo_stream, line).good()) {
186 auto parts = cosmos::split(line, ":",
187 cosmos::SplitFlag::STRIP_PARTS,
188 1);
189 if (parts.size() != 2)
190 continue;
191 else if (parts[0] != "flags")
192 continue;
193
194 // the flags are in octal. potential exception is handled by caller
195 auto raw_flags = std::stoi(parts[1], nullptr, 8);
196 const cosmos::OpenMode mode{
197 raw_flags & (O_RDONLY|O_WRONLY|O_RDWR)};
198 const cosmos::OpenFlags flags{
199 raw_flags & ~(cosmos::to_integral(mode))};
200 info.mode = mode;
201 info.flags = flags;
202 return;
203 }
204
205 throw cosmos::RuntimeError{"no flags found"};
206}
207
208} // end anon ns
209
210std::vector<FDInfo> get_fd_infos(const cosmos::ProcessID pid) {
211 std::vector<FDInfo> ret;
212 cosmos::DirStream dir;
213 const auto proc_pid_fd = cosmos::sprintf("/proc/%d/fd",
214 cosmos::to_integral(pid));
215
216 try {
217 dir.open(proc_pid_fd);
218 } catch (const cosmos::ApiError &ex) {
219 // process disappeared? let caller handle this
220 throw;
221 }
222
223 for (const auto &entry: dir) {
224 if (entry.isDotEntry())
225 continue;
226
227 FDInfo info;
228
229 try {
230 auto fd = std::stoi(entry.name());
231 info.fd = cosmos::FileNum{fd};
232 } catch (...) {
233 // not a number? shouldn't happen.
234 LOG_WARN(__FUNCTION__ << ": " << proc_pid_fd
235 << " non-numerical entry "
236 << entry.name() << "?!");
237 continue;
238 }
239
240 try {
241 auto type_info = cosmos::fs::read_symlink_at(
242 dir.fd(),
243 entry.name());
244
245 if (!parse_type_info(type_info, info)) {
246 LOG_WARN(__FUNCTION__ << ": " << proc_pid_fd
247 << " failed to parse type info \""
248 << type_info << "\"");
249 continue;
250 }
251 } catch (const std::exception &ex) {
252 LOG_WARN(__FUNCTION__ << ": " << proc_pid_fd
253 << " failed to read symlink: "
254 << ex.what());
255 }
256
257 try {
258 parse_fd_info(pid, info);
259 } catch (const std::exception &ex) {
260 LOG_WARN(__FUNCTION__ << ": " << proc_pid_fd
261 << " failed to read fdinfo: "
262 << ex.what());
263 }
264
265 ret.push_back(info);
266 }
267
268 return ret;
269}
270
271std::optional<SystemCallNr> lookup_system_call(const std::string_view name) {
272 auto it = SYSTEM_CALL_NAME_MAP.find(name);
273
274 if (it == SYSTEM_CALL_NAME_MAP.end())
275 return {};
276
277 return it->second;
278}
279
280bool is_default_abi(const ABI abi) {
281 switch (abi) {
282 case ABI::X86_64: return cosmos::arch::X86_64;
283 case ABI::I386: return cosmos::arch::I386;
284 case ABI::AARCH64: return cosmos::arch::AARCH64;
285 default: return false;
286 }
287}
288
289const char* get_abi_label(const ABI abi) {
290 switch (abi) {
291 case ABI::X86_64: return "x86_64";
292 case ABI::I386: return "i386";
293 case ABI::X32: return "x32";
294 case ABI::AARCH64: return "aarch64";
295 default: return "unknown";
296 }
297}
298
299std::array<ABI, SUPPORTED_ABIS> get_supported_abis() {
300#ifdef COSMOS_X86_64
301 /*
302 * all three can be supported at once, although X32 has
303 * declined a lot in popularity, or never had it.
304 */
305 return {ABI::I386, ABI::X86_64, ABI::X32};
306#elif defined(COSMOS_I386)
307 return {ABI::I386};
308#elif defined(COSMOS_AARCH64)
309 /*
310 * In theory aarch64 also support running aarch32 emulation
311 * binaries. In practice this is hardly supported by any Linux
312 * distributions beyond some niche systems. Should this ever
313 * become relevant, we can extend accordingly, when there's
314 * been a chance to test it properly.
315 */
316 return {ABI::AARCH64};
317#else
318 throw cosmos::RuntimeError{"no ABIs supported on this platform?!"};
319#endif
320}
321
322void parse_proc_file(const cosmos::ProcessID pid, const std::string_view subpath,
323 std::function<bool(const std::string&)> parser) {
324 const auto path = cosmos::proc::build_proc_path(pid, subpath);
325
326 std::ifstream fs{path};
327
328 if (!fs) {
329 throw cosmos::RuntimeError{"failed to open proc file"};
330 }
331
332 std::string line;
333
334 while (std::getline(fs, line).good()) {
335 if (parser(line)) {
336 break;
337 }
338 }
339
340 if (fs.fail()) {
341 throw cosmos::RuntimeError{"failed to read proc file"};
342 }
343}
344
345void parse_proc_file(const Tracee &tracee, const std::string_view subpath, std::function<bool(const std::string&)> parser) {
346 return parse_proc_file(tracee.pid(), subpath, parser);
347}
348
349} // end ns
Base class for traced processes.
Definition Tracee.hxx:39
KernelErrno
Errno values that can appear in tracing context.
Definition types.hxx:49
const char * get_ptrace_event_str(const cosmos::ptrace::Event event)
Returns a string label for the given event.
Definition utils.cxx:47
ABI
System Call ABI.
Definition types.hxx:62
@ X32
X86_64 with 32-bit pointers.
Definition types.hxx:66
std::vector< FDInfo > get_fd_infos(const cosmos::ProcessID pid)
Obtain detailed information about currently open file descriptors according to /proc/<pid>/fd.
Definition utils.cxx:210
std::array< ABI, SUPPORTED_ABIS > get_supported_abis()
Returns a list of ABIs supported on the current platform.
Definition utils.cxx:299
std::optional< SystemCallNr > lookup_system_call(const std::string_view name)
Returns the SystemCallNr for the given system call name, if it exists.
Definition utils.cxx:271
const char * get_errno_label(const cosmos::Errno err)
Returns a short errno label like ENOENT for the given errno integer.
Definition utils.cxx:29
const char * get_kernel_errno_label(const KernelErrno err)
Returns a short errno label for extended KernelErrno codes.
Definition utils.cxx:37
std::set< cosmos::FileNum > get_currently_open_fds(const cosmos::ProcessID pid)
Returns the currently open file descriptors according to /proc/<pid>/fd.
Definition utils.cxx:62
void parse_proc_file(const cosmos::ProcessID pid, const std::string_view subpath, std::function< bool(const std::string &)> parser)
Parse a proc file of the given process using the given functor.
Definition utils.cxx:322
bool is_default_abi(const ABI abi)
Returns whether the given ABI is default ABI for this system.
Definition utils.cxx:280
Contextual information about a file descriptor in a Tracee.
Definition types.hxx:75
cosmos::FileNum fd
the actual file descriptor number.
Definition types.hxx:118
Type
Different types of file descriptors.
Definition types.hxx:86
@ FS_PATH
a path opened on the file system (this can still be a device special file, named pipe,...
Definition types.hxx:88