libclues
Linux C++ Tracing Library
Loading...
Searching...
No Matches
clues::Engine Class Reference

Main class for configuring and running libclues. More...

#include <Engine.hxx>

Public Types

enum class  FormatFlag : uint64_t { FD_INFO = 1 }
 
using FormatFlags = cosmos::BitMask<FormatFlag>
 

Public Member Functions

 Engine (EventConsumer &consumer)
 Creates a new Engine that reports events to consumer.
 
virtual ~Engine ()
 Tear down any tracees.
 
TraceePtr addTracee (const cosmos::ProcessID pid, const FollowChildren follow_children, const AttachThreads attach_threads, const cosmos::ProcessID sibling=cosmos::ProcessID::INVALID)
 Add the given pid as tracee.
 
TraceePtr addTracee (const cosmos::StringVector &cmdline, const FollowChildren follow_children)
 Create a new child process to be traced.
 
void trace ()
 Enter the tracing main loop and process tracing events.
 
void stop (const std::optional< cosmos::Signal > signal)
 Stop tracing any active tracees.
 
FormatFlags formatFlags () const
 
void setFormatFlags (const FormatFlags flags)
 Change formatting behaviour for system calls.
 

Protected Types

enum class  Decision { RETRY , DROP , STORE , DONE }
 Different decisions what to do with ptrace events. More...
 
using TraceeMap = std::map<cosmos::ProcessID, TraceePtr>
 
using EventMap = std::map<cosmos::ProcessID, cosmos::ChildState>
 

Protected Member Functions

void checkCleanupTracee (TraceeMap::iterator it)
 
void checkUnknownEvents ()
 
Decision handleEvent (const cosmos::ChildState &data)
 
void handleNoChildren ()
 
Decision checkUnknownTraceeEvent (const cosmos::ChildState &data)
 Check the given trace event if we can make sense of it.
 
bool tryUpdateTraceePID (const cosmos::ProcessID old_pid, const cosmos::ProcessID new_pid)
 
void handleAutoAttach (Tracee &parent, const cosmos::ProcessID pid, const cosmos::ptrace::Event event, const SystemCall &sc)
 Invoked by a Tracee once a new child process is auto-attached.
 
TraceePtr handleSubstitution (const cosmos::ProcessID old_pid)
 Invoked by a Tracee when multi-threaded execve() leads to substitution of a PID by another.
 

Protected Attributes

TraceeMap m_tracees
 Currently active tracees.
 
EventMap m_unknown_events
 Unknown ptrace events stored for later processing.
 
cosmos::ProcessID m_newly_attached_pid = cosmos::ProcessID::INVALID
 The PID of a newly auto-attached Tracee, if any.
 
EventConsumerm_consumer
 
FormatFlags m_format_flags
 Format settings for all tracees attached to this engine.
 

Friends

class Tracee
 

Detailed Description

Main class for configuring and running libclues.

This is the main class for configuring Tracee's and actually performing tracing. The tracing main loop is implemented here and callbacks are delivered to the EventConsumer interface.

Note that this class uses the wait() family of system calls to keep track of trace events. This API consumes process-global events, which means that there will be problems if other components in the same process deal with child processes, like:

  • extra events will be seen that libclues / the other component is not prepared for.
  • events that are expected by libclues can be lost to the other component.

For this reason it is simply not sensibly possible to create regular child process in parallel to tracing.

Definition at line 45 of file Engine.hxx.

Member Typedef Documentation

◆ EventMap

using clues::Engine::EventMap = std::map<cosmos::ProcessID, cosmos::ChildState>
protected

Definition at line 171 of file Engine.hxx.

◆ FormatFlags

using clues::Engine::FormatFlags = cosmos::BitMask<FormatFlag>

Definition at line 53 of file Engine.hxx.

◆ TraceeMap

using clues::Engine::TraceeMap = std::map<cosmos::ProcessID, TraceePtr>
protected

Definition at line 169 of file Engine.hxx.

Member Enumeration Documentation

◆ Decision

enum class clues::Engine::Decision
strongprotected

Different decisions what to do with ptrace events.

Enumerator
RETRY 

retry processing the event.

DROP 

ignore/drop the event.

STORE 

store the event for later.

DONE 

the event has been successfully processed.

Definition at line 174 of file Engine.hxx.

