libclues
Linux C++ Tracing Library
Loading...
Searching...
No Matches
clues::TermTracer Class Reference
+ Inheritance diagram for clues::TermTracer:

Public Member Functions

 TermTracer (const TermTracer &)=delete
 
TermTraceroperator= (const TermTracer &)=delete
 

Protected Types

enum class  FollowExecContext {
  YES , NO , ASK , CHECK_PATH ,
  CHECK_GLOB
}
 What to do upon execve. More...
 
enum class  FollowChildMode { YES , NO , ASK , THREADS }
 
enum class  Flag { SEEN_INITIAL_EXEC = 1 << 0 , DROPPED_TO_LAST_TRACEE = 1 << 1 }
 
using Flags = cosmos::BitMask<Flag>
 

Protected Member Functions

bool processPars ()
 
void printSyscalls ()
 
void printABISyscalls (const std::string &abi)
 
void printABIs ()
 
cosmos::ExitStatus main (const int argc, const char **argv) override
 
bool configureTracee (const cosmos::ProcessID pid)
 
void runTrace ()
 
void configureLogger ()
 
std::string formatTraceeInvocation (const Tracee &tracee)
 
std::string formatTraceeInvocation (const std::string &exe, const cosmos::StringVector &cmdline) const
 
void printPar (std::ostream &trace, const SystemCallItem &value) const
 
void printParsOnEntry (std::ostream &trace, const SystemCall::ParameterVector &pars) const
 
void printParsOnExit (std::ostream &trace, const SystemCall::ParameterVector &pars) const
 
bool followExecutionContext (Tracee &tracee)
 
std::ostream & traceStream (const Tracee &tracee, const bool new_line=true)
 Returns the currently active trace output stream, starting a new output line.
 
void startNewLine (std::ostream &trace, const Tracee &tracee)
 Start a new output line concerning `tracee.
 
bool storeUnfinishedSyscallCtx ()
 Store an active system call in m_unfinished_syscalls.
 
void checkResumedSyscall (const Tracee &tracee)
 Check whether tracee has an unfinished system call pending.
 
bool hasActiveSyscall (const Tracee &tracee) const
 
bool hasActiveSyscall (const cosmos::ProcessID pid) const
 
const SystemCallactiveSyscall (const cosmos::ProcessID pid) const
 
const SystemCallactiveSyscall (const Tracee &tracee) const
 
const SystemCallactiveSyscall () const
 
const SystemCallfindSyscall (const Tracee &tracee) const
 Find any active or unfinished system call for pid.
 
bool isExecSyscall (const SystemCall &sc) const
 
bool isEnabled (const SystemCall *sc) const
 Returns true if sc is set and supposed to the printed.
 
const SystemCallcurrentSyscall (const Tracee &tracee) const
 Returns the system call last seen for tracee, or nullptr if there's none.
 
void cleanupTracee (const Tracee &tracee)
 
void updateTracee (const Tracee &tracee, const cosmos::ProcessID old_pid)
 Update internal data structures in case a tracee changed PID.
 
bool seenInitialExec () const
 
void abortSyscall (const Tracee &tracee)
 Abort syscall if one was active for tracee.
 
void checkABI (const Tracee &tracee, const SystemCallInfo &info)
 Checks the current system call's ABI and reports ABI changes.
 
void syscallEntry (Tracee &tracee, const SystemCall &sc, const StatusFlags flags) override
 A system call is about to be executed in the Tracee.
 
void syscallExit (Tracee &tracee, const SystemCall &sc, const StatusFlags flags) override
 A system call has been finished.
 
void signaled (Tracee &tracee, const cosmos::SigInfo &info) override
 The tracee has received a signal.
 
void attached (Tracee &tracee) override
 The tracee is now properly attached to.
 
void exited (Tracee &tracee, const cosmos::WaitStatus status, const StatusFlags flags) override
 The tracee is about to end execution.
 
void newExecutionContext (Tracee &tracee, const std::string &old_exe, const cosmos::StringVector &old_cmdline, const std::optional< cosmos::ProcessID > old_pid) override
 A new program is executed in the tracee.
 
void newChildProcess (Tracee &parent, Tracee &child, const cosmos::ptrace::Event event, const StatusFlags flags) override
 A new child process has been created.
 
void stopped (Tracee &tracee) override
 The tracee entered group-stop due to a stopping signal.
 
void disappeared (Tracee &tracee, const cosmos::ChildState &data) override
 The tracee disappeared for unclear reasons.
 
- Protected Member Functions inherited from clues::EventConsumer
virtual void resumed (Tracee &tracee)
 The tracee resumed due to SIGCONT.
 
virtual void vforkComplete (Tracee &parent, TraceePtr child)
 A vfork() in parent for child completed.
 

Protected Attributes

Args m_args
 Command line arguments and parser.
 
cosmos::StdLogger m_logger
 cosmos ILogger instance for clues library logging.
 
cosmos::Init m_cosmos
 
Engine m_engine
 libclues main object.
 
bool m_print_pars = true
 Whether to print system call parameters at all (-s 0 disables it).
 
size_t m_par_truncation_len = 64
 Maximum length of of system call parameter values to print before truncating the output.
 
std::string m_exec_context_arg
 optional argument to m_follow_exec (e.g. path, glob, script)
 
std::set< SystemCallNrm_syscall_filter
 Whitelist of system calls to trace, if any.
 
FollowExecContext m_follow_exec = FollowExecContext::YES
 Behaviour upon newExecutionContext()
 
FollowChildMode m_follow_children = FollowChildMode::NO
 Behaviour upon newChildProcess()
 
size_t m_num_tracees = 0
 The number of tracees we're currently dealing with.
 
Flags m_flags
 State flags with global context or carried between different callbacks.
 
cosmos::ProcessID m_main_tracee_pid
 The PID of the main process we're tracing (the one we created or attached to).
 
std::optional< cosmos::WaitStatus > m_main_status
 The WaitStatus of the main process we've seen upon it exiting.
 
std::optional< std::tuple< cosmos::ProcessID, const SystemCall * > > m_active_syscall
 The currently active system call, if any.
 
std::map< cosmos::ProcessID, const SystemCall * > m_unfinished_syscalls
 Unfinished / preempted system calls.
 
std::map< cosmos::ProcessID, std::pair< cosmos::ProcessID, cosmos::ptrace::Event > > m_new_tracees
 Newly created tracees that haven't seen any ptrace stop yet.
 
clues::ABI m_last_abi = clues::ABI::UNKNOWN
 The ABI of the last system call we've seen.
 

Additional Inherited Members

- Public Types inherited from clues::EventConsumer
enum class  StatusFlag {
  INTERRUPTED = 1 << 0 , RESUMED = 1 << 1 , LOST_TO_MT_EXIT = 1 << 2 , ABI_CHANGED = 1 << 3 ,
  CLONED_THREAD = 1 << 4
}
 Different status flags that can appear in callbacks. More...
 
using StatusFlags = cosmos::BitMask<StatusFlag>
 

Detailed Description

Definition at line 25 of file TermTracer.hxx.

Member Typedef Documentation

◆ Flags

using clues::TermTracer::Flags = cosmos::BitMask<Flag>
protected

Definition at line 58 of file TermTracer.hxx.

Member Enumeration Documentation

◆ Flag

enum class clues::TermTracer::Flag
strongprotected
Enumerator
SEEN_INITIAL_EXEC 

whether we've seen a ChildTracee's initial newExecutionContext().

DROPPED_TO_LAST_TRACEE 

whether we've returned to tracing only a single PID anymore.

Definition at line 53 of file TermTracer.hxx.

53 {
54 SEEN_INITIAL_EXEC = 1 << 0,
55 DROPPED_TO_LAST_TRACEE = 1 << 1,
56 };

◆ FollowChildMode

enum class clues::TermTracer::FollowChildMode
strongprotected

Definition at line 46 of file TermTracer.hxx.

46 {
47 YES,
48 NO,
49 ASK,
50 THREADS
51 };

◆ FollowExecContext

enum class clues::TermTracer::FollowExecContext
strongprotected

What to do upon execve.

Definition at line 38 of file TermTracer.hxx.

38 {
39 YES,
40 NO,
41 ASK,
42 CHECK_PATH,
43 CHECK_GLOB
44 };

Constructor & Destructor Documentation

◆ TermTracer()

clues::TermTracer::TermTracer ( )

Definition at line 65 of file TermTracer.cxx.

65 :
66 m_engine{*this} {
67}
Engine m_engine
libclues main object.

Member Function Documentation

◆ abortSyscall()

void clues::TermTracer::abortSyscall ( const Tracee & tracee)
protected

Abort syscall if one was active for tracee.

A condition in tracee occurred which requires to abort a possible already started system call trace. This function performs the necessary steps to do so.

Definition at line 741 of file TermTracer.cxx.

741 {
742 // obtain this information before the active syscall is reset below
743 const auto is_enabled = isEnabled(currentSyscall(tracee));
744
745 if (hasActiveSyscall(tracee)) {
746 m_active_syscall.reset();
747 } else if (is_enabled) {
748 checkResumedSyscall(tracee);
749 }
750
751 if (is_enabled) {
752 /*
753 * finish any possibly pending system call to make clear it
754 * never visibly returned.
755 */
756 traceStream(tracee, false) << ") = ?\n";
757 }
758}
std::optional< std::tuple< cosmos::ProcessID, const SystemCall * > > m_active_syscall
The currently active system call, if any.
const SystemCall * currentSyscall(const Tracee &tracee) const
Returns the system call last seen for tracee, or nullptr if there's none.
std::ostream & traceStream(const Tracee &tracee, const bool new_line=true)
Returns the currently active trace output stream, starting a new output line.
void checkResumedSyscall(const Tracee &tracee)
Check whether tracee has an unfinished system call pending.
bool isEnabled(const SystemCall *sc) const
Returns true if sc is set and supposed to the printed.

◆ activeSyscall() [1/3]

const SystemCall * clues::TermTracer::activeSyscall ( ) const
inlineprotected

Definition at line 144 of file TermTracer.hxx.

144 {
145 if (!m_active_syscall)
146 return nullptr;
147
148 return std::get<const SystemCall*>(*m_active_syscall);
149 }

◆ activeSyscall() [2/3]

const SystemCall * clues::TermTracer::activeSyscall ( const cosmos::ProcessID pid) const
inlineprotected

Definition at line 127 of file TermTracer.hxx.

127 {
128 if (!m_active_syscall)
129 return nullptr;
130
131 const auto info = *m_active_syscall;
132
133 if (std::get<cosmos::ProcessID>(info) != pid) {
134 return nullptr;
135 }
136
137 return std::get<const SystemCall*>(info);
138 }

◆ activeSyscall() [3/3]

const SystemCall * clues::TermTracer::activeSyscall ( const Tracee & tracee) const
inlineprotected

Definition at line 140 of file TermTracer.hxx.

140 {
141 return activeSyscall(tracee.pid());
142 }

◆ attached()

void clues::TermTracer::attached ( Tracee & tracee)
overrideprotectedvirtual

The tracee is now properly attached to.

This should be the first call that that is visible for a tracee. It occurs when the ptrace() relationship is established and a defined tracing state has been reached. The Tracee will be in PTRACE_EVENT_STOP.

The following situations can cause an attached() callback:

  • tracee was actively registered via Engine::addTracee().
  • an already present tracee created a new thread or child process. This is preceded by newChildProcess().
  • an existing process has been attached to via Engine::addTracee(const cosmos::ProcessID, ..., AttachThreads{true}). Any additional threads that exist in the process will then automatically be attached to. tracee.isInitiallyAttachedThread() will return true in this case.

Reimplemented from clues::EventConsumer.

Definition at line 447 of file TermTracer.cxx.

447 {
448 if (m_num_tracees++ == 0) {
449 // the initial process, don't print anything special in this case
450 return;
451 } else if (tracee.isInitiallyAttachedThread()) {
452 // part of the initial attach procedure when attaching to a
453 // possibly multi-threaded existing process.
454 traceStream(tracee) << "→ additionally attached thread\n";
455 } else if (auto it = m_new_tracees.find(tracee.pid()); it != m_new_tracees.end()) {
456 auto [parent, event] = it->second;
457
458 traceStream(tracee) << "→ automatically attached (created by PID " << parent << " via " << to_label(event) << ")\n";
459 m_new_tracees.erase(it);
460 } else {
461 traceStream(tracee) << "unknown Tracee " << cosmos::to_integral(tracee.pid()) << " attached?!";
462 }
463}
std::map< cosmos::ProcessID, std::pair< cosmos::ProcessID, cosmos::ptrace::Event > > m_new_tracees
Newly created tracees that haven't seen any ptrace stop yet.
size_t m_num_tracees
The number of tracees we're currently dealing with.

◆ checkABI()

void clues::TermTracer::checkABI ( const Tracee & tracee,
const SystemCallInfo & info )
protected

Checks the current system call's ABI and reports ABI changes.

Definition at line 760 of file TermTracer.cxx.

760 {
761 bool report_abi = false;
762
763 if (m_last_abi == clues::ABI::UNKNOWN) {
764 // check if the initial system call already has some
765 // non-default ABI.
766 if (!clues::is_default_abi(info.abi()))
767 report_abi = true;
768 } else if (info.abi() != m_last_abi) {
769 report_abi = true;
770 }
771
772 if (report_abi) {
773 traceStream(tracee) << "[system call ABI changed to " << get_abi_label(info.abi()) << "]\n";
774 }
775}
clues::ABI m_last_abi
The ABI of the last system call we've seen.
bool is_default_abi(const ABI abi)
Returns whether the given ABI is default ABI for this system.
Definition utils.cxx:280

◆ checkResumedSyscall()

void clues::TermTracer::checkResumedSyscall ( const Tracee & tracee)
protected

Check whether tracee has an unfinished system call pending.

If tracee has an unfinished system call pending in m_unfinished_syscalls, then clean up the data structure and output appropriate text to indicate the situation.

Definition at line 686 of file TermTracer.cxx.

686 {
687 if (hasActiveSyscall(tracee))
688 return;
689
690 /*
691 * when there's no active system call then we expect one to be stored
692 * as unfinished, otherwise something is off.
693 */
694
695 auto node = m_unfinished_syscalls.extract(tracee.pid());
696 assert(!node.empty());
697 auto sc = node.mapped();
698
699 auto &trace = traceStream(tracee);
700 trace << "<resuming ...> " << sc->name();
701 if (sc->hasOutParameter()) {
702 trace << "(..., ";
703 } else {
704 trace << "(...";
705 }
706}
std::map< cosmos::ProcessID, const SystemCall * > m_unfinished_syscalls
Unfinished / preempted system calls.

◆ cleanupTracee()

void clues::TermTracer::cleanupTracee ( const Tracee & tracee)
protected

Definition at line 708 of file TermTracer.cxx.

708 {
709 /* remove the given tracee from all bookkeeping */
710 if (hasActiveSyscall(tracee)) {
711 m_active_syscall.reset();
712 } else {
713 m_unfinished_syscalls.erase(tracee.pid());
714 }
715
716 m_new_tracees.erase(tracee.pid());
717
718 if (--m_num_tracees == 1) {
720 }
721}
Flags m_flags
State flags with global context or carried between different callbacks.
@ DROPPED_TO_LAST_TRACEE
whether we've returned to tracing only a single PID anymore.

◆ configureLogger()

void clues::TermTracer::configureLogger ( )
protected

Definition at line 250 of file TermTracer.cxx.

250 {
251 // enable errors and warnings by default
252 m_logger.setChannels(true, true, false, false);
253 m_logger.configFromEnvVar("CLUES_LOGGING");
255}
cosmos::StdLogger m_logger
cosmos ILogger instance for clues library logging.
void set_logger(cosmos::ILogger &_logger)
Configure a cosmos ILogger instance to use in the Clues library.
Definition logger.cxx:7

◆ configureTracee()

bool clues::TermTracer::configureTracee ( const cosmos::ProcessID pid)
protected

Definition at line 777 of file TermTracer.cxx.

777 {
778 // this is only for newly created child processes, so set it right away
780 TraceePtr tracee;
781 try {
782 /* strace ties the attach threads behaviour to `-f`. There
783 * can be situations when this is not helpful. Thus we do the
784 * same but offer a `-1` switch to opt out of this and only
785 * attach to the single thread specified on the command line.
786 */
787 tracee = m_engine.addTracee(pid,
788 FollowChildren{m_follow_children == FollowChildMode::NO ? false : true},
789 AttachThreads{m_follow_children == FollowChildMode::NO || m_args.no_initial_threads_attach.isSet() ? false : true});
790 } catch (const cosmos::ApiError &e) {
791 std::cerr << "Failed to attach to PID " << pid << ": " << e.msg() << "\n";
792 if (e.errnum() == cosmos::Errno::PERMISSION) {
793 std::cerr << "You need to be root to attach to processes not owned by you\n";
794 std::cerr << "The YAMA kernel security extension can also prevent attaching your own processes.\n";
795 std::cerr << "This also happens when another process is already tracing this process.\n";
796 }
797 return false;
798 }
799
800 traceStream(*tracee, false) << "--- tracing " << formatTraceeInvocation(*tracee) << " ---\n";
801
802 m_main_tracee_pid = tracee->pid();
803
804 return true;
805}
Args m_args
Command line arguments and parser.
FollowChildMode m_follow_children
Behaviour upon newChildProcess()
@ SEEN_INITIAL_EXEC
whether we've seen a ChildTracee's initial newExecutionContext().
cosmos::ProcessID m_main_tracee_pid
The PID of the main process we're tracing (the one we created or attached to).
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

◆ currentSyscall()

const SystemCall * clues::TermTracer::currentSyscall ( const Tracee & tracee) const
protected

Returns the system call last seen for tracee, or nullptr if there's none.

This checks both the currently active as well as any unfinished system calls for tracee.

Definition at line 660 of file TermTracer.cxx.

660 {
661 if (m_active_syscall) {
662 auto &[pid, syscall] = *m_active_syscall;
663
664 if (pid == tracee.pid()) {
665 return syscall;
666 }
667 }
668
669 auto it = m_unfinished_syscalls.find(tracee.pid());
670 if (it != m_unfinished_syscalls.end()) {
671 return it->second;
672 }
673
674 return nullptr;
675}

◆ disappeared()

void clues::TermTracer::disappeared ( Tracee & tracee,
const cosmos::ChildState & data )
overrideprotectedvirtual

The tracee disappeared for unclear reasons.

This callback can occur in a number of special situations like:

  • another thread of the same process called exit(). This is not always caught as an exit event, which would lead to a call to exited(). This is especially the case when the other thread which calls exit() is not also traced.
  • generally the ptrace() documentation says that a tracee can disappear at any time without warning, which is unlucky. libclues attempts to provide as much contextual information as possible. In the case of disappeared(), the cause remains uncertain, however.

When this call occurs then the tracee can no longer be accessed via the tracing API. data still tells whether the tracee exited regularly or due to a signal.

Reimplemented from clues::EventConsumer.

Definition at line 497 of file TermTracer.cxx.

497 {
498 abortSyscall(tracee);
499
500 traceStream(tracee) << "!!! disappeared (e.g. exit() or execve() in another thread)\n";
501
502 if (data.exited()) {
503 traceStream(tracee) << "+++ exited with " << cosmos::to_integral(*data.status) << " +++\n";
504 } else {
505 traceStream(tracee) << "+++ killed by signal " << cosmos::to_integral(data.signal->raw()) << " +++\n";
506 }
507
508 if (tracee.pid() == m_main_tracee_pid) {
509 /*
510 * Here we get a more complex cosmos::ChildState instead of
511 * WaitStatus as in the other callbacks. Convert it into a
512 * WaitStatus so that we can treat all exit paths
513 * homogeneously.
514 */
515 m_main_status = cosmos::WaitStatus{data};
516 }
517
518 cleanupTracee(tracee);
519}
void abortSyscall(const Tracee &tracee)
Abort syscall if one was active for tracee.
std::optional< cosmos::WaitStatus > m_main_status
The WaitStatus of the main process we've seen upon it exiting.

◆ exited()

void clues::TermTracer::exited ( Tracee & tracee,
const cosmos::WaitStatus status,
const StatusFlags flags )
overrideprotectedvirtual

The tracee is about to end execution.

The tracee is about to either exit regularly, to be killed by a signal or to disappear due to an execve() in a multi-threaded process.

When this callback occurs the tracee can still be inspected using ptrace(). Once execution is continued the tracer <-> tracee relationship is lost.

If the exit happens due to an execve in a multi-threaded process then Status::LOST_TO_MT_EXIT is set in flags.

Reimplemented from clues::EventConsumer.

Definition at line 465 of file TermTracer.cxx.

465 {
466 if (tracee.prevState() == Tracee::State::SYSCALL_ENTER_STOP) {
467 abortSyscall(tracee);
468 }
469
470 if (flags[StatusFlag::LOST_TO_MT_EXIT]) {
471 auto &trace = traceStream(tracee);
472 trace << "--- <lost to exit or execve in another thread> ---\n";
473 }
474
475 if (status.exited()) {
476 traceStream(tracee) << "+++ exited with " << cosmos::to_integral(*status.status()) << " +++\n";
477 if (!seenInitialExec()) {
478 traceStream(tracee) << "!!! failed to execute the specified program\n";
479 }
480 } else {
481 traceStream(tracee) << "+++ killed by signal " << cosmos::to_integral(status.termSig()->raw()) << " +++\n";
482 }
483
484 if (tracee.pid() == m_main_tracee_pid) {
485 m_main_status = status;
486 }
487
488 cleanupTracee(tracee);
489}
@ LOST_TO_MT_EXIT
An exit occurs because another thread called execve() or exit() (only appears in exited()).
@ SYSCALL_ENTER_STOP
system call started.
Definition Tracee.hxx:51

◆ findSyscall()

const SystemCall * clues::TermTracer::findSyscall ( const Tracee & tracee) const
protected

Find any active or unfinished system call for pid.

Since system calls can be preempted by other tracees we sometimes need to lookup a SystemCall instance in both, the currently active system call and the unfinished system calls. This is mostly relevant during PTRACE_EVENT stops.

If no entry is found for pid, then nullptr is returned.

Definition at line 640 of file TermTracer.cxx.

640 {
641 if (const auto syscall = activeSyscall(tracee); syscall)
642 return syscall;
643
644 if (auto it = m_unfinished_syscalls.find(tracee.pid()); it != m_unfinished_syscalls.end()) {
645 return it->second;
646 }
647
648 return nullptr;
649}

◆ followExecutionContext()

bool clues::TermTracer::followExecutionContext ( Tracee & tracee)
protected

Definition at line 584 of file TermTracer.cxx.

584 {
585 switch (m_follow_exec) {
586 case FollowExecContext::YES: return true;
587 case FollowExecContext::NO: return false;
588 case FollowExecContext::ASK: {
589 std::cout << "Follow into new execution context?\n";
590 return ask_yes_no();
591 } case FollowExecContext::CHECK_PATH: {
592 return m_exec_context_arg == tracee.executable();
593 } case FollowExecContext::CHECK_GLOB: {
594 return ::fnmatch(m_exec_context_arg.c_str(), tracee.executable().c_str(), 0) == 0;
595 }
596 default:
597 return false;
598 }
599}
std::string m_exec_context_arg
optional argument to m_follow_exec (e.g. path, glob, script)
FollowExecContext m_follow_exec
Behaviour upon newExecutionContext()

◆ formatTraceeInvocation() [1/2]

std::string clues::TermTracer::formatTraceeInvocation ( const std::string & exe,
const cosmos::StringVector & cmdline ) const
protected

Definition at line 333 of file TermTracer.cxx.

334 {
335 std::stringstream ss;
336 ss << exe << " [";
337 bool first = true;
338 for (const auto &arg: cmdline) {
339 if (first) {
340 first = false;
341 } else {
342 ss << ", ";
343 }
344 ss << "\"" << arg << "\"";
345 }
346
347 ss << "]";
348
349 return ss.str();
350}

◆ formatTraceeInvocation() [2/2]

std::string clues::TermTracer::formatTraceeInvocation ( const Tracee & tracee)
protected

Definition at line 329 of file TermTracer.cxx.

329 {
330 return formatTraceeInvocation(tracee.executable(), tracee.cmdLine());
331}

◆ hasActiveSyscall() [1/2]

bool clues::TermTracer::hasActiveSyscall ( const cosmos::ProcessID pid) const
inlineprotected

Definition at line 123 of file TermTracer.hxx.

123 {
124 return activeSyscall(pid) != nullptr;
125 }

◆ hasActiveSyscall() [2/2]

bool clues::TermTracer::hasActiveSyscall ( const Tracee & tracee) const
inlineprotected

Definition at line 119 of file TermTracer.hxx.

119 {
120 return hasActiveSyscall(tracee.pid());
121 }

◆ isEnabled()

bool clues::TermTracer::isEnabled ( const SystemCall * sc) const
protected

Returns true if sc is set and supposed to the printed.

Definition at line 677 of file TermTracer.cxx.

677 {
678 if (!sc)
679 return false;
680 else if (m_syscall_filter.empty())
681 return true;
682
683 return m_syscall_filter.count(sc->callNr()) != 0;
684}
std::set< SystemCallNr > m_syscall_filter
Whitelist of system calls to trace, if any.

◆ isExecSyscall()

bool clues::TermTracer::isExecSyscall ( const SystemCall & sc) const
protected

Definition at line 651 of file TermTracer.cxx.

651 {
652 switch (sc.callNr()) {
653 default: return false;
654 case SystemCallNr::EXECVE:
655 case SystemCallNr::EXECVEAT:
656 return true;
657 }
658}

◆ main()

cosmos::ExitStatus clues::TermTracer::main ( const int argc,
const char ** argv )
overrideprotected

Definition at line 807 of file TermTracer.cxx.

807 {
808 constexpr auto FAILURE = cosmos::ExitStatus::FAILURE;
809 m_args.cmdline.parse(argc, argv);
810
811 configureLogger();
812
813 if (!processPars()) {
814 return FAILURE;
815 }
816
817 cosmos::signal::block(cosmos::SigSet{cosmos::signal::CHILD});
818
819 if (m_args.attach_proc.isSet()) {
820 if (!configureTracee(cosmos::ProcessID{m_args.attach_proc.getValue()})) {
821 return FAILURE;
822 }
823 } else {
824 // extract any additional arguments into a StringVector
825 cosmos::StringVector sv;
826 bool found_sep = false;
827
828 for (auto arg = 1; arg < argc; arg++) {
829 if (found_sep) {
830 sv.push_back(argv[arg]);
831 } else if (std::string{argv[arg]} == "--") {
832 found_sep = true;
833 }
834 }
835
836 if (sv.empty()) {
837 std::cerr << "Neither -p nor command to execute after '--' was given. Nothing to do.\n";
838 return FAILURE;
839 }
840
841 try {
842 auto tracee = m_engine.addTracee(sv,
843 FollowChildren{m_follow_children == FollowChildMode::NO ? false : true});
844 m_main_tracee_pid = tracee->pid();
845 } catch (const cosmos::CosmosError &ex) {
846 std::cerr << ex.shortWhat() << "\n";
847 return FAILURE;
848 }
849 }
850
851 try {
852 m_engine.trace();
853 } catch (const std::exception &ex) {
854 std::cerr << "internal tracing error: " << ex.what() << std::endl;
855 // try to do a hard exit to avoid blocking in the Engine's
856 // destructor, should the state be messed up
857 cosmos::proc::exit(FAILURE);
858 }
859
860 auto status = cosmos::ExitStatus::SUCCESS;
861
862 /*
863 * Evaluate exit code of the tracee, valid only after detach().
864 *
865 * We mimic the behaviour of the tracee by returning the same exit
866 * status.
867 */
868 if (m_main_status) {
869 if (m_main_status->exited()) {
870 status = *m_main_status->status();
871 } else if (m_main_status->signaled()) {
872 // this is what the shell returns for children that have been killed.
873 // TODO: strace actually sends the same kill signal to iself
874 // I don't know whether this is very useful. It can
875 // cause core dumps of the tracer, thus we'd need to
876 // change ulimit to prevent that.
877 status = cosmos::ExitStatus{128 + cosmos::to_integral(m_main_status->termSig()->raw())};
878 }
879 }
880
881 return status;
882}

◆ newChildProcess()

void clues::TermTracer::newChildProcess ( Tracee & parent,
Tracee & child,
const cosmos::ptrace::Event event,
const StatusFlags flags )
overrideprotectedvirtual

A new child process has been created.

The existing tracee parent has created a new child process, which has automatically been attached to and is now represented in child.

Different ways to create a child process exist, these are differentiated via event. Note that some forms of clone() calls will appear as cosmos::ptrace::Event::FORK or cosmos::ptrace::Event::VFORK instead of cosmos::ptrace::Event::CLONE (when the clone() exit signal is set to SIGCHILD).

cosmos::ptrace::Event::VFORK_DONE occurs when the child process exited or exec()'d and can be ignored if there is no need for it.

StatusFlag::CLONED_THREAD will be set in flags in case the new child process is a thread of parent.

Reimplemented from clues::EventConsumer.

Definition at line 564 of file TermTracer.cxx.

565 {
566 auto follow = m_follow_children;
567
568 if (follow == FollowChildMode::ASK) {
569 traceStream(parent) << "Follow into new child process created by PID " << parent.pid() << " via " << to_label(event) << "?\n";
570 std::cout << "PID " << parent.pid() << " is " << formatTraceeInvocation(parent) << "\n";
571 follow = ask_yes_no() ? FollowChildMode::YES : FollowChildMode::NO;
572 } else if (follow == FollowChildMode::THREADS) {
573 follow = flags[StatusFlag::CLONED_THREAD] ? FollowChildMode::YES : FollowChildMode::NO;
574 }
575
576 if (follow == FollowChildMode::YES) {
577 // keep this information for later when something actually happens in the new child
578 m_new_tracees.insert({child.pid(), {parent.pid(), event}});
579 } else {
580 child.detach();
581 }
582}
@ CLONED_THREAD
used in newChildProcess() to indicate that a new thread has been created.

◆ newExecutionContext()

void clues::TermTracer::newExecutionContext ( Tracee & tracee,
const std::string & old_executable,
const cosmos::StringVector & old_cmdline,
const std::optional< cosmos::ProcessID > old_pid )
overrideprotectedvirtual

A new program is executed in the tracee.

This call occurs after a successful execve() by the tracee.

The new executable path and command line can be retrieved via Tracee::executable() and Tracee::cmdLine(). The previous values are provided as input parameters.

The callee can decide to stop tracing (by detaching) at this point to prevent following the new tracing context.

If the tracee's process was multi-threaded then there is more complexity involved:

  • all but one thread will have exited at this point. Only the main process ID (main thread PID) will remain.
  • The thread that caused the execve() can be a different thread. In this case the different thread will receive a new PID personality (it will receive the main thread PID).
  • In case of a personality change, former_pid contains the former PID that the exec'ing thread had, which is now continuing as the main thread of the new execution context.
  • the current implementation of libclues will attempt to reuse the main thread's Tracee object even if a personality change happened. If the main thread was not traced at all then the exec()'ing thread's Tracee object will be reused. Due to this it is best to rely on the PIDs only (not Tracee pointers) to identify the new/old context.

Reimplemented from clues::EventConsumer.

Definition at line 521 of file TermTracer.cxx.

524 {
525 if (old_pid) {
526 // this needs to be done first to avoid any inconsistent state down below.
527 updateTracee(tracee, *old_pid);
528 }
529
530 // cache this state here, because checkResumedSyscall() might alter the info
531 const auto is_enabled = isEnabled(currentSyscall(tracee));
532
533 if (is_enabled) {
534 checkResumedSyscall(tracee);
535 /* anticipate the success system call status to avoid
536 * complexities while outputting status messages and
537 * interactive dialogs. */
538 traceStream(tracee, false) << ") = 0 (success)\n";
539 }
540
541 m_active_syscall = {};
542
543 if (old_pid) {
544 traceStream(tracee) << "--- PID " << *old_pid << " is now known as PID " << tracee.pid() << " ---\n";
545 }
546
547 // skip the output for the initial exec of a new child process,
548 // because there's no interesting information in that
549 if (seenInitialExec()) {
550 traceStream(tracee) << "--- no longer running " << formatTraceeInvocation(old_exe, old_cmdline) << " ---\n";
551 }
552 traceStream(tracee) << "--- now running " << formatTraceeInvocation(tracee) << " ---\n";
553
554 if (!seenInitialExec()) {
556 } else {
557 if (!followExecutionContext(tracee)) {
558 traceStream(tracee) << "--- detaching after execve ---\n";
559 tracee.detach();
560 }
561 }
562}
void updateTracee(const Tracee &tracee, const cosmos::ProcessID old_pid)
Update internal data structures in case a tracee changed PID.

◆ printABIs()

void clues::TermTracer::printABIs ( )
protected

Definition at line 244 of file TermTracer.cxx.

244 {
245 for (const auto abi: clues::get_supported_abis()) {
246 std::cout << clues::get_abi_label(abi) << "\n";
247 }
248}
std::array< ABI, SUPPORTED_ABIS > get_supported_abis()
Returns a list of ABIs supported on the current platform.
Definition utils.cxx:299

◆ printABISyscalls()

void clues::TermTracer::printABISyscalls ( const std::string & abi)
protected

Definition at line 200 of file TermTracer.cxx.

200 {
201 auto print_syscalls = [](auto sysnr) {
202 using ABISystemCallNr = decltype(sysnr);
203 for (sysnr = ABISystemCallNr::_FIRST;
204 sysnr != ABISystemCallNr::_LAST;
205 sysnr = ABISystemCallNr{cosmos::to_integral(sysnr)+1}) {
206 /*
207 * convert to the generic system call number, this way
208 * we can get the system call name
209 */
210 auto gen_sysnr = clues::to_generic(sysnr);
211 if (gen_sysnr == clues::SystemCallNr::UNKNOWN)
212 // unassigned
213 continue;
214
215 auto name = clues::SYSTEM_CALL_NAMES[cosmos::to_integral(gen_sysnr)];
216
217 std::cout << name << " (";
218 if constexpr (std::is_same<ABISystemCallNr, SystemCallNrX32>::value) {
219 std::cout << "X32_SYSCALL_BIT + " << (cosmos::to_integral(sysnr) & (~clues::X32_SYSCALL_BIT));
220 } else {
221 std::cout << cosmos::to_integral(sysnr);
222 }
223 std::cout << ")\n";
224 }
225 };
226
227 clues::AnySystemCallNr sys_nr;
228
229 if (abi_str == "i386") {
230 sys_nr = SystemCallNrI386{};
231 } else if (abi_str == "x86-64") {
232 sys_nr = SystemCallNrX64{};
233 } else if (abi_str == "x32") {
234 sys_nr = SystemCallNrX32{};
235 } else if (abi_str == "aarch64") {
236 sys_nr = SystemCallNrAARCH64{};
237 } else {
238 throw cosmos::RuntimeError{"unexpected abi string encountered"};
239 }
240
241 std::visit(print_syscalls, sys_nr);
242}
constexpr uint64_t X32_SYSCALL_BIT
Native system call numbers as used by Linux on the x32 ABI.
Definition x32.hxx:38
SystemCallNrAARCH64
Native system call numbers as used by Linux on the aarch64 ABI.
Definition aarch64.hxx:31
SystemCallNrX64
Native system call numbers as used by Linux on the x64 ABI.
Definition x64.hxx:31
clues::SystemCallNr to_generic(const SystemCallNrAARCH64 nr)
Convert the native system call nr. into its generic representation.
Definition aarch64.cxx:21
SystemCallNrI386
Native system call numbers as used by Linux on the i386 ABI.
Definition i386.hxx:32

◆ printPar()

void clues::TermTracer::printPar ( std::ostream & trace,
const SystemCallItem & value ) const
protected

Definition at line 352 of file TermTracer.cxx.

352 {
353 trace << (m_args.verbose.isSet() ? par.longName() : par.shortName());
354
355 if (m_print_pars) {
356 auto value = par.str();
357
358 if (value.size() > m_par_truncation_len) {
359 value.resize(m_par_truncation_len);
360 value += "...";
361 }
362
363 trace << "=" << value;
364 }
365}
size_t m_par_truncation_len
Maximum length of of system call parameter values to print before truncating the output.
bool m_print_pars
Whether to print system call parameters at all (-s 0 disables it).

◆ printParsOnEntry()

void clues::TermTracer::printParsOnEntry ( std::ostream & trace,
const SystemCall::ParameterVector & pars ) const
protected

Definition at line 257 of file TermTracer.cxx.

257 {
258 /*
259 * This logic covers printing of parameters during system-call entry.
260 *
261 * During entry we can print all input-only parameters and stop once
262 * we see a non-input parameter or once we are finished with all
263 * parameters.
264 *
265 * During exit we continue printing from the first non-input parameter
266 * onwards. This way we can present at least some of the system call
267 * data already for blocking system calls.
268 *
269 * There exist system calls where the order of parameters is like
270 * this:
271 *
272 * <input>, <output>, <input>
273 *
274 * In this case, while the final parameter would still be printable,
275 * we will stop at the third parameter which is an output parameter.
276 * `strace` does this similarly. In theory we could print a '?' in
277 * this case for the not yet available output data, then do a
278 * carriage-return during syscall-exit to print the end result.
279 */
280
281 if (pars.empty())
282 return;
283
284 const auto &last = pars.back();
285
286 for (const auto &par: pars) {
287 if (clues::item::is_unused_par(*par)) {
288 continue;
289 } else if (!par->isIn()) {
290 // non-input parameter encountered, stop output
291 break;
292 }
293
294 printPar(trace, *par);
295
296 if (const auto is_last = &par == &last; !is_last) {
297 trace << ", ";
298 }
299 }
300}

◆ printParsOnExit()

void clues::TermTracer::printParsOnExit ( std::ostream & trace,
const SystemCall::ParameterVector & pars ) const
protected

Definition at line 302 of file TermTracer.cxx.

302 {
303 if (pars.empty())
304 return;
305
306 const auto &last = pars.back();
307 bool seen_non_input = false;
308
309 for (const auto &par: pars) {
310 if (clues::item::is_unused_par(*par)) {
311 continue;
312 } else if (!seen_non_input) {
313 if (par->isIn()) {
314 // this was already printed during entry
315 continue;
316 } else {
317 seen_non_input = true;
318 }
319 }
320
321 printPar(trace, *par);
322
323 if (const auto is_last = &par == &last; !is_last) {
324 trace << ", ";
325 }
326 }
327}

◆ printSyscalls()

void clues::TermTracer::printSyscalls ( )
protected

Definition at line 190 of file TermTracer.cxx.

190 {
191 // start at index one to skip the "UKNOWN" system call
192 for (size_t nr = 1; nr < SYSTEM_CALL_NAMES.size(); nr++) {
193 auto NAME = SYSTEM_CALL_NAMES[nr];
194 if (!NAME[0])
195 continue;
196 std::cout << NAME << "\n";
197 }
198}

◆ processPars()

bool clues::TermTracer::processPars ( )
protected

Definition at line 69 of file TermTracer.cxx.

69 {
70
71 auto handle_bad_arg = [](auto &arg) {
72 std::cerr << "bad argument to --" << arg.getName() << ": '" << arg.getValue() << "'\n";
73 return false;
74 };
75
76 if (m_args.list_syscalls.isSet()) {
77 printSyscalls();
78 throw cosmos::ExitStatus::SUCCESS;
79 } else if (m_args.list_abis.isSet()) {
80 printABIs();
81 throw cosmos::ExitStatus::SUCCESS;
82 } else if (m_args.list_abi_syscalls.isSet()) {
83 printABISyscalls(m_args.list_abi_syscalls.getValue());
84 throw cosmos::ExitStatus::SUCCESS;
85 }
86
87 if (const auto max_len = m_args.max_value_len.getValue(); max_len == 0)
88 m_print_pars = false;
89 else if (max_len < 0)
90 m_par_truncation_len = SIZE_MAX;
91 else
92 m_par_truncation_len = static_cast<size_t>(max_len);
93
94 if (m_args.follow_execve.isSet()) {
95 const auto &follow = m_args.follow_execve.getValue();
96 constexpr std::string_view PATH_PREFIX{"path:"};
97 constexpr std::string_view GLOB_PREFIX{"glob:"};
98
99 if (follow == "yes") {
100 m_follow_exec = FollowExecContext::YES;
101 } else if (follow == "no") {
102 m_follow_exec = FollowExecContext::NO;
103 } else if (follow == "ask") {
104 m_follow_exec = FollowExecContext::ASK;
105 } else if (cosmos::is_prefix(follow, PATH_PREFIX)) {
106 m_follow_exec = FollowExecContext::CHECK_PATH;
107 m_exec_context_arg = follow.substr(PATH_PREFIX.size());
108 } else if (cosmos::is_prefix(follow, GLOB_PREFIX)) {
109 m_follow_exec = FollowExecContext::CHECK_GLOB;
110 m_exec_context_arg = follow.substr(GLOB_PREFIX.size());
111 } else {
112 return handle_bad_arg(m_args.follow_execve);
113 }
114 }
115
116 if (m_args.follow_children.isSet() && m_args.follow_children_switch.isSet()) {
117 std::cerr << "cannot set both '-f' and '--follow-children'\n";
118 return false;
119 } else if (m_args.follow_children_switch.isSet()) {
120 m_follow_children = FollowChildMode::YES;
121 } else if (m_args.follow_children.isSet()) {
122 const auto &follow = m_args.follow_children.getValue();
123
124 if (follow == "yes") {
125 m_follow_children = FollowChildMode::YES;
126 } else if (follow == "no") {
127 m_follow_children = FollowChildMode::NO;
128 } else if (follow == "ask") {
129 m_follow_children = FollowChildMode::ASK;
130 } else if (follow == "threads") {
131 m_follow_children = FollowChildMode::THREADS;
132 } else {
133 return handle_bad_arg(m_args.follow_children);
134 }
135 }
136
137 if (m_args.follow_threads.isSet()) {
138 if (m_follow_children != FollowChildMode::NO) {
139 std::cerr << "cannot combine '--threads' with '-f' or '--follow-children'\n";
140 return false;
141 }
142
143 m_follow_children = FollowChildMode::THREADS;
144 }
145
146 if (m_args.syscall_filter.isSet()) {
147 auto parts = cosmos::split(m_args.syscall_filter.getValue(), ",");
148
149 auto translate_name = [](const std::string_view name) {
150 if (auto nr = lookup_system_call(name); nr) {
151 return *nr;
152 }
153
154 std::cerr << "invalid system call name: "
155 << name << "\n";
156 throw cosmos::ExitStatus::FAILURE;
157 };
158
159 std::vector<SystemCallNr> to_add;
160 std::vector<SystemCallNr> to_remove;
161
162 for (auto &part: parts) {
163 if (part.starts_with("!")) {
164 part = part.substr(1);
165 to_remove.push_back(translate_name(part));
166 } else {
167 to_add.push_back(translate_name(part));
168 }
169 }
170
171 for (const auto nr: to_add) {
172 m_syscall_filter.insert(nr);
173 }
174
175 // NOTE: this removal logic can make sense in the future when
176 // we support system call groups (→ add a group, remove a few
177 // again)
178 for (const auto nr: to_remove) {
179 m_syscall_filter.erase(nr);
180 }
181 }
182
183 if (m_args.print_fd_info.isSet()) {
185 }
186
187 return true;
188}
@ FD_INFO
print detailed file descriptor information
Definition Engine.hxx:50
std::optional< SystemCallNr > lookup_system_call(const std::string_view name)
Returns the SystemCallNr for the given system call name, if it exists.
Definition utils.cxx:271

◆ seenInitialExec()

bool clues::TermTracer::seenInitialExec ( ) const
inlineprotected

Definition at line 179 of file TermTracer.hxx.

179 {
181 }

◆ signaled()

void clues::TermTracer::signaled ( Tracee & tracee,
const cosmos::SigInfo & info )
overrideprotectedvirtual

The tracee has received a signal.

This callback notifies about signals being delivered to the tracee.

Reimplemented from clues::EventConsumer.

Definition at line 443 of file TermTracer.cxx.

443 {
444 traceStream(tracee) << "-- " << clues::format::sig_info(info) << " --\n";
445}

◆ startNewLine()

void clues::TermTracer::startNewLine ( std::ostream & trace,
const Tracee & tracee )
protected

Start a new output line concerning `tracee.

This prints out any preamble that might be necessary for starting a new trace output line, like [<pid>], when multiple tracees are present.

It also cares about managing the system call state to make sure unfinished system calls are kept track of.

Definition at line 613 of file TermTracer.cxx.

613 {
614 // make sure to terminate a possibly pending system call line
616 // a system call in another thread remains unfinished
617 trace << " <...unfinished>\n";
618 }
619
620 if (m_num_tracees > 1) {
621 trace << "[" << tracee.pid() << "] ";
622 } else if (m_flags.steal(Flag::DROPPED_TO_LAST_TRACEE)) {
623 trace << "--- only PID " << tracee.pid() << " is remaining ---\n";
624 }
625}
bool storeUnfinishedSyscallCtx()
Store an active system call in m_unfinished_syscalls.

◆ stopped()

void clues::TermTracer::stopped ( Tracee & tracee)
overrideprotectedvirtual

The tracee entered group-stop due to a stopping signal.

Reimplemented from clues::EventConsumer.

Definition at line 491 of file TermTracer.cxx.

491 {
492 if (tracee.syscallCtr() == 0) {
493 traceStream(tracee) << "--- currently in stopped state due to " << *tracee.stopSignal() << " ---\n";
494 }
495}

◆ storeUnfinishedSyscallCtx()

