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

Base class for traced processes. More...

#include <Tracee.hxx>

+ Inheritance diagram for clues::Tracee:

Public Types

enum class  State {
  UNKNOWN , RUNNING , SYSCALL_ENTER_STOP , SYSCALL_EXIT_STOP ,
  SIGNAL_DELIVERY_STOP , GROUP_STOP , EVENT_STOP , DEAD ,
  DETACHED
}
 Current tracing state for a single tracee. More...
 
enum class  Flag {
  WAIT_FOR_ATTACH_STOP = 1 << 0 , DETACH_AT_NEXT_STOP = 1 << 1 , INJECTED_SIGSTOP = 1 << 2 , INJECTED_SIGCONT = 1 << 3 ,
  SYSCALL_ENTERED = 1 << 4 , WAIT_FOR_EXITED = 1 << 5 , ATTACH_THREADS_PENDING = 1 << 6 , SEEN_SIGRETURN = 1 << 7
}
 Different flags reflecting the tracer status. More...
 
using Flags = cosmos::BitMask<Flag>
 

Public Member Functions

const std::string & executable () const
 
const cosmos::StringVector & cmdLine () const
 
const FDInfoMapfdInfoMap () const
 Provides access to the current knowledge about file descriptors in the tracee.
 
cosmos::ProcessID pid () const
 
bool alive () const
 
size_t maxBufferPrefetch () const
 
void setMaxBufferPrefetch (const size_t bytes)
 Sets an upper limit for the retrieval of variable-length buffer parameters in system calls.
 
size_t syscallCtr () const
 Returns the number of system calls observed so far.
 
std::optional< cosmos::Signal > stopSignal () const
 For state() == State::GROUP_STOP this returns the stopping signal that caused it.
 
State state () const
 
State prevState () const
 
bool isEnterStop () const
 
bool isExitStop () const
 
Flags flags () const
 
std::optional< cosmos::ChildState > exitData () const
 Returns possible tracee exit data.
 
virtual void attach (const FollowChildren follow_children, const AttachThreads attach_threads=AttachThreads{false})
 Logic to handle attaching to the tracee.
 
bool detach ()
 Attempt to detach the Tracee.
 
long getData (const ForeignPtr addr) const
 Reads a word of data from the tracee's memory.
 
void readString (const ForeignPtr addr, std::string &out) const
 Reads a zero-terminated C-string from the tracee.
 
void readBlob (const ForeignPtr addr, char *buffer, const size_t bytes) const
 Reads an arbitrary binary blob of fixed length from the tracee.
 
template<typename T, bool CHECK_TRIVIAL = true>
bool readStruct (const ForeignPtr addr, T &out) const
 Reads a system call struct from the tracee's address space into out.
 
template<typename VECTOR>
void readVector (const ForeignPtr pointer, VECTOR &out) const
 Reads in a zero terminated array of data items into the STL-vector like parameter out.
 
virtual bool isChildProcess () const
 Returns whether the tracee is a child process created by us.
 
bool isThreadGroupLeader () const
 Checks in the proc file system whether the Tracee is a thread group leader.
 
bool isInitiallyAttachedThread () const
 Indicates whether this Tracee is an automatically attached thread.
 
std::optional< SystemCallNrcurrentSystemCallNr () const
 Returns the number of the currently running system call, if any.
 
const std::optional< SystemCallInfo > & currentSystemCallInfo () const
 
const Engineengine () const
 

Static Public Member Functions

static const char * getStateLabel (const State state)
 

Protected Member Functions

 Tracee (Engine &engine, EventConsumer &consumer, TraceePtr sibling=nullptr)
 
void processEvent (const cosmos::ChildState &data)
 Process the given ptrace event.
 
void updateExecutable ()
 
void updateCmdLine ()
 
void updateExecInfo ()
 
void syncFDsAfterExec ()
 
void changeState (const State new_state)
 
void interrupt ()
 Forces the traced process to stop.
 
void restart (const cosmos::Tracee::RestartMode mode=cosmos::Tracee::RestartMode::CONT, const std::optional< cosmos::Signal > signal={})
 Restarts the traced process, optionally delivering signal.
 
void setOptions (const cosmos::ptrace::Opts opts)
 Applies the given trace flags.
 
void seize (const cosmos::ptrace::Opts opts)
 Makes the tracee a tracee.
 
void setPID (const cosmos::ProcessID tracee)
 Sets the tracee PID.
 
template<ABI abi>
void getRegisters (RegisterSet< abi > &rs)
 Reads the current register set from the process.
 
void handleSystemCall ()
 
void handleSystemCallEntry ()
 
void handleSystemCallExit ()
 
void handleStateMismatch ()
 
void handleSignal (const cosmos::SigInfo &info)
 
void handleEvent (const cosmos::ChildState &data, const cosmos::ptrace::Event event, const cosmos::Signal signal)
 
void handleStopEvent (const cosmos::Signal signal)
 
void handleExitEvent ()
 
void handleExecEvent (const cosmos::ProcessID main_pid)
 
void handleNewChildEvent (const cosmos::ptrace::Event event)
 
void handleAttached ()
 
void syncState (Tracee &other)
 
void attachThreads ()
 
template<typename FILLER>
void fillData (ForeignPtr addr, FILLER &filler) const
 Reads data from the Tracee starting at addr and feeds it to filler until it's saturated.
 
virtual void cleanupChild ()
 
void verifyArch ()
 Verifies the tracee's architecture according to m_syscall_info, throws on mismatch.
 
std::optional< SystemCallNrgetInitialSyscallNr (const ABI abi) const
 Returns the initial system call nr. stored in m_initial_regset, if available for abi.
 
void getInitialRegisters ()
 
void unshareProcessData ()
 
void doDetach ()
 Actually perform a detach without checking tracee state.
 
void handleError (const cosmos::ApiError &error)
 Handle ApiErrors raised from ptrace() requests.
 
bool hasClonedThread () const
 Returns whether the current/last seen system call was a clone() for a thread.
 
void trackFD (FDInfo &&info) const
 Track a new file descriptor for this Tracee's thread group.
 
void dropFD (const cosmos::FileNum fd) const
 Drop a file descriptor from the tracking of the Tracee's thread group.
 

Protected Attributes

Enginem_engine
 The engine that manages this tracee.
 
EventConsumerm_consumer
 Callback interface receiving our information.
 
State m_state = State::UNKNOWN
 The current state the tracee is in.
 
State m_prev_state = State::UNKNOWN
 The previous Tracee state we've seen (except RUNNING).
 
Flags m_flags
 These keep track of various state on the tracer side.
 
cosmos::Tracee m_ptrace
 libcosmos API for the Tracee.
 
cosmos::ptrace::Opts m_ptrace_opts
 The options we've set for ptrace().
 
std::optional< SystemCallInfom_syscall_info
 The current system call information, if any.
 
SystemCallDB m_syscall_db
 Reusable database object for tracing system calls.
 
SystemCallm_current_syscall = nullptr
 Holds state for the currently executing system call.
 
SystemCallm_interrupted_syscall = nullptr
 Previous system call, if it has been interrupted.
 
cosmos::Tracee::RestartMode m_restart_mode = cosmos::Tracee::RestartMode::CONT
 current RestartMode to use.
 
std::optional< cosmos::Signal > m_inject_sig
 signal to inject upon next restart of the tracee.
 
std::optional< cosmos::ChildState > m_exit_data
 If tracee exit was observed then this contains the final exit data.
 
std::optional< cosmos::ProcessID > m_initial_attacher
 If this Tracee was automatically attached due to AttachThreads{true} then this contains the ProcessID of the initial Thread that caused this.
 
size_t m_syscall_ctr = 0
 Number of system calls observed.
 
AnyRegisterSet m_initial_regset
 Register set observed during initial attach event stop.
 
std::optional< cosmos::Signal > m_stop_signal
 For State::GROUP_STOP this contains the signal that caused it.
 
ProcessDataPtr m_process_data
 Shared process data.
 
size_t m_max_buffer_prefetch = 128
 Number of bytes system calls will fetch for variable-length data buffers.
 

Static Protected Attributes

static constexpr std::array< cosmos::Signal, 4 > STOPPING_SIGNALS
 Array of signals that cause tracee stop.
 

Friends

class Engine
 
class SystemCall
 

Detailed Description

Base class for traced processes.

The concrete implementation of Tracee defines the means of how the traced process is attached to and detached from etc.

Definition at line 39 of file Tracee.hxx.

Member Typedef Documentation

◆ Flags

using clues::Tracee::Flags = cosmos::BitMask<Flag>

Definition at line 72 of file Tracee.hxx.

Member Enumeration Documentation

◆ Flag

enum class clues::Tracee::Flag
strong

Different flags reflecting the tracer status.