174 {
175 RETRY,
176 DROP,
177 STORE,
178 DONE,
179 };

◆ FormatFlag

enum class clues::Engine::FormatFlag : uint64_t
strong
Enumerator
FD_INFO 

print detailed file descriptor information

Definition at line 49 of file Engine.hxx.

49 : uint64_t {
50 FD_INFO = 1,
51 };

Constructor & Destructor Documentation

◆ Engine()

clues::Engine::Engine ( EventConsumer & consumer)
inlineexplicit

Creates a new Engine that reports events to consumer.

Definition at line 58 of file Engine.hxx.

58 :
59 m_consumer{consumer} {
60 }

◆ ~Engine()

clues::Engine::~Engine ( )
virtual

Tear down any tracees.

If tracees are still present then they will be detached from via stop(cosmos::signal::KILL).

Definition at line 18 of file Engine.cxx.

18 {
19 if (!m_tracees.empty()) {
20 try {
21 stop(cosmos::signal::KILL);
22 trace();
23 } catch (const std::exception &ex) {
24 LOG_WARN("Trying to stop remaining tracess in ~Engine():" << ex.what());
25
26 if (!m_tracees.empty()) {
27 LOG_ERROR("Failed to cleanup Engine");
28 std::abort();
29 }
30 }
31 }
32
33 for (auto &pair: m_unknown_events) {
34 LOG_WARN("Unknown event for PID " << cosmos::to_integral(pair.first) << " left unprocessed");
35 }
36}
TraceeMap m_tracees
Currently active tracees.
Definition Engine.hxx:218
EventMap m_unknown_events
Unknown ptrace events stored for later processing.
Definition Engine.hxx:220
void stop(const std::optional< cosmos::Signal > signal)
Stop tracing any active tracees.
Definition Engine.cxx:249
void trace()
Enter the tracing main loop and process tracing events.
Definition Engine.cxx:119

Member Function Documentation

◆ addTracee() [1/2]

TraceePtr clues::Engine::addTracee ( const cosmos::ProcessID pid,
const FollowChildren follow_children,
const AttachThreads attach_threads,
const cosmos::ProcessID sibling = cosmos::ProcessID::INVALID )

Add the given pid as tracee.

For tracing unrelated processes the current process needs to have sufficient privileges. This is usually the case if you are root or if the target process has the same credentials as the current process.

The Linux Yama kernel security module can further restrict tracing of unrelated processes. Check the /proc/sys/kernel/yama/ptrace_scope file for more information.

If attaching fails then this call immediately throws an exception.

On success the target process will be interrupted to determine its current state. Any state can be encountered and the caller needs to be prepared for any kind of tracing event (signal, system call, process exit, ...) to occur as a result.

follow_children determines whether newly created child processes will automatically be attached.

See also
Tracee::attach().

attach_threads determines whether other threads of the target process should automatically be attached, too.

