mirror of
https://github.com/RGBCube/serenity
synced 2025-07-25 12:17:44 +00:00

Moving the formatting of strace's output into a separate function will allow us to introduce more complexity into the formatting logic without touching the main body of the program. The new function uses a switch statement to select how to format the arguments and result depending on the syscall. At this point we only include the default formatting, where the registers are simply dumped, but later on we can add specializations for each system call we want to support.
198 lines
5.1 KiB
C++
198 lines
5.1 KiB
C++
/*
|
|
* Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org>
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#include <AK/Assertions.h>
|
|
#include <AK/Types.h>
|
|
#include <LibC/sys/arch/i386/regs.h>
|
|
#include <LibCore/ArgsParser.h>
|
|
#include <LibCore/File.h>
|
|
#include <signal.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/ptrace.h>
|
|
#include <sys/wait.h>
|
|
#include <syscall.h>
|
|
#include <unistd.h>
|
|
|
|
static int g_pid = -1;
|
|
|
|
#if ARCH(I386)
|
|
using syscall_arg_t = u32;
|
|
#else
|
|
using syscall_arg_t = u64;
|
|
#endif
|
|
|
|
static void handle_sigint(int)
|
|
{
|
|
if (g_pid == -1)
|
|
return;
|
|
|
|
if (ptrace(PT_DETACH, g_pid, 0, 0) == -1) {
|
|
perror("detach");
|
|
}
|
|
}
|
|
|
|
static String format_syscall(syscall_arg_t syscall_index, syscall_arg_t arg1, syscall_arg_t arg2, syscall_arg_t arg3, syscall_arg_t res)
|
|
{
|
|
auto syscall_function = (Syscall::Function)syscall_index;
|
|
StringBuilder builder;
|
|
builder.append(Syscall::to_string(syscall_function));
|
|
builder.append('(');
|
|
|
|
switch (syscall_function) {
|
|
default:
|
|
builder.appendff("{:#08x}, {:#08x}, {:#08x})\t={}",
|
|
arg1,
|
|
arg2,
|
|
arg3,
|
|
res);
|
|
}
|
|
|
|
builder.append('\n');
|
|
return builder.to_string();
|
|
}
|
|
|
|
int main(int argc, char** argv)
|
|
{
|
|
if (pledge("stdio wpath cpath proc exec ptrace sigaction", nullptr) < 0) {
|
|
perror("pledge");
|
|
return 1;
|
|
}
|
|
|
|
Vector<const char*> child_argv;
|
|
|
|
const char* output_filename = nullptr;
|
|
auto trace_file = Core::File::standard_error();
|
|
|
|
Core::ArgsParser parser;
|
|
parser.set_stop_on_first_non_option(true);
|
|
parser.set_general_help(
|
|
"Trace all syscalls and their result.");
|
|
parser.add_option(g_pid, "Trace the given PID", "pid", 'p', "pid");
|
|
parser.add_option(output_filename, "Filename to write output to", "output", 'o', "output");
|
|
parser.add_positional_argument(child_argv, "Arguments to exec", "argument", Core::ArgsParser::Required::No);
|
|
|
|
parser.parse(argc, argv);
|
|
|
|
if (output_filename != nullptr) {
|
|
auto open_result = Core::File::open(output_filename, Core::OpenMode::WriteOnly);
|
|
if (open_result.is_error()) {
|
|
outln(stderr, "Failed to open output file: {}", open_result.error());
|
|
return 1;
|
|
}
|
|
trace_file = open_result.value();
|
|
}
|
|
|
|
if (pledge("stdio proc exec ptrace sigaction", nullptr) < 0) {
|
|
perror("pledge");
|
|
return 1;
|
|
}
|
|
|
|
int status;
|
|
if (g_pid == -1) {
|
|
if (child_argv.is_empty()) {
|
|
warnln("strace: Expected either a pid or some arguments");
|
|
return 1;
|
|
}
|
|
|
|
child_argv.append(nullptr);
|
|
int pid = fork();
|
|
if (pid < 0) {
|
|
perror("fork");
|
|
return 1;
|
|
}
|
|
|
|
if (!pid) {
|
|
if (ptrace(PT_TRACE_ME, 0, 0, 0) == -1) {
|
|
perror("traceme");
|
|
return 1;
|
|
}
|
|
int rc = execvp(child_argv.first(), const_cast<char**>(child_argv.data()));
|
|
if (rc < 0) {
|
|
perror("execvp");
|
|
exit(1);
|
|
}
|
|
VERIFY_NOT_REACHED();
|
|
}
|
|
|
|
g_pid = pid;
|
|
if (waitpid(pid, &status, WSTOPPED | WEXITED) != pid || !WIFSTOPPED(status)) {
|
|
perror("waitpid");
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
struct sigaction sa;
|
|
memset(&sa, 0, sizeof(struct sigaction));
|
|
sa.sa_handler = handle_sigint;
|
|
sigaction(SIGINT, &sa, nullptr);
|
|
|
|
if (ptrace(PT_ATTACH, g_pid, 0, 0) == -1) {
|
|
perror("attach");
|
|
return 1;
|
|
}
|
|
if (waitpid(g_pid, &status, WSTOPPED | WEXITED) != g_pid || !WIFSTOPPED(status)) {
|
|
perror("waitpid");
|
|
return 1;
|
|
}
|
|
|
|
for (;;) {
|
|
if (ptrace(PT_SYSCALL, g_pid, 0, 0) == -1) {
|
|
perror("syscall");
|
|
return 1;
|
|
}
|
|
if (waitpid(g_pid, &status, WSTOPPED | WEXITED) != g_pid || !WIFSTOPPED(status)) {
|
|
perror("wait_pid");
|
|
return 1;
|
|
}
|
|
PtraceRegisters regs = {};
|
|
if (ptrace(PT_GETREGS, g_pid, ®s, 0) == -1) {
|
|
perror("getregs");
|
|
return 1;
|
|
}
|
|
#if ARCH(I386)
|
|
syscall_arg_t syscall_index = regs.eax;
|
|
syscall_arg_t arg1 = regs.edx;
|
|
syscall_arg_t arg2 = regs.ecx;
|
|
syscall_arg_t arg3 = regs.ebx;
|
|
#else
|
|
syscall_arg_t syscall_index = regs.rax;
|
|
syscall_arg_t arg1 = regs.rdx;
|
|
syscall_arg_t arg2 = regs.rcx;
|
|
syscall_arg_t arg3 = regs.rbx;
|
|
#endif
|
|
|
|
if (ptrace(PT_SYSCALL, g_pid, 0, 0) == -1) {
|
|
perror("syscall");
|
|
return 1;
|
|
}
|
|
if (waitpid(g_pid, &status, WSTOPPED | WEXITED) != g_pid || !WIFSTOPPED(status)) {
|
|
perror("wait_pid");
|
|
return 1;
|
|
}
|
|
|
|
if (ptrace(PT_GETREGS, g_pid, ®s, 0) == -1) {
|
|
perror("getregs");
|
|
return 1;
|
|
}
|
|
|
|
#if ARCH(I386)
|
|
u32 res = regs.eax;
|
|
#else
|
|
u64 res = regs.rax;
|
|
#endif
|
|
|
|
auto string = format_syscall(syscall_index, arg1, arg2, arg3, res);
|
|
|
|
if (!trace_file->write(string)) {
|
|
warnln("write: {}", trace_file->error_string());
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|