bool clues::TermTracer::storeUnfinishedSyscallCtx ( )
protected

Store an active system call in m_unfinished_syscalls.

If there's an active system call then store if in m_unfinished_syscalls and reset it.

The return value indicates whether a traced system call has been interrupted by this operation. Interrupted but filtered-out system calls are not counted here.

Definition at line 627 of file TermTracer.cxx.

627 {
628 if (!m_active_syscall) {
629 return false;
630 }
631
632 auto [pid, syscall] = *m_active_syscall;
633 const bool ret = isEnabled(syscall);
634
635 m_unfinished_syscalls[pid] = syscall;
636 m_active_syscall.reset();
637 return ret;
638}

◆ syscallEntry()

void clues::TermTracer::syscallEntry ( Tracee & tracee,
const SystemCall & sc,
const StatusFlags flags )
overrideprotectedvirtual

A system call is about to be executed in the Tracee.

At this stage only the type of system call as well system call parameters of ItemType::PARAM_IN or ItemType::PARAM_IN_OUT have useful values. Any out parameters and the return value should not be inspected.

Reimplemented from clues::EventConsumer.

Definition at line 367 of file TermTracer.cxx.

369 {
370
371 if (!isEnabled(&sc)) {
372 return;
373 }
374
375 // this needs to be assigned before returning from this function but
376 // after traceStream() is called, if at all.
377 auto defer_assign_syscall = cosmos::defer([this, &tracee, &sc]() {
378 m_active_syscall = std::make_optional(
379 std::make_tuple(tracee.pid(), &sc));
380 });
381
382 const auto syscall_info = *tracee.currentSystemCallInfo();
383 checkABI(tracee, syscall_info);
384 /* libclues also offers an ABI_CHANGED flag in `cflags`, but this
385 * doesn't suffice for us, since we might filter system calls, which
386 * means we don't necessarily need to report every ABI change.
387 */
388 m_last_abi = syscall_info.abi();
389
390 auto &trace = traceStream(tracee);
391
392 if (flags[StatusFlag::RESUMED]) {
393 trace << "<resuming previously interrupted " << sc.name() << "...>\n";
394 }
395 trace << sc.name() << "(";
396
397 printParsOnEntry(trace, sc.parameters());
398
399 trace << std::flush;
400}
@ RESUMED
A previously interrupted system call is resumed (only appears during syscallEntry()).
void checkABI(const Tracee &tracee, const SystemCallInfo &info)
Checks the current system call's ABI and reports ABI changes.