sibling“, if set, refers to an existing Tracee that belongs to the same process aspid`. The tracees will then share the same ProcessData.

Definition at line 38 of file Engine.cxx.

39 {
40 TraceePtr sibling_ptr;
41 if (auto it = m_tracees.find(sibling); it != m_tracees.end()) {
42 sibling_ptr = it->second;
43 }
44 auto tracee = std::make_shared<ForeignTracee>(*this, m_consumer, sibling_ptr);
45 tracee->configure(pid);
46 tracee->attach(follow_children, attach_threads);
47 m_tracees[pid] = tracee;
48 return tracee;
49}

◆ addTracee() [2/2]

TraceePtr clues::Engine::addTracee ( const cosmos::StringVector & cmdline,
const FollowChildren follow_children )

Create a new child process to be traced.

Use this function to create a new child process which will be traced from the very beginning. The first tracing event observed will typically be a system call entry.

follow_children determines whether newly created child processes will automatically be attached.

See also
Tracee::attach().

The library will perform an initial check whether the executable specified in cmdline exists and is executable. If this is not the case, then a RuntimeError is thrown. There can be other errors trying to execute the new process that cannot be caught before the new process is forked. To catch these situations, it is best to observe the tracee's system calls: If it exits before a successful initial execve() system call occurred, then a pre execution error happened.

Definition at line 51 of file Engine.cxx.

51 {
52 auto tracee = std::make_shared<ChildTracee>(*this, m_consumer);
53 tracee->create(cmdline);
54 tracee->attach(follow_children);
55 m_tracees[tracee->pid()] = tracee;
56 return tracee;
57}

◆ checkCleanupTracee()

void clues::Engine::checkCleanupTracee ( TraceeMap::iterator it)
protected

Definition at line 59 of file Engine.cxx.

59 {
60 auto &tracee = *it->second;
61
62 if (tracee.state() == Tracee::State::DETACHED) {
63 if (!tracee.isChildProcess()) {
64 m_tracees.erase(it);
65 }
66 } else if (tracee.state() == Tracee::State::DEAD) {
67 tracee.doDetach();
68 m_tracees.erase(it);
69 }
70}
@ DETACHED
we already detached from the tracee
Definition Tracee.hxx:57
@ DEAD
the tracee no longer exists.
Definition Tracee.hxx:56

◆ checkUnknownEvents()

void clues::Engine::checkUnknownEvents ( )
protected

Definition at line 72 of file Engine.cxx.

72 {
73 if (m_newly_attached_pid == cosmos::ProcessID::INVALID)
74 return;
75
76 const auto new_pid = m_newly_attached_pid;
77 m_newly_attached_pid = cosmos::ProcessID::INVALID;
78
79 if (auto it = m_unknown_events.find(new_pid); it != m_unknown_events.end()) {
80 if (handleEvent(it->second) != Decision::DONE) {
81 // this should not cause any special outcomes anymore
82 LOG_WARN("delayed handling of unknown event yielded unexpected decision");
83 }
84 m_unknown_events.erase(it);
85 }
86}
@ DONE
the event has been successfully processed.
Definition Engine.hxx:178
cosmos::ProcessID m_newly_attached_pid
The PID of a newly auto-attached Tracee, if any.
Definition Engine.hxx:222

◆ checkUnknownTraceeEvent()

Engine::Decision clues::Engine::checkUnknownTraceeEvent ( const cosmos::ChildState & data)
protected

Check the given trace event if we can make sense of it.

If the function was able to adjust internal state for being able to properly handle data, then it returns true and a retry should be performed. Otherwise false is returned and the event should be discarded.

Definition at line 192 of file Engine.cxx.

192 {
193
194 if (data.trapped() && data.signal->isPtraceEventStop()) {
195 const auto [_, event] = cosmos::ptrace::decode_event(*data.signal);
196 if (event == cosmos::ptrace::Event::EXEC) {
197 /*
198 * This means execve() happened in a multi-threaded
199 * process, but we're not tracing the main thread,
200 * only the exec()'ing thread.
201 *
202 * The exec()'ing thread now has become the main
203 * thread, changing PID personality. Try to recover.
204 */
205 cosmos::Tracee ptrace{data.child.pid};
206 const auto former_pid = ptrace.getPIDEventMsg();
207 LOG_DEBUG("PID " << cosmos::to_integral(former_pid) << " issued execve(), but main thread is not traced. Trying to update records.");
208 if (tryUpdateTraceePID(former_pid, data.child.pid)) {
209 return Decision::RETRY;
210 }
211
212 return Decision::DROP;
213 } else if (event == cosmos::ptrace::Event::STOP) {
214 /*
215 * this can actually happen when an auto-attached
216 * child tracee is created and scheduled before the
217 * creating parent has had a chance to reports its
218 * PTRACE_EVENT_CLONE & friend event.
219 *
220 * Without knowing the relation of parent/child it's
221 * plain confusing to forwarding this to clients of
222 * Engine. Store the event and forward it once we see
223 * the creation event.
224 */
225 LOG_DEBUG("PID " << cosmos::to_integral(data.child.pid)
226 << " likely auto-attached tracee for which we didn't see the CLONE/[V]FORK event yet, storing event for later.");
227 return Decision::STORE;
228 }
229 }
230
231 return Decision::DROP;
232}
@ STORE
store the event for later.
Definition Engine.hxx:177
@ DROP
ignore/drop the event.
Definition Engine.hxx:176
@ RETRY
retry processing the event.
Definition Engine.hxx:175

◆ formatFlags()

FormatFlags clues::Engine::formatFlags ( ) const
inline

Definition at line 154 of file Engine.hxx.

154 {
155 return m_format_flags;
156 }
FormatFlags m_format_flags
Format settings for all tracees attached to this engine.
Definition Engine.hxx:225

◆ handleAutoAttach()

void clues::Engine::handleAutoAttach ( Tracee & parent,
const cosmos::ProcessID pid,
const cosmos::ptrace::Event event,
const SystemCall & sc )
protected

Invoked by a Tracee once a new child process is auto-attached.

pid provides the process ID of the new child process and event describes the event that triggered the creation of the new child. sc refers to the system call that lead to the creation of the new child, providing additional details of child properties.

Definition at line 263 of file Engine.cxx.

264 {
265
266 LOG_DEBUG("auto-attach for " << cosmos::to_integral(pid));
267
268 if (event != cosmos::ptrace::Event::VFORK_DONE) {
269 auto tracee = std::make_shared<AutoAttachedTracee>(
270 *this,
271 m_consumer,
272 m_tracees[parent.pid()]);
273
274 tracee->configure(pid, event, sc);
275
276 auto [it, _] = m_tracees.insert({pid, tracee});
277
278 EventConsumer::StatusFlags flags;
279 if (parent.hasClonedThread()) {
281 }
282 m_consumer.newChildProcess(parent, *it->second, event, flags);
283
284 checkCleanupTracee(it);
285 } else {
286 // if the pid is no longer found then it either already died
287 // or it was detached from
288 if (auto it = m_tracees.find(pid); it != m_tracees.end()) {
289 m_consumer.vforkComplete(parent, it->second);
290 } else {
291 m_consumer.vforkComplete(parent, nullptr);
292 }
293 }
294
295 /*
296 * If we have any unknown events stored, we can now check whether they
297 * have a matching object by now. Don't do this right away, because
298 * `parent` is still busy processing its event. Only do this after
299 * that is finished. This is the purpose of this flag.
300 */
302}
@ CLONED_THREAD
used in newChildProcess() to indicate that a new thread has been created.

◆ handleEvent()

Engine::Decision clues::Engine::handleEvent ( const cosmos::ChildState & data)
protected

Definition at line 157 of file Engine.cxx.

157 {
158 if (auto it = m_tracees.find(data.child.pid); it != m_tracees.end()) {
159 Tracee &tracee = *it->second;
160 try {
161 tracee.processEvent(data);
162 } catch (const cosmos::ApiError &ex) {
163 auto pid = cosmos::to_integral(tracee.pid());
164 if (ex.errnum() == cosmos::Errno::SEARCH) {
165 /*
166 * this can happen when the process was killed in the
167 * meantime, or in a multi-threaded process when
168 * another thread called execve() in parallel.
169 *
170 * we _should_ still receive an exit notification
171 * about the tracee, and the consumer can then detect
172 * that the system call was interrupted (actually if
173 * this is a system call exit event, then it wasn't
174 * interrupted, but we couldn't finish tracing it.
175 * Kind of a small loophole).
176 */
177 LOG_INFO("tracee " << pid << " disappeared");
178 } else {
179 // something more severe
180 LOG_ERROR("tracee " << pid << " handling process event failed: " << ex.what());
181 }
182 }
183
184 checkCleanupTracee(it);
185 checkUnknownEvents();
186 return Decision::DONE;
187 } else {
188 return checkUnknownTraceeEvent(data);
189 }
190}
Decision checkUnknownTraceeEvent(const cosmos::ChildState &data)
Check the given trace event if we can make sense of it.
Definition Engine.cxx:192

◆ handleNoChildren()

void clues::Engine::handleNoChildren ( )
protected

Definition at line 88 of file Engine.cxx.

88 {
89 for (auto it = m_tracees.begin(); it != m_tracees.end(); it++) {
90 auto &tracee = it->second;
91
92 if (tracee->flags()[Tracee::Flag::WAIT_FOR_EXITED]) {
93 /*
94 * This can be observed with the main thread when
95 * another thread (which isn't traced) calls execve().
96 *
97 * For this situation no EXITED wait() status is
98 * reported for the main thread, only PTHREAD_EXIT is
99 * seen. After that we can ECHLD with wait() and ESRCH
100 * with ptrace().
101 */
102 LOG_DEBUG("Tracee "
103 << cosmos::to_integral(tracee->pid())
104 << " likely disappeared because of execve() in another thread");
105 } else {
106 LOG_WARN("Tracee " << cosmos::to_integral(tracee->pid())
107 << " suddenly lost?!");
108 }
109
110 // actually we already seem to be detached, but this function
111 // also takes care of this case and properly resets object
112 // state
113 tracee->detach();
114
115 checkCleanupTracee(it);
116 }
117}
@ WAIT_FOR_EXITED
we've already seen PTHREAD_EVENT_EXIT but are still waiting for CLD_EXITED.
Definition Tracee.hxx:67

◆ handleSubstitution()

TraceePtr clues::Engine::handleSubstitution ( const cosmos::ProcessID old_pid)
protected

Invoked by a Tracee when multi-threaded execve() leads to substitution of a PID by another.

Definition at line 304 of file Engine.cxx.

304 {
305 /*
306 * The following scenarios exist for multi-threaded processes:
307 *
308 * a) we are tracing all threads of the process. Some thread calls
309 * execve(). We'll see all other threads exiting out of the blue. The
310 * main thread's Tracee object will set the
311 * WAIT_FOR_EXECVE_REPLACEMENT flag. Once the PID personality change
312 * happens we'll recycle the main thread's Tracee object to be used
313 * for further tracing. The reason for this is that the Tracee object
314 * may be a ChildTracee with ownership of a SubProc that needs to live
315 * on.
316 *
317 * b) we are only tracing a thread other than the main thread or the
318 * execve() thread: it will just exit and tracing ends
319 *
320 * c) we are only tracing the execve() thread which is not the main
321 * thread. Upon PID substitution we'll suddenly get a ptrace()
322 * event for a PID we never attached to. Engine decodes the event in
323 * `checkUnknownTraceeEvent()` and will update the key in `m_tracees`,
324 * then feed the event to the Tracee object. In this case the only
325 * available Tracee object will be kept.
326 *
327 * d) we are tracing only the main thread but another thread calls
328 * execve(): the thread disappears and `wait()` suddenly fails
329 * with ENOCHILD.
330 *
331 * What does strace do in these cases?
332 *
333 * - in case c) it sees the ptrace event for the changed PID, then
334 * detaches from it stating it is an unknown PID. Then tracing ends.
335 * - in case d) it fails with ENOCHILD and tracing ends.
336 *
337 * Technically is is possible to deal with case c), which is complex
338 * but managable. It makes sense to continue tracing in this case.
339 *
340 * In case d) it could be argued that is also makes sense to continue
341 * tracing, since the main thread is traced but continues in another
342 * context. Technically it is not possible to do this, though, because
343 * we have no information at all about the execve() that is happening
344 * until we lose the tracee. Thus in this case we try to detach cleanly.
345 * Clients of libclues can identify the exit reason via
346 * Tracee::flags() by looking for Flag::WAIT_FOR_EXECVE_REPLACEMENT,
347 * which will still be set in this scenario.
348 */
349
350 auto it = m_tracees.find(old_pid);
351
352 if (it == m_tracees.end()) {
353 // this is likely case c), the `old_pid` was not traced by us.
354 return nullptr;
355 }
356
357 auto &old_tracee = *it->second;
358
359 try {
360 old_tracee.detach();
361 } catch (const cosmos::ApiError &err) {
362 // it seems the kernel implicitly detaches at this point
363 if (err.errnum() != cosmos::Errno::SEARCH) {
364 throw;
365 }
366 }
367
368 auto ret = it->second;
369
370 m_tracees.erase(it);
371
372 /* The man page says at this stage we should forget about any other
373 * treads of the process that still exist.
374 * Is this really necessary? It also says that it is guaranteed that
375 * only two threads will still exist now: the main and the execve()
376 * thread. So forgetting about any other threads would only be
377 * relevant for the non-execve() thread that still remains. We already
378 * deal successfully with all the constellations that can occur, so I
379 * don't think there's any explicit "forgetting" to be implemented
380 * here.
381 */
382
383 return ret;
384}

◆ setFormatFlags()

void clues::Engine::setFormatFlags ( const FormatFlags flags)
inline

Change formatting behaviour for system calls.

These flags influences the implementation of SystemCallItem::str(). See FormatFlag for details.

Definition at line 163 of file Engine.hxx.

163 {
164 m_format_flags = flags;
165 }

◆ stop()

void clues::Engine::stop ( const std::optional< cosmos::Signal > signal)

Stop tracing any active tracees.

If you want to stop a running trace then you can call this function to initiate detach from all active tracees. Each tracee will either be immediately be detached from, if its current state allows this, or it will be interrupted to detach from it during the next ptrace event.

If new child processes have been created for tracing then signal determines how they will be dealt with:

  • std::nullopt means that they will be waited for, without sending a signal. This can mean that it will take a long time before the child processes terminate and the trace() call can return.
  • Otherwise signal denotes the signal that will be sent to direct children to have them terminate in a timely fashion. cosmos::signal::KILL will be the safest way to tear down any remaining child processes.

After calling this function an active trace() invocation should be returning soon. Further trace events may be reported to the EventConsumer interface until the detachment process is complete.

Definition at line 249 of file Engine.cxx.

249 {
250 for (auto it = m_tracees.begin(); it != m_tracees.end(); it++) {
251 auto &tracee = *it->second;
252 if (tracee.isChildProcess() && tracee.alive() && signal) {
253 cosmos::signal::send(tracee.pid(), *signal);
254 }
255 tracee.detach();
256
257 if (!tracee.alive()) {
258 m_tracees.erase(it);
259 }
260 }
261}

◆ trace()

void clues::Engine::trace ( )

Enter the tracing main loop and process tracing events.

At least one tracee must have been added via addTracee() for this call to do anything. The call will block and deliver tracing events to the given consumer interface until all of the tracees have exited or have been detached from.

Definition at line 119 of file Engine.cxx.

119 {
120 cosmos::ChildState data;
121
122 while (!m_tracees.empty()) {
123 try {
124 data = *cosmos::proc::wait(cosmos::WaitFlags{
125 cosmos::WaitFlag::WAIT_FOR_EXITED,
126 cosmos::WaitFlag::WAIT_FOR_STOPPED});
127 } catch (const cosmos::ApiError &ex) {
128 if (ex.errnum() == cosmos::Errno::NO_CHILD) {
129 handleNoChildren();
130 return;
131 }
132
133 throw;
134 }
135
136 while (true) {
137 if (const auto decision = handleEvent(data); decision == Decision::RETRY) {
138 // retry if we can process the event the next time.
139 continue;
140 } else if (decision == Decision::STORE) {
141 const auto res = m_unknown_events.insert(
142 std::make_pair(data.child.pid, std::move(data)));
143 if (!res.second) {
144 // we only expect one unknown event to appear per PID (PTRACE_EVENT_STOP)
145 LOG_WARN("additional unknown trace event for PID " <<
146 cosmos::to_integral(data.child.pid));
147 }
148 } else if (decision == Decision::DROP) {
149 LOG_WARN("received unknown trace event " << format::event(data));
150 }
151
152 break;
153 }
154 }
155}

◆ tryUpdateTraceePID()

bool clues::Engine::tryUpdateTraceePID ( const cosmos::ProcessID old_pid,
const cosmos::ProcessID new_pid )
protected

Definition at line 234 of file Engine.cxx.

234 {
235 auto node = m_tracees.extract(old_pid);
236 if (node.empty())
237 return false;
238 // don't change the Tracee object's PID just yet. This will be done
239 // in Tracee::handleExecEvent()).
240 //
241 // At this stage we just want to be able to lookup the correct Tracee
242 // within Engine for now.
243 node.key() = new_pid;
244
245 m_tracees.insert(std::move(node));
246 return true;
247}

Friends And Related Symbol Documentation

◆ Tracee

friend class Tracee
friend

Definition at line 46 of file Engine.hxx.

Member Data Documentation

◆ m_consumer

EventConsumer& clues::Engine::m_consumer
protected

Definition at line 223 of file Engine.hxx.

◆ m_format_flags

FormatFlags clues::Engine::m_format_flags
protected

Format settings for all tracees attached to this engine.

Definition at line 225 of file Engine.hxx.

◆ m_newly_attached_pid

cosmos::ProcessID clues::Engine::m_newly_attached_pid = cosmos::ProcessID::INVALID
protected

The PID of a newly auto-attached Tracee, if any.

Definition at line 222 of file Engine.hxx.

◆ m_tracees

TraceeMap clues::Engine::m_tracees
protected

Currently active tracees.

Definition at line 218 of file Engine.hxx.

◆ m_unknown_events

EventMap clues::Engine::m_unknown_events
protected

Unknown ptrace events stored for later processing.

Definition at line 220 of file Engine.hxx.


The documentation for this class was generated from the following files: