6#include <cosmos/compiler.hxx>
7#include <cosmos/error/ApiError.hxx>
8#include <cosmos/error/RuntimeError.hxx>
9#include <cosmos/formatting.hxx>
10#include <cosmos/fs/DirStream.hxx>
11#include <cosmos/fs/filesystem.hxx>
12#include <cosmos/io/ILogger.hxx>
15#include <clues/Engine.hxx>
16#include <clues/EventConsumer.hxx>
17#include <clues/items/clone.hxx>
18#include <clues/logger.hxx>
19#include <clues/RegisterSet.hxx>
20#include <clues/syscalls/process.hxx>
21#include <clues/sysnrs/generic.hxx>
22#include <clues/SystemCall.hxx>
23#include <clues/Tracee.hxx>
24#include <clues/utils.hxx>
26#define LOG_DEBUG_PID(X) LOG_DEBUG("[" << cosmos::to_integral(m_ptrace.pid()) << "] " << X)
27#define LOG_INFO_PID(X) LOG_INFO("[" << cosmos::to_integral(m_ptrace.pid()) << "] " << X)
28#define LOG_WARN_PID(X) LOG_WARN("[" << cosmos::to_integral(m_ptrace.pid()) << "] " << X)
29#define LOG_ERROR_PID(X) LOG_ERROR("[" << cosmos::to_integral(m_ptrace.pid()) << "] " << X)
38template <
typename CONTAINER>
39class ContainerFiller {
40 using ptr_type =
typename CONTAINER::pointer;
43 explicit ContainerFiller(CONTAINER &container) :
44 m_container{container} {
47 bool operator()(
long word) {
48 static_assert(std::is_trivial_v<typename CONTAINER::value_type> ==
true);
49 static_assert(
sizeof(ITEM_SIZE) <=
sizeof(long),
"Unexpected ITEM_SIZE (must be <= sizeof(long))");
50 ptr_type word_ptr =
reinterpret_cast<ptr_type
>(&word);
51 typename CONTAINER::value_type item;
53 for (
size_t numitem = 0; numitem <
sizeof(word) / ITEM_SIZE; numitem++) {
54 std::memcpy(&item, word_ptr + numitem,
sizeof(item));
59 m_container.push_back(item);
66 CONTAINER &m_container;
67 static constexpr size_t ITEM_SIZE =
sizeof(
typename CONTAINER::value_type);
74 BlobFiller(
const size_t bytes,
char *buffer) :
79 bool operator()(
long word) {
80 const size_t to_copy = std::min(
sizeof(word), m_left);
82 std::memcpy(m_buffer, &word, to_copy);
110 LOG_WARN_PID(
"destroying Tracee in live state");
114const char* Tracee::getStateLabel(
const State state) {
125 default:
return "???";
134 using Opt = cosmos::ptrace::Opt;
141 if (follow_children) {
168 cosmos::signal::send(
m_ptrace.pid(), cosmos::signal::CONT);
172 if (attach_threads) {
198 }
catch (
const cosmos::ApiError &error) {
210 }
catch (
const cosmos::ApiError &error) {
220 if (error.errnum() == cosmos::Errno::SEARCH) {
225 LOG_WARN_PID(
"tracee found to be already dead upon detach()/interrupt()");
243 switch (syscall.callNr()) {
244 case SystemCallNr::CLONE: {
246 return clone_sc.flags.flags()[cosmos::CloneFlag::THREAD];
247 }
case SystemCallNr::CLONE3: {
250 return args.flags()[cosmos::CloneFlag::THREAD];
257void Tracee::updateExecutable() {
262 const auto path = cosmos::sprintf(
"/proc/%d/exe", cosmos::to_integral(
m_ptrace.pid()));
265 }
catch (
const cosmos::ApiError &e) {
284void Tracee::updateCmdLine() {
294 const auto path = cosmos::sprintf(
"/proc/%d/cmdline", cosmos::to_integral(
m_ptrace.pid()));
298 std::ifstream is{path};
304 while (std::getline(is, arg,
'\0').good()) {
309void Tracee::syncFDsAfterExec() {
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);
326 }
catch (
const cosmos::ApiError &ex) {
332 LOG_WARN_PID(
"unable to get currently open fds: " << ex.what());
336void Tracee::changeState(
const State new_state) {
337 LOG_DEBUG_PID(
"state " <<
m_state <<
" → " << new_state);
371void Tracee::handleStateMismatch() {
372 LOG_ERROR_PID(
"encountered system call state mismatch: we believe "
374 <<
"a system call, but we got a "
375 << (
m_syscall_info->isEntry() ?
"SyscallInfo::ENTRY" :
"SyscallInfo::EXIT")
378 throw cosmos::RuntimeError{
"system call state mismatch"};
381void Tracee::handleSystemCall() {
390 if (!info.isExit()) {
391 handleStateMismatch();
395 handleSystemCallExit();
397 if (!info.isEntry()) {
398 handleStateMismatch();
404 handleSystemCallEntry();
410void Tracee::handleSystemCallEntry() {
411 EventConsumer::StatusFlags flags;
417 last_abi != ABI::UNKNOWN &&
424 if (nr == SystemCallNr::RESTART_SYSCALL) {
431 LOG_WARN_PID(
"unknown system call is resumed");
452 if (*orig_syscall != SystemCallNr::RESTART_SYSCALL &&
SystemCall::validNr(*orig_syscall)) {
465 LOG_WARN_PID(
"unknown system call is resumed after sigreturn");
477void Tracee::handleSystemCallExit() {
478 EventConsumer::StatusFlags flags;
483 if (
auto error = syscall.error(); error && error->hasKernelErrorCode()) {
489 m_consumer.syscallExit(*
this, syscall, flags);
491 auto is_sigreturn = [](
const SystemCall &call) ->
bool {
492 switch (call.callNr()) {
493 case SystemCallNr::RT_SIGRETURN: [[fallthrough]];
495 case SystemCallNr::SIGRETURN:
return true;
496 default:
return false;
512 if (
const auto error = syscall.error(); error &&
513 error->hasErrorCode() &&
514 error->errorCode() == cosmos::Errno::INTERRUPTED) {
524void Tracee::handleSignal(
const cosmos::SigInfo &info) {
525 LOG_INFO_PID(
"Signal: " << info.sigNr());
536void Tracee::handleEvent(
const cosmos::ChildState &data,
537 const cosmos::ptrace::Event event,
538 const cosmos::Signal signal) {
541 using Event = cosmos::ptrace::Event;
544 case Event::STOP:
return handleStopEvent(signal);
545 case Event::EXIT:
return handleExitEvent();
546 case Event::EXEC:
return handleExecEvent(data.child.pid);
549 case Event::VFORK_DONE:
551 return handleNewChildEvent(event);
552 default: LOG_WARN_PID(
"PTRACE_EVENT unhandled");
556void Tracee::handleStopEvent(
const cosmos::Signal signal) {
559 LOG_INFO_PID(
"initial ptrace-stop");
568 }
else if (signal == cosmos::signal::TRAP) {
581void Tracee::handleExitEvent() {
583 EventConsumer::StatusFlags flags;
595 const auto wait_status =
m_ptrace.getExitEventMsg();
597 if (wait_status.exited() &&
600 {SystemCallNr::EXIT_GROUP, SystemCallNr::EXIT}))) {
611 LOG_INFO_PID(
"multi-threading related EVENT_EXIT detected");
620void Tracee::handleExecEvent(
const cosmos::ProcessID main_pid) {
627 cosmos::Tracee ptrace{main_pid};
629 std::optional<cosmos::ProcessID> old_pid;
632 if (
const auto former_pid = ptrace.getPIDEventMsg(); former_pid != main_pid) {
633 old_pid = former_pid;
637 auto old_tracee =
m_engine.handleSubstitution(*old_pid);
655 syncState(*old_tracee);
677 m_consumer.newExecutionContext(*
this, old_exe, old_cmdline, old_pid);
680void Tracee::handleNewChildEvent(
const cosmos::ptrace::Event event) {
689void Tracee::handleAttached() {
696 getInitialRegisters();
706 map.insert({info.fd, std::move(info)});
708 }
catch (
const cosmos::CosmosError &error) {
709 LOG_WARN_PID(
"failed to get initial FD infos: " << error.what());
718 }
catch (
const std::exception &e) {
719 LOG_ERROR_PID(
"failed to attach to other threads: " << e.what());
726 const auto path = cosmos::sprintf(
"/proc/%d/task",
727 cosmos::to_integral(
m_ptrace.pid()));
753 cosmos::DirStream task_dir{path};
757 for (
const auto &entry: task_dir) {
758 if (entry.isDotEntry() ||
759 entry.type() != cosmos::DirEntry::Type::DIRECTORY)
762 auto pid = cosmos::ProcessID{
static_cast<int>(std::strtol(entry.name(),
nullptr, 10))};
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());
796 using Arch = cosmos::ptrace::Arch;
799 if (cosmos::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"};
809 if (!cosmos::arch::AARCH64) {
810 throw cosmos::RuntimeError{
"tracing 64-bit binaries with a 32-bit tracer is not supported"};
814 throw cosmos::RuntimeError{
"tracing this architecture/ABI is not currently supported"};
819 auto visitor = [abi](
const auto &rs) -> std::optional<SystemCallNr> {
821 return rs.syscallNr();
828void Tracee::syncState(
Tracee &other) {
840 if (data.exited() || data.killed() || data.dumped()) {
847 }
else if (data.trapped()) {
853 if (data.signal == cosmos::signal::SYS_TRAP) {
856 }
catch (
const std::exception &ex) {
866 }
else if (data.signal->isPtraceEventStop()) {
868 const auto [signr, event] = cosmos::ptrace::decode_event(*data.signal);
869 handleEvent(data, event, cosmos::Signal{signr});
872 cosmos::SigInfo info;
877 }
else if (data.signaled()) {
883 LOG_WARN_PID(
"seeing non-trap signal delivery stop");
885 cosmos::SigInfo info;
890 LOG_WARN_PID(
"Other Tracee event?");
910 cosmos::InputMemoryRegion iovec;
918 return m_ptrace.peekData(
reinterpret_cast<const long*
>(
919 cosmos::to_integral(addr)));
923template <
typename FILLER>
932 }
while (filler(word));
939template <
typename VECTOR>
943 ContainerFiller<VECTOR> filler{out};
948 const auto path = cosmos::sprintf(
"/proc/%d/status",
949 cosmos::to_integral(
m_ptrace.pid()));
950 std::ifstream is{path};
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();
968 throw cosmos::RuntimeError{
"failed to parse Tgid"};
982 BlobFiller filler{bytes, buffer};
986void Tracee::getInitialRegisters() {
997 if constexpr (rs.ABI == ABI::X86_64) {
1009 }
catch (
const cosmos::RuntimeError &) {
1011 RegisterSet<ABI::I386> rs2;
1020void Tracee::unshareProcessData() {
1025 const auto fd = info.fd;
1027 std::make_pair(fd, std::move(info))
1031 LOG_WARN_PID(
"FD " << cosmos::to_integral(fd) <<
" was already open?!");
1036 LOG_DEBUG_PID(
"new file descriptor tracking for fd " << cosmos::to_integral(fd));
1041 LOG_DEBUG_PID(
"removed fd " << cosmos::to_integral(fd) <<
" from registered mappings");
1043 LOG_WARN_PID(
"closed fd " << cosmos::to_integral(fd) <<
" wasn't open before?!");
1048#if !defined(COSMOS_I386) && !defined(COSMOS_X32)
1057 o << clues::Tracee::getStateLabel(state);
Callback interface for consumers of tracing events.
@ ABI_CHANGED
The system call ABI changed since the last observed system call.
@ INTERRUPTED
A system call was interrupted (only appears during syscallExit()).
@ LOST_TO_MT_EXIT
An exit occurs because another thread called execve() or exit() (only appears in exited()).
@ RESUMED
A previously interrupted system call is resumed (only appears during syscallEntry()).
This type contains data that is shared between tracees of the same thread group.
Holds a set of registers for the given ABI.
void fillIov(cosmos::InputMemoryRegion &iov)
Prepares iov for doing a ptrace system call of type ptrace::Request::GETREGSET.
void iovFilled(const cosmos::InputMemoryRegion &iov)
Verify data received from a ptrace system call of type ptrace::Request::GETREGSET.
auto abiSyscallNr() const
Returns the ABI-specific system call number on entry to a syscall.
static constexpr cosmos::ptrace::RegisterType registerType()
The type to pass to ptrace::Request::GETREGSET for obtaining the general purpose registers.
static bool validNr(const SystemCallNr nr)
Returns whether the given system call number is in a valid range.
Base class for traced processes.
bool hasClonedThread() const
Returns whether the current/last seen system call was a clone() for a thread.
void setPID(const cosmos::ProcessID tracee)
Sets the tracee PID.
void trackFD(FDInfo &&info) const
Track a new file descriptor for this Tracee's thread group.
void verifyArch()
Verifies the tracee's architecture according to m_syscall_info, throws on mismatch.
cosmos::ptrace::Opts m_ptrace_opts
The options we've set for ptrace().
ProcessDataPtr m_process_data
Shared process data.
bool isThreadGroupLeader() const
Checks in the proc file system whether the Tracee is a thread group leader.
@ INJECTED_SIGSTOP
whether we've injected a SIGSTOP that needs to be undone.
@ SYSCALL_ENTERED
we've seen a syscall-enter-stop and are waiting for the corresponding exit-stop.
@ SEEN_SIGRETURN
a sigreturn with a pending interrupted system call has been observed.
@ INJECTED_SIGCONT
whether we've injected a SIGCONT that needs to be ignored.
@ DETACH_AT_NEXT_STOP
as soon as the tracee reaches a stop stace, detach from it.
@ WAIT_FOR_ATTACH_STOP
we're still waiting for the PTRACE_INTERRUPT event 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.
std::optional< cosmos::Signal > m_stop_signal
For State::GROUP_STOP this contains the signal that caused it.
void handleError(const cosmos::ApiError &error)
Handle ApiErrors raised from ptrace() requests.
bool detach()
Attempt to detach the Tracee.
Tracee(Engine &engine, EventConsumer &consumer, TraceePtr sibling=nullptr)
void seize(const cosmos::ptrace::Opts opts)
Makes the tracee a tracee.
size_t m_syscall_ctr
Number of system calls observed.
void doDetach()
Actually perform a detach without checking tracee state.
SystemCall * m_current_syscall
Holds state for the currently executing system call.
void dropFD(const cosmos::FileNum fd) const
Drop a file descriptor from the tracking of the Tracee's thread group.
Engine & m_engine
The engine that manages this tracee.
std::optional< cosmos::ChildState > m_exit_data
If tracee exit was observed then this contains the final exit data.
Flags m_flags
These keep track of various state on the tracer side.
void readVector(const ForeignPtr pointer, VECTOR &out) const
Reads in a zero terminated array of data items into the STL-vector like parameter out.
cosmos::Tracee m_ptrace
libcosmos API for the Tracee.
void processEvent(const cosmos::ChildState &data)
Process the given ptrace event.
long getData(const ForeignPtr addr) const
Reads a word of data from the tracee's memory.
std::optional< SystemCallNr > currentSystemCallNr() const
Returns the number of the currently running system call, if any.
std::optional< SystemCallInfo > m_syscall_info
The current system call information, if any.
static constexpr std::array< cosmos::Signal, 4 > STOPPING_SIGNALS
Array of signals that cause tracee stop.
State
Current tracing state for a single tracee.
@ DETACHED
we already detached from the tracee
@ SYSCALL_ENTER_STOP
system call started.
@ SYSCALL_EXIT_STOP
system call finished.
@ RUNNING
tracee is running normally / not in a special trace state.
@ SIGNAL_DELIVERY_STOP
signal was delivered.
@ UNKNOWN
initial PTRACE_SIZE / PTRACE_INTERRUPT.
@ EVENT_STOP
special ptrace event occurred.
@ DEAD
the tracee no longer exists.
@ GROUP_STOP
SIGSTOP executed, the tracee is stopped.
void readBlob(const ForeignPtr addr, char *buffer, const size_t bytes) const
Reads an arbitrary binary blob of fixed length from the tracee.
SystemCall * m_interrupted_syscall
Previous system call, if it has been interrupted.
void readString(const ForeignPtr addr, std::string &out) const
Reads a zero-terminated C-string from the tracee.
State m_state
The current state the tracee is in.
std::optional< cosmos::Signal > m_inject_sig
signal to inject upon next restart of the tracee.
std::optional< cosmos::ProcessID > m_initial_attacher
If this Tracee was automatically attached due to AttachThreads{true} then this contains the ProcessID...
virtual bool isChildProcess() const
Returns whether the tracee is a child process created by us.
cosmos::Tracee::RestartMode m_restart_mode
current RestartMode to use.
virtual void attach(const FollowChildren follow_children, const AttachThreads attach_threads=AttachThreads{false})
Logic to handle attaching to the tracee.
EventConsumer & m_consumer
Callback interface receiving our information.
std::optional< SystemCallNr > getInitialSyscallNr(const ABI abi) const
Returns the initial system call nr. stored in m_initial_regset, if available for abi.
void getRegisters(RegisterSet< abi > &rs)
Reads the current register set from the process.
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 fillData(ForeignPtr addr, FILLER &filler) const
Reads data from the Tracee starting at addr and feeds it to filler until it's saturated.
AnyRegisterSet m_initial_regset
Register set observed during initial attach event stop.
State m_prev_state
The previous Tracee state we've seen (except RUNNING).
SystemCallDB m_syscall_db
Reusable database object for tracing system calls.
const std::optional< cosmos::CloneArgs > & args() const
Returns an optional containing the cosmos::CloneArgs structure, if available.
cosmos::NamedBool< struct attach_threads_t, true > AttachThreads
A strong boolean type denoting whether to automatically all other threads of a process.
const char * get_ptrace_event_str(const cosmos::ptrace::Event event)
Returns a string label for the given event.
constexpr ABI get_default_abi()
Returns the default ABI for this system.
constexpr uint64_t X32_SYSCALL_BIT
Native system call numbers as used by Linux on the x32 ABI.
std::vector< FDInfo > get_fd_infos(const cosmos::ProcessID pid)
Obtain detailed information about currently open file descriptors according to /proc/<pid>/fd.
SystemCallNr
Abstract system call number usable across architectures and ABIs.
cosmos::NamedBool< struct follow_children_t, true > FollowChildren
A strong boolean type denoting whether to automatically attach to newly created child processes.
ForeignPtr
Strongly typed opaque pointer to tracee memory.
std::set< cosmos::FileNum > get_currently_open_fds(const cosmos::ProcessID pid)
Returns the currently open file descriptors according to /proc/<pid>/fd.
item::CloneArgs cl_args
Combined clone arguments.
Wrapper for the clone() and clone2() system calls.
Contextual information about a file descriptor in a Tracee.