◆ syscallExit()

void clues::TermTracer::syscallExit ( Tracee & tracee,
const SystemCall & sc,
const StatusFlags flags )
overrideprotectedvirtual

A system call has been finished.

If the system call failed then sc.hasErrorCode() returns true and the ErrnoResult can be inspected from sc.error().

If the system call succeeded sc.hasResultValue() returns true and any system call parameters of ItemType::PARAM_OUT or ItemType::PARAM_IN_OUT should have been updated by the kernel and can be updated and processed by the implementation.

If flags[StatusFlag::INTERRUPTED] is set, then the system call was interrupted by a signal and aborted by the kernel. Whether this happens depends on various factors, e.g. if the SA_RESTART flag is set in the Tracee for the signal which was received and also on the type of system call which is executed. If automatic system call restarting is not active then a regular EINTR error return will be observed and the Tracee has to actively deal with the situation.

libclues tries to keep track of when an automatically restarted system call will be resumed and will set flags[StatusFlag::RESUMED] during syscallEntry() accordingly. There are two different scenarios to take into account here:

  • restart_syscall() is injected by the kernel. This is the case when the signal was SIGSTOP and the system call was time-related, like clock_nanosleep(), to give userspace (libc) the chance to adjust times before resuming the system call.

    libclues looks up the original system call and will not report SystemCallNr::RESTART_SYSCALL, but the resumed system call and will set StatusFlag::RESUMED accordingly.

  • the system call is transparently restarted by the kernel. In this case the system call entry is not easily distinguishable from a completely new system call. The Tracee might have executed additional system calls in-between when an asynchronous signal handler was executed. libclues is looking for the rt_sigreturn() system call to appear to make out the end of the interruption and then looks for the next system call of the same type as the interrupted system call. When this situation is detected then StatusFlag::RESUMED is set as well.