Enumerator
WAIT_FOR_ATTACH_STOP 

we're still waiting for the PTRACE_INTERRUPT event stop.

DETACH_AT_NEXT_STOP 

as soon as the tracee reaches a stop stace, detach from it.

INJECTED_SIGSTOP 

whether we've injected a SIGSTOP that needs to be undone.

INJECTED_SIGCONT 

whether we've injected a SIGCONT that needs to be ignored.

SYSCALL_ENTERED 

we've seen a syscall-enter-stop and are waiting for the corresponding exit-stop.

WAIT_FOR_EXITED 

we've already seen PTHREAD_EVENT_EXIT but are still waiting for CLD_EXITED.

ATTACH_THREADS_PENDING 

attach all threads of the process as soon as the initial event stop happens.

SEEN_SIGRETURN 

a sigreturn with a pending interrupted system call has been observed.

Definition at line 61 of file Tracee.hxx.

61 {
62 WAIT_FOR_ATTACH_STOP = 1 << 0,
63 DETACH_AT_NEXT_STOP = 1 << 1,
64 INJECTED_SIGSTOP = 1 << 2,
65 INJECTED_SIGCONT = 1 << 3,
66 SYSCALL_ENTERED = 1 << 4,
67 WAIT_FOR_EXITED = 1 << 5,
68 ATTACH_THREADS_PENDING = 1 << 6,
69 SEEN_SIGRETURN = 1 << 7,
70 };

◆ State

enum class clues::Tracee::State
strong

Current tracing state for a single tracee.

These states are modelled after the states described in man(2) ptrace.

Enumerator
UNKNOWN 

initial PTRACE_SIZE / PTRACE_INTERRUPT.

RUNNING 

tracee is running normally / not in a special trace state.

SYSCALL_ENTER_STOP 

system call started.

SYSCALL_EXIT_STOP 

system call finished.

SIGNAL_DELIVERY_STOP 

signal was delivered.

GROUP_STOP 

SIGSTOP executed, the tracee is stopped.

EVENT_STOP 

special ptrace event occurred.

DEAD 

the tracee no longer exists.

DETACHED 

we already detached from the tracee

Definition at line 48 of file Tracee.hxx.

48 {
49 UNKNOWN,
50 RUNNING,
51 SYSCALL_ENTER_STOP,
52 SYSCALL_EXIT_STOP,
53 SIGNAL_DELIVERY_STOP,
54 GROUP_STOP,
55 EVENT_STOP,
56 DEAD,
57 DETACHED,
58 };

Constructor & Destructor Documentation

◆ ~Tracee()

clues::Tracee::~Tracee ( )
virtual

Definition at line 108 of file Tracee.cxx.

108 {
110 LOG_WARN_PID("destroying Tracee in live state");
111 }
112}
@ DETACHED
we already detached from the tracee
Definition Tracee.hxx:57
@ DEAD
the tracee no longer exists.
Definition Tracee.hxx:56
State m_state
The current state the tracee is in.
Definition Tracee.hxx:438

◆ Tracee()