This needs to be reset before returning from this function but after checkResumedSyscall() is called, if at all.

Reimplemented from clues::EventConsumer.

Definition at line 402 of file TermTracer.cxx.

403 {
404
405 if (isExecSyscall(sc) && sc.result()) {
406 // this was already dealt with in newExecutionContext()
407 return;
408 } else if (!isEnabled(&sc)) {
409 return;
410 }
411
414 auto defer_reset_syscall = cosmos::defer([this]() {
415 m_active_syscall.reset();
416 });
417
418
419 checkResumedSyscall(tracee);
420
421 // we are preempting another Tracee's system call (one Tracee already entered, this Tracee exits
422 const bool need_newline = m_active_syscall && !hasActiveSyscall(tracee);
423
424 auto &trace = traceStream(tracee, need_newline);
425 printParsOnExit(trace, sc.parameters());
426
427 trace << ") = ";
428
429 if (auto res = sc.result(); res) {
430 trace << res->str() << " (" << (m_args.verbose.isSet() ? res->longName() : res->shortName()) << ")";
431 } else {
432 const auto err = *sc.error();
433 trace << err.str() << " (errno)";
434 }
435
436 if (flags[StatusFlag::INTERRUPTED]) {
437 trace << " (interrupted)";
438 }
439
440 trace << std::endl;
441}
@ INTERRUPTED
A system call was interrupted (only appears during syscallExit()).