clues::Tracee::Tracee ( Engine & engine,
EventConsumer & consumer,
TraceePtr sibling = nullptr )
explicitprotected
Parameters
[in]siblingestablish a process data sharing relationship with `sibling. Do this only if you knows that both tracees are related (e.g. member of the same thread group).

Definition at line 100 of file Tracee.cxx.

100 :
101 m_engine{engine},
102 m_consumer{consumer},
103 m_process_data{sibling ?
104 sibling->m_process_data :
105 std::make_shared<ProcessData>()} {
106}
ProcessDataPtr m_process_data
Shared process data.
Definition Tracee.hxx:470
Engine & m_engine
The engine that manages this tracee.
Definition Tracee.hxx:434
EventConsumer & m_consumer
Callback interface receiving our information.
Definition Tracee.hxx:436

Member Function Documentation

◆ alive()

bool clues::Tracee::alive ( ) const
inline

Definition at line 95 of file Tracee.hxx.

95 {
96 return pid() != cosmos::ProcessID::INVALID;
97 }

◆ attach()

void clues::Tracee::attach ( const FollowChildren follow_children,
const AttachThreads attach_threads = AttachThreads{false} )
virtual

Logic to handle attaching to the tracee.

Parameters
[in]follow_childrenIf true then newly created child processes will automatically be attached. The EventConsumer interface will received a newChildProcess() callback once a new child process has been attached. This covers all ways by which new child processes can be created (fork, vfork, clone).

Definition at line 133 of file Tracee.cxx.

133 {
134 using Opt = cosmos::ptrace::Opt;
135 m_ptrace_opts = cosmos::ptrace::Opts{
136 Opt::TRACESYSGOOD,
137 Opt::TRACEEXIT,
138 Opt::TRACEEXEC,
139 };
140
141 if (follow_children) {
142 m_ptrace_opts.set({
143 Opt::TRACECLONE,
144 Opt::TRACEFORK,
145 Opt::TRACEVFORK,
146 Opt::TRACEVFORKDONE
147 });
148 }
149
150 try {
152 } catch (...) {
153 if (!isChildProcess()) {
154 changeState(State::DEAD);
155 }
156 throw;
157 }
158 updateExecutable();
159 updateCmdLine();
160 interrupt();
163 // send SIGCONT via kill() instead of via a ptrace injected
164 // signal. For some reason, when injecting the signal via
165 // ptrace, the stopped state will be applied again after
166 // performing a PTRACE_DETACH operation. This could even be a
167 // kernel bug.
168 cosmos::signal::send(m_ptrace.pid(), cosmos::signal::CONT);
170 }
171
172 if (attach_threads) {
174 }
175}
cosmos::ptrace::Opts m_ptrace_opts
The options we've set for ptrace().
Definition Tracee.hxx:446
@ INJECTED_SIGSTOP
whether we've injected a SIGSTOP that needs to be undone.
Definition Tracee.hxx:64
@ INJECTED_SIGCONT
whether we've injected a SIGCONT that needs to be ignored.
Definition Tracee.hxx:65
@ WAIT_FOR_ATTACH_STOP
we're still waiting for the PTRACE_INTERRUPT event stop.
Definition Tracee.hxx:62
@ ATTACH_THREADS_PENDING
attach all threads of the process as soon as the initial event stop happens.
Definition Tracee.hxx:68
void seize(const cosmos::ptrace::Opts opts)
Makes the tracee a tracee.
Definition Tracee.hxx:344
Flags m_flags
These keep track of various state on the tracer side.
Definition Tracee.hxx:442
cosmos::Tracee m_ptrace
libcosmos API for the Tracee.
Definition Tracee.hxx:444
virtual bool isChildProcess() const
Returns whether the tracee is a child process created by us.
Definition Tracee.hxx:245
void interrupt()
Forces the traced process to stop.
Definition Tracee.hxx:328

◆ attachThreads()

void clues::Tracee::attachThreads ( )
protected

Attach to all threads of the current Tracee's process.

Definition at line 725 of file Tracee.cxx.

725 {
726 const auto path = cosmos::sprintf("/proc/%d/task",
727 cosmos::to_integral(m_ptrace.pid()));
728
729 /*
730 * TODO: there is a race condition involved here:
731 *
732 * We are stopping one of the threads but all the others are still
733 * running while we're looking into /proc. Two things can happen here:
734 *
735 * - threads we're trying to attach might already be lost by the time
736 * we're trying to call ptrace() on them. This is not much of an
737 * issue.
738 * - new threads might come into existence that we don't catch in
739 * /proc, and we'll never trace them, giving an incomplete picture.
740 *
741 * It would be safer to send a SIGSTOP to the whole thread group
742 * before attaching, but this would make the attach procedure even
743 * more complicated than it already is.
744 *
745 * It seems `strace` does not take any precautions regarding this
746 * matter neither.
747 *
748 * We could look at /proc/<pid>/status, which has a thread count
749 * field. This is again racy, however. We could do this after the
750 * fact for verification, or iterate over /proc/<pid>/task until we're
751 * sure we've attached all threads.
752 */
753 cosmos::DirStream task_dir{path};
754 const FollowChildren follow_children{
755 m_ptrace_opts[cosmos::ptrace::Opt::TRACEFORK]};
756
757 for (const auto &entry: task_dir) {
758 if (entry.isDotEntry() ||
759 entry.type() != cosmos::DirEntry::Type::DIRECTORY)
760 continue;
761
762 auto pid = cosmos::ProcessID{static_cast<int>(std::strtol(entry.name(), nullptr, 10))};
763 if (pid == m_ptrace.pid())
764 // ourselves
765 continue;
766
767 try {
768 auto tracee = m_engine.addTracee(
769 pid, follow_children, AttachThreads{false}, this->pid());
770 tracee->m_initial_attacher = m_ptrace.pid();
771 } catch (const std::exception &ex) {
772 LOG_WARN_PID("failed to attach to thread " << cosmos::to_integral(pid) << ": " << ex.what());
773 }
774 }
775}
cosmos::NamedBool< struct attach_threads_t, true > AttachThreads
A strong boolean type denoting whether to automatically all other threads of a process.
Definition types.hxx:27
cosmos::NamedBool< struct follow_children_t, true > FollowChildren
A strong boolean type denoting whether to automatically attach to newly created child processes.
Definition types.hxx:24

◆ changeState()

void clues::Tracee::changeState ( const State new_state)
protected

Definition at line 336 of file Tracee.cxx.

336 {
337 LOG_DEBUG_PID("state " << m_state << " → " << new_state);
338
339 if (new_state == State::SYSCALL_ENTER_STOP) {
341 } else if (new_state == State::SYSCALL_EXIT_STOP) {
343 } else if (new_state == State::GROUP_STOP) {
345 // our own injected group stop
346 // let the tracee continue and we're tracing syscalls now
348 m_restart_mode = cosmos::Tracee::RestartMode::SYSCALL;
349 } else {
350 // enter listen state to avoid restarting a stopped
351 // process as a side-effect.
352 m_restart_mode = cosmos::Tracee::RestartMode::LISTEN;
353 }
354 } else if (new_state == State::DETACHED) {
356 } else if (new_state == State::DEAD) {
357 if (isChildProcess()) {
358 cleanupChild();
359 }
360 }
361
362 if (m_state != State::RUNNING)
364
366 m_stop_signal = std::nullopt;
367
368 m_state = new_state;
369}
@ SYSCALL_ENTERED
we've seen a syscall-enter-stop and are waiting for the corresponding exit-stop.
Definition Tracee.hxx:66
@ DETACH_AT_NEXT_STOP
as soon as the tracee reaches a stop stace, detach from it.
Definition Tracee.hxx:63
std::optional< cosmos::Signal > m_stop_signal
For State::GROUP_STOP this contains the signal that caused it.
Definition Tracee.hxx:468
@ SYSCALL_ENTER_STOP
system call started.
Definition Tracee.hxx:51
@ SYSCALL_EXIT_STOP
system call finished.
Definition Tracee.hxx:52
@ RUNNING
tracee is running normally / not in a special trace state.
Definition Tracee.hxx:50
@ GROUP_STOP
SIGSTOP executed, the tracee is stopped.
Definition Tracee.hxx:54
cosmos::Tracee::RestartMode m_restart_mode
current RestartMode to use.
Definition Tracee.hxx:456
State m_prev_state
The previous Tracee state we've seen (except RUNNING).
Definition Tracee.hxx:440

◆ cleanupChild()

virtual void clues::Tracee::cleanupChild ( )
inlineprotectedvirtual

Definition at line 393 of file Tracee.hxx.

393{}

◆ cmdLine()

const cosmos::StringVector & clues::Tracee::cmdLine ( ) const
inline

Definition at line 82 of file Tracee.hxx.

82 {
83 return m_process_data->cmdline;
84 }

◆ currentSystemCallInfo()

const std::optional< SystemCallInfo > & clues::Tracee::currentSystemCallInfo ( ) const
inline

Definition at line 276 of file Tracee.hxx.

276 {
277 return m_syscall_info;
278 }
std::optional< SystemCallInfo > m_syscall_info
The current system call information, if any.
Definition Tracee.hxx:448

◆ currentSystemCallNr()

std::optional< SystemCallNr > clues::Tracee::currentSystemCallNr ( ) const

Returns the number of the currently running system call, if any.

Definition at line 974 of file Tracee.cxx.

974 {
976 return {};
977
978 return m_current_syscall->callNr();
979}
SystemCall * m_current_syscall
Holds state for the currently executing system call.
Definition Tracee.hxx:452

◆ detach()

bool clues::Tracee::detach ( )

Attempt to detach the Tracee.

Detaching is only possible if the tracee is in a stopped state. If this is the case then the detach is performed and true is returned.

Otherwise a future detach will be prepared by adjusting state flags and false is returned.

If the tracee is already detached then nothing happens and true is returned.

Definition at line 177 of file Tracee.cxx.

177 {
178 if (!m_ptrace.valid()) {
179 return true;
180 } else if (m_state == State::DEAD || m_state == State::DETACHED) {
181 return true;
182 }
183
185 // we can detach immediately
186 doDetach();
187 return true;
188 }
189
190 // otherwise we need to indicate that we want to detach at the
191 // next ptrace stop we're seeing
193
195 // interrupt, if not still pending, for being able to detach
196 try {
197 interrupt();
198 } catch (const cosmos::ApiError &error) {
199 handleError(error);
200 }
201 }
202
203 return false;
204}
void handleError(const cosmos::ApiError &error)
Handle ApiErrors raised from ptrace() requests.
Definition Tracee.cxx:219
void doDetach()
Actually perform a detach without checking tracee state.
Definition Tracee.cxx:206
@ UNKNOWN
initial PTRACE_SIZE / PTRACE_INTERRUPT.
Definition Tracee.hxx:49

◆ doDetach()

void clues::Tracee::doDetach ( )
protected

Actually perform a detach without checking tracee state.

This can throw if the current trace state doesn't allow detaching.

Definition at line 206 of file Tracee.cxx.

206 {
207 try {
208 m_ptrace.detach();
209 changeState(State::DETACHED);
210 } catch (const cosmos::ApiError &error) {
211 handleError(error);
212 }
213
214 m_ptrace = cosmos::Tracee{};
215 m_flags = {};
216 m_initial_attacher.reset();
217}
std::optional< cosmos::ProcessID > m_initial_attacher
If this Tracee was automatically attached due to AttachThreads{true} then this contains the ProcessID...
Definition Tracee.hxx:462

◆ dropFD()

void clues::Tracee::dropFD ( const cosmos::FileNum fd) const
protected

Drop a file descriptor from the tracking of the Tracee's thread group.

Definition at line 1039 of file Tracee.cxx.

1039 {
1040 if (m_process_data->fd_info_map.erase(fd) != 0) {
1041 LOG_DEBUG_PID("removed fd " << cosmos::to_integral(fd) << " from registered mappings");
1042 } else {
1043 LOG_WARN_PID("closed fd " << cosmos::to_integral(fd) << " wasn't open before?!");
1044 }
1045}

◆ engine()

const Engine & clues::Tracee::engine ( ) const
inline

Definition at line 280 of file Tracee.hxx.

280 {
281 return m_engine;
282 }

◆ executable()

const std::string & clues::Tracee::executable ( ) const
inline

Definition at line 78 of file Tracee.hxx.

78 {
79 return m_process_data->executable;
80 }

◆ exitData()

std::optional< cosmos::ChildState > clues::Tracee::exitData ( ) const
inline

Returns possible tracee exit data.

After the tracee is detached, if tracee exit was observed, this returns the tracee exit information (it's exit status or kill signal etc.).

Definition at line 169 of file Tracee.hxx.

169 {
170 return m_exit_data;
171 }
std::optional< cosmos::ChildState > m_exit_data
If tracee exit was observed then this contains the final exit data.
Definition Tracee.hxx:460

◆ fdInfoMap()

const FDInfoMap & clues::Tracee::fdInfoMap ( ) const
inline

Provides access to the current knowledge about file descriptors in the tracee.

Definition at line 87 of file Tracee.hxx.

87 {
88 return m_process_data->fd_info_map;
89 }

◆ fillData()

template<typename FILLER>
void clues::Tracee::fillData ( ForeignPtr addr,
FILLER & filler ) const
protected

Reads data from the Tracee starting at addr and feeds it to filler until it's saturated.

Reads data from the Tracee and feeds it to filler until it's saturated.

Definition at line 924 of file Tracee.cxx.

924 {
925 long word;
926
927 do {
928 word = getData(addr);
929
930 // get the next word
931 addr++;
932 } while (filler(word));
933}
long getData(const ForeignPtr addr) const
Reads a word of data from the tracee's memory.
Definition Tracee.cxx:917

◆ flags()

Flags clues::Tracee::flags ( ) const
inline

Definition at line 159 of file Tracee.hxx.

159 {
160 return m_flags;
161 }

◆ getData()

long clues::Tracee::getData ( const ForeignPtr addr) const

Reads a word of data from the tracee's memory.

The word found at addr is returned from this function on success. On error an exception is thrown.

Definition at line 917 of file Tracee.cxx.

917 {
918 return m_ptrace.peekData(reinterpret_cast<const long*>(
919 cosmos::to_integral(addr)));
920}

◆ getInitialRegisters()

void clues::Tracee::getInitialRegisters ( )
protected

Definition at line 986 of file Tracee.cxx.

986 {
987 /*
988 * TODO: we don't know on which ABI we're sitting on currently, we
989 * could inspect the ELF binary? (like strace does?), but it's also
990 * only kind of a heuristic.
991 */
992
993 try {
994 RegisterSet<get_default_abi()> rs;
995 getRegisters(rs);
996 m_initial_regset = rs;
997 if constexpr (rs.ABI == ABI::X86_64) {
998 /*
999 * detecting the X32 ABI is hard when fetching the
1000 * initial registers. We need to check for the syscall
1001 * bit and redo using the proper ABI in this case.
1002 */
1003 if ((cosmos::to_integral(rs.abiSyscallNr()) & X32_SYSCALL_BIT) != 0) {
1004 RegisterSet<ABI::X32> rs_x32;
1005 getRegisters(rs_x32);
1006 m_initial_regset = rs_x32;
1007 }
1008 }
1009 } catch (const cosmos::RuntimeError &) {
1010 if (get_default_abi() == ABI::X86_64) {
1011 RegisterSet<ABI::I386> rs2;
1012 getRegisters(rs2);
1013 m_initial_regset = rs2;
1014 } else {
1015 throw;
1016 }
1017 }
1018}
void getRegisters(RegisterSet< abi > &rs)
Reads the current register set from the process.
Definition Tracee.cxx:909
AnyRegisterSet m_initial_regset
Register set observed during initial attach event stop.
Definition Tracee.hxx:466
constexpr ABI get_default_abi()
Returns the default ABI for this system.
Definition utils.hxx:59
constexpr uint64_t X32_SYSCALL_BIT
Native system call numbers as used by Linux on the x32 ABI.
Definition x32.hxx:38

◆ getInitialSyscallNr()

std::optional< SystemCallNr > clues::Tracee::getInitialSyscallNr ( const ABI abi) const
protected

Returns the initial system call nr. stored in m_initial_regset, if available for abi.

Definition at line 818 of file Tracee.cxx.

818 {
819 auto visitor = [abi](const auto &rs) -> std::optional<SystemCallNr> {
820 if (abi == rs.ABI)
821 return rs.syscallNr();
822 return {};
823 };
824
825 return std::visit(visitor, m_initial_regset);
826}

◆ getRegisters()

template<ABI abi>
void clues::Tracee::getRegisters ( RegisterSet< abi > & rs)
protected

Reads the current register set from the process.

This is only possible it the tracee is currently in stopped state.

Definition at line 909 of file Tracee.cxx.

909 {
910 cosmos::InputMemoryRegion iovec;
911 rs.fillIov(iovec);
912
913 m_ptrace.getRegisterSet(rs.registerType(), iovec);
914 rs.iovFilled(iovec);
915}

◆ getStateLabel()

const char * clues::Tracee::getStateLabel ( const State state)
static

Definition at line 114 of file Tracee.cxx.

114 {
115 switch (state) {
116 case State::UNKNOWN: return "UNKNOWN";
117 case State::RUNNING: return "RUNNING";
118 case State::SYSCALL_ENTER_STOP: return "SYSCALL_ENTER_STOP";
119 case State::SYSCALL_EXIT_STOP: return "SYSCALL_EXIT_STOP";
120 case State::SIGNAL_DELIVERY_STOP: return "SIGNAL_DELIVERY_STOP";
121 case State::GROUP_STOP: return "GROUP_STOP";
122 case State::EVENT_STOP: return "EVENT_STOP";
123 case State::DEAD: return "DEAD";
124 case State::DETACHED: return "DETACHED";
125 default: return "???";
126 }
127}
@ SIGNAL_DELIVERY_STOP
signal was delivered.
Definition Tracee.hxx:53
@ EVENT_STOP
special ptrace event occurred.
Definition Tracee.hxx:55

◆ handleAttached()

void clues::Tracee::handleAttached ( )
protected

Definition at line 689 of file Tracee.cxx.

689 {
691
693 m_restart_mode = cosmos::Tracee::RestartMode::SYSCALL;
694 }
695
696 getInitialRegisters();
697
698 if (m_process_data->fd_info_map.empty()) {
699 auto &map = m_process_data->fd_info_map;
700 /*
701 * this is a root tracee, either a direct child process or an
702 * initially attached foreign process
703 */
704 try {
705 for (auto &info: get_fd_infos(pid())) {
706 map.insert({info.fd, std::move(info)});
707 }
708 } catch (const cosmos::CosmosError &error) {
709 LOG_WARN_PID("failed to get initial FD infos: " << error.what());
710 }
711 }
712
713 m_consumer.attached(*this);
714
716 try {
718 } catch (const std::exception &e) {
719 LOG_ERROR_PID("failed to attach to other threads: " << e.what());
720 }
722 }
723}
void attachThreads()
Definition Tracee.cxx:725
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

◆ handleError()

void clues::Tracee::handleError ( const cosmos::ApiError & error)
protected

Handle ApiErrors raised from ptrace() requests.

This function will potentially clean up object state but can also rethrow the exception, thus it must be called from a catch block.

Definition at line 219 of file Tracee.cxx.

219 {
220 if (error.errnum() == cosmos::Errno::SEARCH) {
221 // some execve() scenarios end up without a proper
222 // exit notification. ignore them.
224 currentSystemCallNr() != SystemCallNr::EXECVE) {
225 LOG_WARN_PID("tracee found to be already dead upon detach()/interrupt()");
226 }
227 // already gone for some reason
228 changeState(State::DEAD);
229 } else {
230 // shouldn't really happen, unless we're not a
231 // tracee for the process at all anyway
232 m_ptrace = cosmos::Tracee{};
233 throw;
234 }
235}
@ WAIT_FOR_EXITED
we've already seen PTHREAD_EVENT_EXIT but are still waiting for CLD_EXITED.
Definition Tracee.hxx:67
std::optional< SystemCallNr > currentSystemCallNr() const
Returns the number of the currently running system call, if any.
Definition Tracee.cxx:974

◆ handleEvent()

void clues::Tracee::handleEvent ( const cosmos::ChildState & data,
const cosmos::ptrace::Event event,
const cosmos::Signal signal )
protected

Definition at line 536 of file Tracee.cxx.

538 {
539 LOG_INFO_PID("PTRACE_EVENT_" << get_ptrace_event_str(event) << " (" << signal << ")");
540
541 using Event = cosmos::ptrace::Event;
542
543 switch(event) {
544 case Event::STOP: return handleStopEvent(signal);
545 case Event::EXIT: return handleExitEvent();
546 case Event::EXEC: return handleExecEvent(data.child.pid);
547 case Event::CLONE:
548 case Event::VFORK:
549 case Event::VFORK_DONE:
550 case Event::FORK:
551 return handleNewChildEvent(event);
552 default: LOG_WARN_PID("PTRACE_EVENT unhandled");
553 }
554}
const char * get_ptrace_event_str(const cosmos::ptrace::Event event)
Returns a string label for the given event.
Definition utils.cxx:47

◆ handleExecEvent()

void clues::Tracee::handleExecEvent ( const cosmos::ProcessID main_pid)
protected

Definition at line 620 of file Tracee.cxx.

620 {
621 const auto old_exe = m_process_data->executable;
622 const auto old_cmdline = m_process_data->cmdline;
623
624 // in a multi-threaded conext we can receive this event under a
625 // different PID than we currently have. Thus use a dedicated ptrace
626 // wrapper here until we maybe call setPID() below..
627 cosmos::Tracee ptrace{main_pid};
628
629 std::optional<cosmos::ProcessID> old_pid;
630
631 // for a regular execve() this returns the same pid as we have, ignore that
632 if (const auto former_pid = ptrace.getPIDEventMsg(); former_pid != main_pid) {
633 old_pid = former_pid;
634 }
635
636 if (old_pid) {
637 auto old_tracee = m_engine.handleSubstitution(*old_pid);
638 /*
639 * we will receive this event already under the new PID i.e.
640 * if we trace the main thread (which doesn't exec) and the
641 * exec'ing thread as well then we will receive this in main
642 * thread context.
643 *
644 * The "old" main thread no longer exists and we need to sync
645 * state with the actually exec()'ing thread.
646 *
647 * This is good for us currently, because if we have a
648 * ChildTracee running then it will be the main thread and
649 * this thread will stay the main thread in the newly
650 * executing process no matter what. Otherwise we'd have to
651 * abandon the ChildTracee, which holds a SubProc that needs
652 * to be wait()'ed on.
653 */
654 if (old_tracee) {
655 syncState(*old_tracee);
656 } else {
657 /*
658 * If the old main thread was not traced at all then
659 * `old_tracee` is not available here and we are
660 * already the correct Tracee (Engine takes care of
661 * that).
662 * We have to actively change our own PID, though.
663 */
664 setPID(ptrace.pid());
665 }
666 }
667
668 // only update executable info here, since our PID might have changed
669 // above
670 updateExecInfo();
671
672 /*
673 * Check which file descriptors have been closed due to execve.
674 */
675 syncFDsAfterExec();
676
677 m_consumer.newExecutionContext(*this, old_exe, old_cmdline, old_pid);
678}
void setPID(const cosmos::ProcessID tracee)
Sets the tracee PID.
Definition Tracee.cxx:129

◆ handleExitEvent()

void clues::Tracee::handleExitEvent ( )
protected

Definition at line 581 of file Tracee.cxx.

581 {
582
583 EventConsumer::StatusFlags flags;
584
585 /*
586 * Detecting "lost to execve" is a bit of a heuristic:
587 *
588 * - regular exit happens due to exit_group(2). No SYSCALL_EXIT_STOP
589 * will be reported for this.
590 * - exit due to signal. SIGNAL_DELIVERY_STOP should have been the
591 * last state we saw.
592 * - anything else should be execve in multi-threaded process.
593 */
594
595 const auto wait_status = m_ptrace.getExitEventMsg();
596
597 if (wait_status.exited() &&
598 (prevState() != State::SYSCALL_ENTER_STOP ||
599 !cosmos::in_list(*currentSystemCallNr(),
600 {SystemCallNr::EXIT_GROUP, SystemCallNr::EXIT}))) {
601 // the exit status in case of an execve() by another thread is simply 0;
602 // otherwise the exit status specified by the other thread
603 //
604 // it seems we cannot really differentiate the exact reason for
605 // the exit here, see also "death under ptrace" in ptrace(2).
606 //
607 // in theory, provided we're tracing all threads of a process,
608 // we could inspect all other threads if any has an exit or
609 // execve() system call running, but this would still be racy
610 // in a lot of ways.
611 LOG_INFO_PID("multi-threading related EVENT_EXIT detected");
613 }
614
615 m_consumer.exited(*this, wait_status, flags);
616
618}
@ LOST_TO_MT_EXIT
An exit occurs because another thread called execve() or exit() (only appears in exited()).

◆ handleNewChildEvent()

void clues::Tracee::handleNewChildEvent ( const cosmos::ptrace::Event event)
protected

Definition at line 680 of file Tracee.cxx.

680 {
681 // the engine will perform callbacks at m_consumer, since it is
682 // responsible for creating the new Tracee instance.
683 //
684 // m_current_syscall should always be valid at this point, since the
685 // new child must have been created some way.
686 m_engine.handleAutoAttach(*this, m_ptrace.getPIDEventMsg(), event, *m_current_syscall);
687}

◆ handleSignal()

void clues::Tracee::handleSignal ( const cosmos::SigInfo & info)
protected

Definition at line 524 of file Tracee.cxx.

524 {
525 LOG_INFO_PID("Signal: " << info.sigNr());
526
527 if (m_flags[Flag::INJECTED_SIGCONT] && info.sigNr() == cosmos::signal::CONT) {
528 // ignore injected SIGCONT
530 return;
531 }
532
533 m_consumer.signaled(*this, info);
534}

◆ handleStateMismatch()

void clues::Tracee::handleStateMismatch ( )
protected

Definition at line 371 of file Tracee.cxx.

371 {
372 LOG_ERROR_PID("encountered system call state mismatch: we believe "
373 << (m_flags[Flag::SYSCALL_ENTERED] ? "we already entered " : "we did not enter ")
374 << "a system call, but we got a "
375 << (m_syscall_info->isEntry() ? "SyscallInfo::ENTRY" : "SyscallInfo::EXIT")
376 << " event.");
377
378 throw cosmos::RuntimeError{"system call state mismatch"};
379}

◆ handleStopEvent()

void clues::Tracee::handleStopEvent ( const cosmos::Signal signal)
protected

Definition at line 556 of file Tracee.cxx.

556 {
558 // this is the initial ptrace-stop. now we can start tracing
559 LOG_INFO_PID("initial ptrace-stop");
560 handleAttached();
561 } else if (cosmos::in_container(signal, STOPPING_SIGNALS)) {
562 // must be a group stop, unless we're tracing
563 // automatically-attached children, which is not yet
564 // implemented
565 changeState(State::GROUP_STOP);
566 m_stop_signal = signal;
567 m_consumer.stopped(*this);
568 } else if (signal == cosmos::signal::TRAP) {
569 // SIGTRAP has quite a blurry meaning in the ptrace()
570 // API. From practical experiments and the original
571 // strace code I could deduce that for some reason
572 // PTRACE_EVENT_STOP combined with SIGTRAP is observed
573 // when a SIGCONT is received by a tracee that is
574 // currently in group-stop.
575 // We then need to restart using system call tracing
576 // to see the actual SIGCONT being delivered.
577 m_restart_mode = cosmos::Tracee::RestartMode::SYSCALL;
578 }
579}
static constexpr std::array< cosmos::Signal, 4 > STOPPING_SIGNALS
Array of signals that cause tracee stop.
Definition Tracee.hxx:292

◆ handleSystemCall()

void clues::Tracee::handleSystemCall ( )
protected

Definition at line 381 of file Tracee.cxx.

381 {
382
383 m_syscall_info = SystemCallInfo{};
384 auto &info = *m_syscall_info;
385
386 // NOTE: this call can fail if the tracee was killed meanwhile
387 m_ptrace.getSyscallInfo(info);
388
390 if (!info.isExit()) {
391 handleStateMismatch();
392 }
393
394 changeState(State::SYSCALL_EXIT_STOP);
395 handleSystemCallExit();
396 } else {
397 if (!info.isEntry()) {
398 handleStateMismatch();
399 }
400
401 m_syscall_info->updateSysNr();
402
403 changeState(State::SYSCALL_ENTER_STOP);
404 handleSystemCallEntry();
405 }
406
407 m_syscall_info.reset();
408}

◆ handleSystemCallEntry()

void clues::Tracee::handleSystemCallEntry ( )
protected

Definition at line 410 of file Tracee.cxx.

410 {
411 EventConsumer::StatusFlags flags;
412
413 const SystemCallNr nr = m_syscall_info->sysNr();
415
416 if (const auto last_abi = m_current_syscall->abi();
417 last_abi != ABI::UNKNOWN &&
418 last_abi != m_syscall_info->abi()) {
420 }
421
422 verifyArch();
423
424 if (nr == SystemCallNr::RESTART_SYSCALL) {
427 m_interrupted_syscall = nullptr;
429 } else if (m_syscall_ctr != 0) {
430 // explicit restart_syscall done by user space?
431 LOG_WARN_PID("unknown system call is resumed");
432 } else if (auto orig_syscall = getInitialSyscallNr(m_syscall_info->abi()); orig_syscall) {
433 // this happens when attaching to a non-child process
434 // and a system call like clock_nanosleep is
435 // interrupted as a result.
436 // we cannot know which system call is being resumed,
437 // since we have no history. restart_syscall() carries
438 // no additional context information pointing us to
439 // the kind of system call that is being resumed.
440 //
441 // it would be quite a feature, though, to know which
442 // system call is being resumed, as it happens quite
443 // often that a process is attached that behaves
444 // unusually e.g. because it blocks.
445
446 // m_initial_regset is obtained during the initial
447 // ptrace-event-stop. It seems it contains the
448 // currently running system call. This only works if
449 // the system call was not interrupted before already,
450 // though.
451
452 if (*orig_syscall != SystemCallNr::RESTART_SYSCALL && SystemCall::validNr(*orig_syscall)) {
453 m_current_syscall = &m_syscall_db.get(*orig_syscall);
455 }
456 }
458 if (m_interrupted_syscall->callNr() == m_current_syscall->callNr()) {
459 /*
460 * no restart_syscall is used in this case, but we can
461 * still set the flag and cleanup state.
462 */
464 } else {
465 LOG_WARN_PID("unknown system call is resumed after sigreturn");
466 }
467
469 m_interrupted_syscall = nullptr;
470 }
471
472 m_current_syscall->setEntryInfo(*this, *m_syscall_info);
474 m_consumer.syscallEntry(*this, *m_current_syscall, flags);
475}
@ ABI_CHANGED
The system call ABI changed since the last observed system call.
@ RESUMED
A previously interrupted system call is resumed (only appears during syscallEntry()).
static bool validNr(const SystemCallNr nr)
Returns whether the given system call number is in a valid range.
void verifyArch()
Verifies the tracee's architecture according to m_syscall_info, throws on mismatch.
Definition Tracee.cxx:777
@ SEEN_SIGRETURN
a sigreturn with a pending interrupted system call has been observed.
Definition Tracee.hxx:69
size_t m_syscall_ctr
Number of system calls observed.
Definition Tracee.hxx:464
SystemCall * m_interrupted_syscall
Previous system call, if it has been interrupted.
Definition Tracee.hxx:454
std::optional< SystemCallNr > getInitialSyscallNr(const ABI abi) const
Returns the initial system call nr. stored in m_initial_regset, if available for abi.
Definition Tracee.cxx:818
SystemCallDB m_syscall_db
Reusable database object for tracing system calls.
Definition Tracee.hxx:450
SystemCallNr
Abstract system call number usable across architectures and ABIs.
Definition generic.hxx:29

◆ handleSystemCallExit()

void clues::Tracee::handleSystemCallExit ( )
protected

Definition at line 477 of file Tracee.cxx.

477 {
478 EventConsumer::StatusFlags flags;
479 auto &syscall = *m_current_syscall;
480
481 syscall.setExitInfo(*this, *m_syscall_info);
482
483 if (auto error = syscall.error(); error && error->hasKernelErrorCode()) {
484 // system call was interrupted, remember it for later
487 }
488
489 m_consumer.syscallExit(*this, syscall, flags);
490
491 auto is_sigreturn = [](const SystemCall &call) -> bool {
492 switch (call.callNr()) {
493 case SystemCallNr::RT_SIGRETURN: [[fallthrough]];
494 // although this looks legacy it is still used on the I386 ABI
495 case SystemCallNr::SIGRETURN: return true;
496 default: return false;
497 }
498 };
499
500 /*
501 * check if this is a sigreturn() system calling marking the end of
502 * asynchronous signal handler execution and thus the end of the
503 * interruption.
504 */
505 if (m_interrupted_syscall && is_sigreturn(syscall)) {
506 /*
507 * to differentiate transparent restart and visible EINTR
508 * returns we need to check this syscall's return value. If it
509 * succeeds then it is a transparent restart, otherwise a
510 * visible EINTR.
511 */
512 if (const auto error = syscall.error(); error &&
513 error->hasErrorCode() &&
514 error->errorCode() == cosmos::Errno::INTERRUPTED) {
515 m_interrupted_syscall = nullptr;
516 } else {
518 }
519 }
520
521 m_current_syscall = nullptr;
522}
@ INTERRUPTED
A system call was interrupted (only appears during syscallExit()).

◆ hasClonedThread()

bool clues::Tracee::hasClonedThread ( ) const
protected

Returns whether the current/last seen system call was a clone() for a thread.

Definition at line 237 of file Tracee.cxx.

237 {
239 return false;
240
241 const auto &syscall = *m_current_syscall;
242
243 switch (syscall.callNr()) {
244 case SystemCallNr::CLONE: {
245 auto &clone_sc = dynamic_cast<const CloneSystemCall&>(syscall);
246 return clone_sc.flags.flags()[cosmos::CloneFlag::THREAD];
247 } case SystemCallNr::CLONE3: {
248 auto &clone3_sc = dynamic_cast<const Clone3SystemCall&>(syscall);
249 const auto &args = *clone3_sc.cl_args.args();
250 return args.flags()[cosmos::CloneFlag::THREAD];
251 } default: {
252 return false;
253 }
254 }
255}

◆ interrupt()

void clues::Tracee::interrupt ( )
inlineprotected

Forces the traced process to stop.

Definition at line 328 of file Tracee.hxx.

328 {
329 m_ptrace.interrupt();
330 }

◆ isChildProcess()

virtual bool clues::Tracee::isChildProcess ( ) const
inlinevirtual

Returns whether the tracee is a child process created by us.

If the tracee is a child process then the tracer needs to take care of its lifecycle, while non-related tracee's can just be detached from.

Reimplemented in clues::ChildTracee.

Definition at line 245 of file Tracee.hxx.

245 {
246 return false;
247 }

◆ isEnterStop()

bool clues::Tracee::isEnterStop ( ) const
inline

Definition at line 151 of file Tracee.hxx.

151 {
152 return state() == State::SYSCALL_ENTER_STOP;
153 }

◆ isExitStop()

bool clues::Tracee::isExitStop ( ) const
inline

Definition at line 155 of file Tracee.hxx.

155 {
156 return state() == State::SYSCALL_EXIT_STOP;
157 }

◆ isInitiallyAttachedThread()

bool clues::Tracee::isInitiallyAttachedThread ( ) const
inline

Indicates whether this Tracee is an automatically attached thread.

If this is true then the Tracee was attached to due to the AttachThreads{true} setting.

Definition at line 269 of file Tracee.hxx.

269 {
270 return m_initial_attacher != std::nullopt;
271 }

◆ isThreadGroupLeader()

bool clues::Tracee::isThreadGroupLeader ( ) const

Checks in the proc file system whether the Tracee is a thread group leader.

Calling this function is only safe during a ptrace stop.

This information is relevant for the execve in a multi-threaded process situation.

Sadly this bit is not easily accessible via system calls. The only way to determine it is from /proc/<pid>/status or implicitly e.g. a newly fork()'ed process is a thread-group leader while clone()'ed processes can be thread-group leaders (if its not a thread that has been created).

Definition at line 947 of file Tracee.cxx.

947 {
948 const auto path = cosmos::sprintf("/proc/%d/status",
949 cosmos::to_integral(m_ptrace.pid()));
950 std::ifstream is{path};
951
952 if (!is) {
953 // Tracee disappeared?!
954 return false;
955 }
956
957 std::string line;
958
959 while (std::getline(is, line).good()) {
960 auto parts = cosmos::split(line, ":",
961 cosmos::SplitFlags{cosmos::SplitFlag::STRIP_PARTS});
962 if (parts[0] == "Tgid") {
963 auto tgid = std::strtol(parts[1].c_str(), nullptr, 10);
964 return cosmos::ProcessID{static_cast<int>(tgid)} == m_ptrace.pid();
965 }
966 }
967
968 throw cosmos::RuntimeError{"failed to parse Tgid"};
969
970 // nothing found?!
971 return false;
972}

◆ maxBufferPrefetch()

size_t clues::Tracee::maxBufferPrefetch ( ) const
inline

Definition at line 99 of file Tracee.hxx.

99 {
101 }
size_t m_max_buffer_prefetch
Number of bytes system calls will fetch for variable-length data buffers.
Definition Tracee.hxx:472

◆ pid()

cosmos::ProcessID clues::Tracee::pid ( ) const
inline

Definition at line 91 of file Tracee.hxx.

91 {
92 return m_ptrace.pid();
93 }

◆ prevState()

State clues::Tracee::prevState ( ) const
inline

Definition at line 147 of file Tracee.hxx.

147 {
148 return m_prev_state;
149 }

◆ processEvent()

void clues::Tracee::processEvent ( const cosmos::ChildState & data)
protected

Process the given ptrace event.

This call will be invoked by the Engine class to drive the tracing logic.

Definition at line 837 of file Tracee.cxx.

837 {
838 m_inject_sig = {};
839
840 if (data.exited() || data.killed() || data.dumped()) {
841 changeState(State::DEAD);
843 m_consumer.disappeared(*this, data);
844 }
845 m_exit_data = data;
846 return;
847 } else if (data.trapped()) {
849 doDetach();
850 return;
851 }
852
853 if (data.signal == cosmos::signal::SYS_TRAP) {
854 try {
855 handleSystemCall();
856 } catch (const std::exception &ex) {
857 /*
858 * TODO: we need some way to robustly deal
859 * with unexpected errors during tracing.
860 *
861 * we could offer an API to lib clients to let
862 * them decide what to do.
863 */
864 throw;
865 }
866 } else if (data.signal->isPtraceEventStop()) {
867 changeState(State::EVENT_STOP);
868 const auto [signr, event] = cosmos::ptrace::decode_event(*data.signal);
869 handleEvent(data, event, cosmos::Signal{signr});
870 } else {
871 changeState(State::SIGNAL_DELIVERY_STOP);
872 cosmos::SigInfo info;
873 m_ptrace.getSigInfo(info);
874 handleSignal(info);
875 m_inject_sig = *data.signal;
876 }
877 } else if (data.signaled()) {
878 // it seems this branch is never hit, signals are
879 // always handled as a TRAP, never as regular events
880 // when tracing.
881 // TODO: but for direct child processes that we want to stop
882 // tracing this could happen after detaching.
883 LOG_WARN_PID("seeing non-trap signal delivery stop");
884 changeState(State::SIGNAL_DELIVERY_STOP);
885 cosmos::SigInfo info;
886 m_ptrace.getSigInfo(info);
887 handleSignal(info);
888 m_inject_sig = *data.signal;
889 } else {
890 LOG_WARN_PID("Other Tracee event?");
891 }
892
894 return;
895
896 // NOTE: this can fail with ESRCH if the tracee got killed meanwhile
898
899 if (m_restart_mode != cosmos::Tracee::RestartMode::LISTEN) {
900 const auto resumed = m_state == State::GROUP_STOP;
901 changeState(State::RUNNING);
902 if (resumed) {
903 m_consumer.resumed(*this);
904 }
905 }
906}
std::optional< cosmos::Signal > m_inject_sig
signal to inject upon next restart of the tracee.
Definition Tracee.hxx:458
void restart(const cosmos::Tracee::RestartMode mode=cosmos::Tracee::RestartMode::CONT, const std::optional< cosmos::Signal > signal={})
Restarts the traced process, optionally delivering signal.
Definition Tracee.hxx:333

◆ readBlob()

void clues::Tracee::readBlob ( const ForeignPtr addr,
char * buffer,
const size_t bytes ) const

Reads an arbitrary binary blob of fixed length from the tracee.

Definition at line 981 of file Tracee.cxx.

981 {
982 BlobFiller filler{bytes, buffer};
983 fillData(addr, filler);
984}
void fillData(ForeignPtr addr, FILLER &filler) const
Reads data from the Tracee starting at addr and feeds it to filler until it's saturated.
Definition Tracee.cxx:924

◆ readString()

void clues::Tracee::readString ( const ForeignPtr addr,
std::string & out ) const

Reads a zero-terminated C-string from the tracee.

Read from the tracee's address space starting at addr into the C++ string object out.

Definition at line 935 of file Tracee.cxx.

935 {
936 return readVector(addr, out);
937}
void readVector(const ForeignPtr pointer, VECTOR &out) const
Reads in a zero terminated array of data items into the STL-vector like parameter out.
Definition Tracee.cxx:940

◆ readStruct()

template<typename T, bool CHECK_TRIVIAL = true>
bool clues::Tracee::readStruct ( const ForeignPtr addr,
T & out ) const
inline

Reads a system call struct from the tracee's address space into out.

Returns
true if out could be filled, false otherwise (e.g. nullptr was encouneterd).

Definition at line 221 of file Tracee.hxx.

221 {
222 // the address of the struct in the tracee's address space
223 if (addr == ForeignPtr::NO_POINTER)
224 // null address specification
225 return false;
226
227 if constexpr (CHECK_TRIVIAL) {
228 static_assert(std::is_trivial_v<T> == true);
229 }
230
231 readBlob(addr, reinterpret_cast<char*>(&out), sizeof(T));
232 return true;
233 }
void readBlob(const ForeignPtr addr, char *buffer, const size_t bytes) const
Reads an arbitrary binary blob of fixed length from the tracee.
Definition Tracee.cxx:981

◆ readVector()

template<typename VECTOR>
void clues::Tracee::readVector ( const ForeignPtr pointer,
VECTOR & out ) const

Reads in a zero terminated array of data items into the STL-vector like parameter out.

Definition at line 940 of file Tracee.cxx.

940 {
941 out.clear();
942
943 ContainerFiller<VECTOR> filler{out};
944 fillData(addr, filler);
945}

◆ restart()

void clues::Tracee::restart ( const cosmos::Tracee::RestartMode mode = cosmos::Tracee::RestartMode::CONT,
const std::optional< cosmos::Signal > signal = {} )
inlineprotected

Restarts the traced process, optionally delivering signal.

Definition at line 333 of file Tracee.hxx.

334 {}) {
335 m_ptrace.restart(mode, signal);
336 }

◆ seize()

void clues::Tracee::seize ( const cosmos::ptrace::Opts opts)
inlineprotected

Makes the tracee a tracee.

Definition at line 344 of file Tracee.hxx.

344 {
345 m_ptrace.seize(opts);
346 }

◆ setMaxBufferPrefetch()

void clues::Tracee::setMaxBufferPrefetch ( const size_t bytes)
inline

Sets an upper limit for the retrieval of variable-length buffer parameters in system calls.

System calls like read() and write() process variable-length raw buffer data, which can be quite large. Fetching the complete buffer contents from tracee memory for all these system calls can affect tracing performance, especially when the full buffer data is not actually needed by the tracer application.

By default only a small part of the buffer data will be automatically fetched by specializations of SystemCall. You can set this to 0 to completely disable prefetching of buffer data or to SIZE_MAX to always fetch the complete buffers from tracees. In case the buffer data has not been completely pre-fetched, but the application is interested in the buffer data for a specific system call only, then the application can explicitly fetch the rest of the data from the tracee during a system call stop event.

This is a per-tracee setting to allow tracee-specific variations of this setting.

Definition at line 123 of file Tracee.hxx.

123 {
124 m_max_buffer_prefetch = bytes;
125 }

◆ setOptions()

void clues::Tracee::setOptions ( const cosmos::ptrace::Opts opts)
inlineprotected

Applies the given trace flags.

Definition at line 339 of file Tracee.hxx.

339 {
340 m_ptrace.setOptions(opts);
341 }

◆ setPID()

void clues::Tracee::setPID ( const cosmos::ProcessID tracee)
protected

Sets the tracee PID.

Definition at line 129 of file Tracee.cxx.

129 {
130 m_ptrace = cosmos::Tracee{tracee};
131}

◆ state()

State clues::Tracee::state ( ) const
inline

Definition at line 143 of file Tracee.hxx.

143 {
144 return m_state;
145 }

◆ stopSignal()

std::optional< cosmos::Signal > clues::Tracee::stopSignal ( ) const
inline

For state() == State::GROUP_STOP this returns the stopping signal that caused it.

Definition at line 137 of file Tracee.hxx.

137 {
138 return m_stop_signal;
139 }

◆ syncFDsAfterExec()

void clues::Tracee::syncFDsAfterExec ( )
protected

Definition at line 309 of file Tracee.cxx.

309 {
310 /*
311 * We could mimic what the kernel does by inspecting the file
312 * descriptor flags. To prevent any inconsistencies and because it
313 * shouldn't bee too expensive to do this for each execve(),
314 * synchronize the state with what is found in /proc/<pid>/fd.
315 */
316 try {
317 auto left_fds = clues::get_currently_open_fds(m_ptrace.pid());
318
319 auto &fd_info_map = m_process_data->fd_info_map;
320
321 for (auto it = fd_info_map.begin(); it != fd_info_map.end(); it++) {
322 if (left_fds.count(it->first) == 0) {
323 it = fd_info_map.erase(it);
324 }
325 }
326 } catch (const cosmos::ApiError &ex) {
327 /* It seems the process died.
328 * The Tracee will be cleaned up soon anyway, so let's keep
329 * the file descriptors we've seen last time to avoid further
330 * confusion.
331 */
332 LOG_WARN_PID("unable to get currently open fds: " << ex.what());
333 }
334}
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

◆ syncState()

void clues::Tracee::syncState ( Tracee & other)
protected

Definition at line 828 of file Tracee.cxx.

828 {
829 m_syscall_info = other.m_syscall_info;
830 m_syscall_db = std::move(other.m_syscall_db);
831 m_current_syscall = other.m_current_syscall;
832 m_interrupted_syscall = other.m_interrupted_syscall;
833 m_syscall_ctr = other.m_syscall_ctr;
834 other.changeState(State::DEAD);
835}

◆ syscallCtr()

size_t clues::Tracee::syscallCtr ( ) const
inline

Returns the number of system calls observed so far.

This counts the number of system call entries observed while tracing this PID.

Definition at line 132 of file Tracee.hxx.

132 {
133 return m_syscall_ctr;
134 }

◆ trackFD()

void clues::Tracee::trackFD ( FDInfo && info) const
protected

Track a new file descriptor for this Tracee's thread group.

This function is intended in the context of SystemCall exit based on the knowledge of concrete SystemCall implementations.

Definition at line 1024 of file Tracee.cxx.

1024 {
1025 const auto fd = info.fd;
1026 auto res = m_process_data->fd_info_map.insert(
1027 std::make_pair(fd, std::move(info))
1028 );
1029
1030 if (!res.second) {
1031 LOG_WARN_PID("FD " << cosmos::to_integral(fd) << " was already open?!");
1032 // bail out
1033 return;
1034 }
1035
1036 LOG_DEBUG_PID("new file descriptor tracking for fd " << cosmos::to_integral(fd));
1037}

◆ unshareProcessData()

void clues::Tracee::unshareProcessData ( )
protected

Definition at line 1020 of file Tracee.cxx.

1020 {
1021 m_process_data = std::make_shared<ProcessData>(*m_process_data);
1022}

◆ updateCmdLine()

void clues::Tracee::updateCmdLine ( )
protected

Definition at line 284 of file Tracee.cxx.

284 {
285 // the cmdline file contains null terminator separated strings
286 // the tracee controls this data and could place crafted data here.
287 //
288 // obtain the data from here instead of from execve() has the
289 // following advantages:
290 // - it works the same for newly created tracee's as for tracee's
291 // we're attaching to during runtime.
292 // - we can obtain the information without having to find the correct
293 // syscall-exit stop context that caused a ptrace-exec event stop.
294 const auto path = cosmos::sprintf("/proc/%d/cmdline", cosmos::to_integral(m_ptrace.pid()));
295
296 m_process_data->cmdline.clear();
297
298 std::ifstream is{path};
299 if (!is) {
300 // see updateExecutable() for the rationale here
301 return;
302 }
303 std::string arg;
304 while (std::getline(is, arg, '\0').good()) {
305 m_process_data->cmdline.push_back(arg);
306 }
307}

◆ updateExecInfo()

void clues::Tracee::updateExecInfo ( )
inlineprotected

Definition at line 318 of file Tracee.hxx.

318 {
319 updateExecutable();
320 updateCmdLine();
321 }

◆ updateExecutable()

void clues::Tracee::updateExecutable ( )
protected

Definition at line 257 of file Tracee.cxx.

257 {
258 // obtain the executable name from proc.
259 // as long as we're tracing the PID there is no race involved.
260 // this information is more reliable than what we see in execve(),
261 // since symlinks or other magic might be involved.
262 const auto path = cosmos::sprintf("/proc/%d/exe", cosmos::to_integral(m_ptrace.pid()));
263 try {
264 m_process_data->executable = cosmos::fs::read_symlink(path);
265 } catch (const cosmos::ApiError &e) {
266 // likely ENOENT, tracee disappeared
267 // but can this even happen while we're tracing it? and even
268 // if it happens, then no more tracing will happen, so the
269 // information is no longer relevant.
270 //
271 // As a fallback we could rely on the less precise information
272 // from evecve-entry. The issue is that the execve-event-stop
273 // happens before system-call-exit-stop and also that in
274 // multi-threaded contexts, the actual execve system call data
275 // might be in a wholly different tracee, or one that we're
276 // not even tracing.
277 //
278 // so let's ignore this for the moment, assuming that this is
279 // not a relevant situation anyway.
280 m_process_data->executable.clear();
281 }
282}

◆ verifyArch()

void clues::Tracee::verifyArch ( )
protected

Verifies the tracee's architecture according to m_syscall_info, throws on mismatch.

Definition at line 777 of file Tracee.cxx.

777 {
778 /*
779 * A Tracee can change "personality" during its lifetime, thus every
780 * system call has to be observed for changes in ABI. In theory the
781 * same binary could even employ multiple calling conventions at the
782 * same time.
783 *
784 * The usual situation is that 32-bit programs can also be executed on
785 * 64-bit operating systems. This is not only the case on x86_64 but
786 * also on powerpc64, arm64 etc. On x86 we even have three different
787 * ABIs: i386, x86_64 and x32. The latter is using the amd64
788 * instruction set but only 32-bit data types.
789 *
790 * To support these different ABIs we use an abstract SystemCallNr
791 * which is the same for all ABIs, allowing us to identify the correct
792 * system calls. Differences between ABIs might still need to be taken
793 * into account by concrete SystemCall instances (e.g. differently
794 * sized structures when running 32-bit emulation binaries).
795 */
796 using Arch = cosmos::ptrace::Arch;
797 switch (m_syscall_info->arch()) {
798 case Arch::I386:
799 if (cosmos::arch::X86_64) {
800 // 32-bit emulation, should work out of the box
801 }
802 return;
803 case Arch::X86_64:
804 if (!cosmos::arch::X86_64) {
805 throw cosmos::RuntimeError{"tracing 64-bit binaries with a 32-bit tracer is not supported"};
806 }
807 return;
808 case Arch::AARCH64:
809 if (!cosmos::arch::AARCH64) {
810 throw cosmos::RuntimeError{"tracing 64-bit binaries with a 32-bit tracer is not supported"};
811 }
812 return;
813 default:
814 throw cosmos::RuntimeError{"tracing this architecture/ABI is not currently supported"};
815 }
816}

Friends And Related Symbol Documentation

◆ Engine

friend class Engine
friend

Definition at line 40 of file Tracee.hxx.

◆ SystemCall

friend class SystemCall
friend

Definition at line 41 of file Tracee.hxx.

Member Data Documentation

◆ m_consumer

EventConsumer& clues::Tracee::m_consumer
protected

Callback interface receiving our information.

Definition at line 436 of file Tracee.hxx.

◆ m_current_syscall

SystemCall* clues::Tracee::m_current_syscall = nullptr
protected

Holds state for the currently executing system call.

Definition at line 452 of file Tracee.hxx.

◆ m_engine

Engine& clues::Tracee::m_engine
protected

The engine that manages this tracee.

Definition at line 434 of file Tracee.hxx.

◆ m_exit_data

std::optional<cosmos::ChildState> clues::Tracee::m_exit_data
protected

If tracee exit was observed then this contains the final exit data.

Definition at line 460 of file Tracee.hxx.

◆ m_flags

Flags clues::Tracee::m_flags
protected

These keep track of various state on the tracer side.

Definition at line 442 of file Tracee.hxx.

◆ m_initial_attacher

std::optional<cosmos::ProcessID> clues::Tracee::m_initial_attacher
protected

If this Tracee was automatically attached due to AttachThreads{true} then this contains the ProcessID of the initial Thread that caused this.

Definition at line 462 of file Tracee.hxx.

◆ m_initial_regset

AnyRegisterSet clues::Tracee::m_initial_regset
protected

Register set observed during initial attach event stop.

Definition at line 466 of file Tracee.hxx.

◆ m_inject_sig

std::optional<cosmos::Signal> clues::Tracee::m_inject_sig
protected

signal to inject upon next restart of the tracee.

Definition at line 458 of file Tracee.hxx.

◆ m_interrupted_syscall

SystemCall* clues::Tracee::m_interrupted_syscall = nullptr
protected

Previous system call, if it has been interrupted.

Definition at line 454 of file Tracee.hxx.

◆ m_max_buffer_prefetch

size_t clues::Tracee::m_max_buffer_prefetch = 128
protected

Number of bytes system calls will fetch for variable-length data buffers.

Definition at line 472 of file Tracee.hxx.

◆ m_prev_state

State clues::Tracee::m_prev_state = State::UNKNOWN
protected

The previous Tracee state we've seen (except RUNNING).

Definition at line 440 of file Tracee.hxx.

◆ m_process_data

ProcessDataPtr clues::Tracee::m_process_data
protected

Shared process data.

Definition at line 470 of file Tracee.hxx.

◆ m_ptrace

cosmos::Tracee clues::Tracee::m_ptrace
protected

libcosmos API for the Tracee.

Definition at line 444 of file Tracee.hxx.

◆ m_ptrace_opts

cosmos::ptrace::Opts clues::Tracee::m_ptrace_opts
protected

The options we've set for ptrace().

Definition at line 446 of file Tracee.hxx.

◆ m_restart_mode

cosmos::Tracee::RestartMode clues::Tracee::m_restart_mode = cosmos::Tracee::RestartMode::CONT
protected

current RestartMode to use.

Definition at line 456 of file Tracee.hxx.

◆ m_state

State clues::Tracee::m_state = State::UNKNOWN
protected

The current state the tracee is in.

Definition at line 438 of file Tracee.hxx.

◆ m_stop_signal

std::optional<cosmos::Signal> clues::Tracee::m_stop_signal
protected

For State::GROUP_STOP this contains the signal that caused it.

Definition at line 468 of file Tracee.hxx.

◆ m_syscall_ctr

size_t clues::Tracee::m_syscall_ctr = 0
protected

Number of system calls observed.

Definition at line 464 of file Tracee.hxx.

◆ m_syscall_db

SystemCallDB clues::Tracee::m_syscall_db
protected

Reusable database object for tracing system calls.

Definition at line 450 of file Tracee.hxx.

◆ m_syscall_info

std::optional<SystemCallInfo> clues::Tracee::m_syscall_info
protected

The current system call information, if any.

Definition at line 448 of file Tracee.hxx.

◆ STOPPING_SIGNALS

std::array<cosmos::Signal, 4> clues::Tracee::STOPPING_SIGNALS
staticconstexprprotected
Initial value:
= {
cosmos::signal::STOP, cosmos::signal::TERM_STOP,
cosmos::signal::TERM_INPUT, cosmos::signal::TERM_OUTPUT
}

Array of signals that cause tracee stop.

Stopping signals are treated specially during tracing since they result in a group-stop state change. This constexpr array is used to identify them.

Definition at line 292 of file Tracee.hxx.

292 {
293 cosmos::signal::STOP, cosmos::signal::TERM_STOP,
294 cosmos::signal::TERM_INPUT, cosmos::signal::TERM_OUTPUT
295 };

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