◆ traceStream()

std::ostream & clues::TermTracer::traceStream ( const Tracee & tracee,
const bool new_line = true )
protected

Returns the currently active trace output stream, starting a new output line.

Definition at line 601 of file TermTracer.cxx.

601 {
602 // TODO: this is currently fixed to cerr, but we should implement
603 // trace to file, maybe also separate files for each PID, or even
604 // separate PTYs in some form.
605 auto &stream = std::cerr;
606
607 if (new_line) {
608 startNewLine(stream, tracee);
609 }
610 return std::cerr;
611}
void startNewLine(std::ostream &trace, const Tracee &tracee)
Start a new output line concerning `tracee.

◆ updateTracee()

void clues::TermTracer::updateTracee ( const Tracee & tracee,
const cosmos::ProcessID old_pid )
protected

Update internal data structures in case a tracee changed PID.

Definition at line 723 of file TermTracer.cxx.

723 {
724 if (hasActiveSyscall(old_pid)) {
725 m_active_syscall = std::make_tuple(tracee.pid(), activeSyscall());
726 } else if (auto it = m_unfinished_syscalls.find(old_pid); it != m_unfinished_syscalls.end()) {
727 m_unfinished_syscalls[tracee.pid()] = it->second;
728 m_unfinished_syscalls.erase(it);
729 }
730
731 // if there should be an entry for the new PID already, then it's from
732 // an already dead sibbling tracee. get rid of that.
733 m_new_tracees.erase(tracee.pid());
734
735 if (auto it = m_new_tracees.find(old_pid); it != m_new_tracees.end()) {
736 m_new_tracees[tracee.pid()] = it->second;
737 m_new_tracees.erase(it);
738 }
739}

Member Data Documentation

◆ m_active_syscall

std::optional<std::tuple<cosmos::ProcessID, const SystemCall*> > clues::TermTracer::m_active_syscall
protected

The currently active system call, if any.

This is only keeping track of system calls that are actually printed to stdout. Ignored system calls are not kept track of here. The Tracee objects keep track of any active system calls already.

Definition at line 261 of file TermTracer.hxx.

◆ m_args

Args clues::TermTracer::m_args
protected

Command line arguments and parser.

Definition at line 224 of file TermTracer.hxx.

◆ m_cosmos

cosmos::Init clues::TermTracer::m_cosmos
protected

Definition at line 228 of file TermTracer.hxx.

◆ m_engine

Engine clues::TermTracer::m_engine
protected

libclues main object.

Definition at line 231 of file TermTracer.hxx.

◆ m_exec_context_arg

std::string clues::TermTracer::m_exec_context_arg
protected

optional argument to m_follow_exec (e.g. path, glob, script)

Definition at line 238 of file TermTracer.hxx.

◆ m_flags

Flags clues::TermTracer::m_flags
protected

State flags with global context or carried between different callbacks.

Definition at line 249 of file TermTracer.hxx.

◆ m_follow_children

FollowChildMode clues::TermTracer::m_follow_children = FollowChildMode::NO
protected

Behaviour upon newChildProcess()

Definition at line 244 of file TermTracer.hxx.

◆ m_follow_exec

FollowExecContext clues::TermTracer::m_follow_exec = FollowExecContext::YES
protected

Behaviour upon newExecutionContext()

Definition at line 242 of file TermTracer.hxx.

◆ m_last_abi

clues::ABI clues::TermTracer::m_last_abi = clues::ABI::UNKNOWN
protected

The ABI of the last system call we've seen.

Definition at line 276 of file TermTracer.hxx.

◆ m_logger

cosmos::StdLogger clues::TermTracer::m_logger
protected

cosmos ILogger instance for clues library logging.

Definition at line 227 of file TermTracer.hxx.

◆ m_main_status

std::optional<cosmos::WaitStatus> clues::TermTracer::m_main_status
protected

The WaitStatus of the main process we've seen upon it exiting.

Definition at line 253 of file TermTracer.hxx.

◆ m_main_tracee_pid

cosmos::ProcessID clues::TermTracer::m_main_tracee_pid
protected

The PID of the main process we're tracing (the one we created or attached to).

Definition at line 251 of file TermTracer.hxx.

◆ m_new_tracees

std::map<cosmos::ProcessID, std::pair<cosmos::ProcessID, cosmos::ptrace::Event> > clues::TermTracer::m_new_tracees
protected

Newly created tracees that haven't seen any ptrace stop yet.

The values contain the PID that created the tracee and the type of event that led to the creation.

Definition at line 274 of file TermTracer.hxx.

◆ m_num_tracees

size_t clues::TermTracer::m_num_tracees = 0
protected

The number of tracees we're currently dealing with.

Definition at line 247 of file TermTracer.hxx.

◆ m_par_truncation_len

size_t clues::TermTracer::m_par_truncation_len = 64
protected

Maximum length of of system call parameter values to print before truncating the output.

Definition at line 236 of file TermTracer.hxx.

◆ m_print_pars

bool clues::TermTracer::m_print_pars = true
protected

Whether to print system call parameters at all (-s 0 disables it).

Definition at line 234 of file TermTracer.hxx.

◆ m_syscall_filter

std::set<SystemCallNr> clues::TermTracer::m_syscall_filter
protected

Whitelist of system calls to trace, if any.

Definition at line 240 of file TermTracer.hxx.

◆ m_unfinished_syscalls

std::map<cosmos::ProcessID, const SystemCall*> clues::TermTracer::m_unfinished_syscalls
protected

Unfinished / preempted system calls.

Unfinished in this context is purely tracing related, it only means that we already started printing system call entry for one tracee, while another event came in, preempting this line.

Definition at line 268 of file TermTracer.hxx.


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