ladybird/Userland/Utilities/strace.cpp
Daniel Bertalan 77f9272aaf Kernel+UE: Add MAP_FIXED_NOREPLACE mmap() flag
This feature was introduced in version 4.17 of the Linux kernel, and
while it's not specified by POSIX, I think it will be a nice addition to
our system.

MAP_FIXED_NOREPLACE provides a less error-prone alternative to
MAP_FIXED: while regular fixed mappings would cause any intersecting
ranges to be unmapped, MAP_FIXED_NOREPLACE returns EEXIST instead. This
ensures that we don't corrupt our process's address space if something
is already at the requested address.

Note that the more portable way to do this is to use regular
MAP_ANONYMOUS, and check afterwards whether the returned address matches
what we wanted. This, however, has a large performance impact on
programs like Wine which try to reserve large portions of the address
space at once, as the non-matching addresses have to be unmapped
separately.
2021-12-23 23:08:10 +01:00

958 lines
27 KiB
C++

/*
* Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Assertions.h>
#include <AK/Format.h>
#include <AK/IPv4Address.h>
#include <AK/StdLibExtras.h>
#include <AK/Types.h>
#include <LibC/sys/arch/i386/regs.h>
#include <LibCore/ArgsParser.h>
#include <LibCore/File.h>
#include <LibCore/System.h>
#include <errno.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/ptrace.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <syscall.h>
#include <unistd.h>
#define HANDLE(VALUE) \
case VALUE: \
return #VALUE;
#define VALUES_TO_NAMES(FUNC_NAME) \
static String FUNC_NAME(int value) \
{ \
switch (value) {
#define END_VALUES_TO_NAMES() \
} \
return String::formatted("{}", value); \
}
VALUES_TO_NAMES(errno_name)
HANDLE(EPERM)
HANDLE(ENOENT)
HANDLE(ESRCH)
HANDLE(EINTR)
HANDLE(EIO)
HANDLE(ENXIO)
HANDLE(E2BIG)
HANDLE(ENOEXEC)
HANDLE(EBADF)
HANDLE(ECHILD)
HANDLE(EAGAIN)
HANDLE(ENOMEM)
HANDLE(EACCES)
HANDLE(EFAULT)
HANDLE(ENOTBLK)
HANDLE(EBUSY)
HANDLE(EEXIST)
HANDLE(EXDEV)
HANDLE(ENODEV)
HANDLE(ENOTDIR)
HANDLE(EISDIR)
HANDLE(EINVAL)
HANDLE(ENFILE)
HANDLE(EMFILE)
HANDLE(ENOTTY)
HANDLE(ETXTBSY)
HANDLE(EFBIG)
HANDLE(ENOSPC)
HANDLE(ESPIPE)
HANDLE(EROFS)
HANDLE(EMLINK)
HANDLE(EPIPE)
HANDLE(ERANGE)
HANDLE(ENAMETOOLONG)
HANDLE(ELOOP)
HANDLE(EOVERFLOW)
HANDLE(EOPNOTSUPP)
HANDLE(ENOSYS)
HANDLE(ENOTIMPL)
HANDLE(EAFNOSUPPORT)
HANDLE(ENOTSOCK)
HANDLE(EADDRINUSE)
HANDLE(EWHYTHO)
HANDLE(ENOTEMPTY)
HANDLE(EDOM)
HANDLE(ECONNREFUSED)
HANDLE(EHOSTDOWN)
HANDLE(EADDRNOTAVAIL)
HANDLE(EISCONN)
HANDLE(ECONNABORTED)
HANDLE(EALREADY)
HANDLE(ECONNRESET)
HANDLE(EDESTADDRREQ)
HANDLE(EHOSTUNREACH)
HANDLE(EILSEQ)
HANDLE(EMSGSIZE)
HANDLE(ENETDOWN)
HANDLE(ENETUNREACH)
HANDLE(ENETRESET)
HANDLE(ENOBUFS)
HANDLE(ENOLCK)
HANDLE(ENOMSG)
HANDLE(ENOPROTOOPT)
HANDLE(ENOTCONN)
HANDLE(ESHUTDOWN)
HANDLE(ETOOMANYREFS)
HANDLE(EPROTONOSUPPORT)
HANDLE(ESOCKTNOSUPPORT)
HANDLE(EDEADLK)
HANDLE(ETIMEDOUT)
HANDLE(EPROTOTYPE)
HANDLE(EINPROGRESS)
HANDLE(ENOTHREAD)
HANDLE(EPROTO)
HANDLE(ENOTSUP)
HANDLE(EPFNOSUPPORT)
HANDLE(EDIRINTOSELF)
HANDLE(EDQUOT)
HANDLE(EMAXERRNO)
END_VALUES_TO_NAMES()
VALUES_TO_NAMES(whence_name)
HANDLE(SEEK_SET)
HANDLE(SEEK_CUR)
HANDLE(SEEK_END)
END_VALUES_TO_NAMES()
VALUES_TO_NAMES(ioctl_request_name)
HANDLE(TIOCGPGRP)
HANDLE(TIOCSPGRP)
HANDLE(TCGETS)
HANDLE(TCSETS)
HANDLE(TCSETSW)
HANDLE(TCSETSF)
HANDLE(TCFLSH)
HANDLE(TIOCGWINSZ)
HANDLE(TIOCSCTTY)
HANDLE(TIOCSTI)
HANDLE(TIOCNOTTY)
HANDLE(TIOCSWINSZ)
HANDLE(FB_IOCTL_GET_PROPERTIES)
HANDLE(FB_IOCTL_GET_HEAD_PROPERTIES)
HANDLE(FB_IOCTL_SET_HEAD_RESOLUTION)
HANDLE(FB_IOCTL_SET_HEAD_VERTICAL_OFFSET_BUFFER)
HANDLE(FB_IOCTL_GET_HEAD_VERTICAL_OFFSET_BUFFER)
HANDLE(FB_IOCTL_FLUSH_HEAD_BUFFERS)
HANDLE(FB_IOCTL_FLUSH_HEAD)
HANDLE(KEYBOARD_IOCTL_GET_NUM_LOCK)
HANDLE(KEYBOARD_IOCTL_SET_NUM_LOCK)
HANDLE(KEYBOARD_IOCTL_GET_CAPS_LOCK)
HANDLE(KEYBOARD_IOCTL_SET_CAPS_LOCK)
HANDLE(SIOCSIFADDR)
HANDLE(SIOCGIFADDR)
HANDLE(SIOCGIFHWADDR)
HANDLE(SIOCGIFNETMASK)
HANDLE(SIOCSIFNETMASK)
HANDLE(SIOCGIFBRDADDR)
HANDLE(SIOCGIFMTU)
HANDLE(SIOCGIFFLAGS)
HANDLE(SIOCGIFCONF)
HANDLE(SIOCADDRT)
HANDLE(SIOCDELRT)
HANDLE(SIOCSARP)
HANDLE(SIOCDARP)
HANDLE(FIBMAP)
HANDLE(FIONBIO)
HANDLE(FIONREAD)
HANDLE(KCOV_SETBUFSIZE)
HANDLE(KCOV_ENABLE)
HANDLE(KCOV_DISABLE)
HANDLE(SOUNDCARD_IOCTL_SET_SAMPLE_RATE)
HANDLE(SOUNDCARD_IOCTL_GET_SAMPLE_RATE)
HANDLE(STORAGE_DEVICE_GET_SIZE)
HANDLE(STORAGE_DEVICE_GET_BLOCK_SIZE)
END_VALUES_TO_NAMES()
VALUES_TO_NAMES(domain_name)
HANDLE(AF_UNSPEC)
HANDLE(AF_UNIX)
HANDLE(AF_INET)
HANDLE(AF_INET6)
END_VALUES_TO_NAMES()
VALUES_TO_NAMES(socket_type_name)
HANDLE(SOCK_STREAM)
HANDLE(SOCK_DGRAM)
HANDLE(SOCK_RAW)
HANDLE(SOCK_RDM)
HANDLE(SOCK_SEQPACKET)
END_VALUES_TO_NAMES()
VALUES_TO_NAMES(protocol_name)
HANDLE(PF_UNSPEC)
HANDLE(PF_UNIX)
HANDLE(PF_INET)
HANDLE(PF_INET6)
END_VALUES_TO_NAMES()
VALUES_TO_NAMES(clockid_name)
HANDLE(CLOCK_REALTIME)
HANDLE(CLOCK_MONOTONIC)
HANDLE(CLOCK_REALTIME_COARSE)
HANDLE(CLOCK_MONOTONIC_COARSE)
END_VALUES_TO_NAMES()
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 ErrorOr<void> copy_from_process(const void* source, Bytes target)
{
return Core::System::ptrace_peekbuf(g_pid, const_cast<void*>(source), target);
}
static ErrorOr<ByteBuffer> copy_from_process(const void* source, size_t length)
{
auto buffer = ByteBuffer::create_uninitialized(length);
if (!buffer.has_value()) {
// Allocation failed. Inject an error:
return Error::from_errno(ENOMEM);
}
TRY(copy_from_process(source, buffer->bytes()));
return buffer.release_value();
}
template<typename T>
static ErrorOr<T> copy_from_process(const T* source)
{
T value {};
TRY(copy_from_process(source, Bytes { &value, sizeof(T) }));
return value;
}
struct BitflagOption {
int value;
StringView name;
};
#define BITFLAG(NAME) \
BitflagOption { NAME, #NAME }
struct BitflagBase {
int flagset;
// Derivatives must define 'options', like so:
// static constexpr auto options = { BITFLAG(O_CREAT), BITFLAG(O_DIRECTORY) };
};
namespace AK {
template<typename BitflagDerivative>
requires(IsBaseOf<BitflagBase, BitflagDerivative>) && requires { BitflagDerivative::options; }
struct Formatter<BitflagDerivative> : StandardFormatter {
Formatter() = default;
explicit Formatter(StandardFormatter formatter)
: StandardFormatter(formatter)
{
}
ErrorOr<void> format(FormatBuilder& format_builder, BitflagDerivative const& value)
{
bool had_any_output = false;
int remaining = value.flagset;
for (BitflagOption const& option : BitflagDerivative::options) {
if ((remaining & option.value) != option.value)
continue;
remaining &= ~option.value;
if (had_any_output)
TRY(format_builder.put_literal(" | "));
TRY(format_builder.put_literal(option.name));
had_any_output = true;
}
if (remaining != 0) {
// No more BitflagOptions are available. Any remaining flags are unrecognized.
if (had_any_output)
TRY(format_builder.put_literal(" | "));
format_builder.builder().appendff("0x{:x} (?)", static_cast<unsigned>(remaining));
had_any_output = true;
}
if (!had_any_output) {
if constexpr (requires { BitflagDerivative::default_; })
TRY(format_builder.put_literal(BitflagDerivative::default_));
else
TRY(format_builder.put_literal("0"));
}
return {};
}
};
}
struct PointerArgument {
const void* value;
};
namespace AK {
template<>
struct Formatter<PointerArgument> : StandardFormatter {
Formatter() = default;
explicit Formatter(StandardFormatter formatter)
: StandardFormatter(formatter)
{
}
ErrorOr<void> format(FormatBuilder& format_builder, PointerArgument const& value)
{
auto& builder = format_builder.builder();
if (value.value == nullptr)
builder.append("null");
else
builder.appendff("{}", value.value);
return {};
}
};
}
struct StringArgument {
Syscall::StringArgument argument;
StringView trim_by {};
};
namespace AK {
template<>
struct Formatter<StringArgument> : StandardFormatter {
Formatter() = default;
explicit Formatter(StandardFormatter formatter)
: StandardFormatter(formatter)
{
}
ErrorOr<void> format(FormatBuilder& format_builder, StringArgument const& string_argument)
{
auto& builder = format_builder.builder();
if (string_argument.argument.characters == nullptr) {
builder.append("null");
return {};
}
// TODO: Avoid trying to copy excessively long strings.
auto string_buffer = copy_from_process(string_argument.argument.characters, string_argument.argument.length);
if (string_buffer.is_error()) {
builder.appendff("{}{{{:p}, {}b}}", string_buffer.error(), (void const*)string_argument.argument.characters, string_argument.argument.length);
} else {
auto view = StringView(string_buffer.value());
if (!string_argument.trim_by.is_empty())
view = view.trim(string_argument.trim_by);
builder.appendff("\"{}\"", view);
}
return {};
}
};
}
class FormattedSyscallBuilder {
public:
FormattedSyscallBuilder(StringView syscall_name)
{
m_builder.append(syscall_name);
m_builder.append('(');
}
template<typename T>
void add_argument(CheckedFormatString<T> format, T&& arg)
{
add_argument_separator();
m_builder.appendff(format.view(), forward<T>(arg));
}
template<typename T>
void add_argument(T&& arg)
{
add_argument("{}", forward<T>(arg));
}
template<typename... Ts>
void add_arguments(Ts&&... args)
{
(add_argument(forward<Ts>(args)), ...);
}
template<typename T>
void format_result_no_error(T res)
{
m_builder.appendff(") = {}\n", res);
}
void format_result(Integral auto res)
{
m_builder.append(") = ");
if (res < 0)
m_builder.appendff("{} {}", res, errno_name(-(int)res));
else
m_builder.appendff("{}", res);
m_builder.append('\n');
}
void format_result(void* res)
{
if (res == MAP_FAILED)
m_builder.append(") = MAP_FAILED\n");
else if (FlatPtr(res) > FlatPtr(-EMAXERRNO))
m_builder.appendff(") = {} {}\n", res, errno_name(-static_cast<int>(FlatPtr(res))));
else
m_builder.appendff(") = {}\n", res);
}
void format_result()
{
m_builder.append(")\n");
}
StringView string_view()
{
return m_builder.string_view();
}
private:
void add_argument_separator()
{
if (!m_first_arg) {
m_builder.append(", ");
}
m_first_arg = false;
}
StringBuilder m_builder;
bool m_first_arg { true };
};
static void format_getrandom(FormattedSyscallBuilder& builder, void* buffer, size_t size, unsigned flags)
{
builder.add_arguments(buffer, size, flags);
}
static void format_realpath(FormattedSyscallBuilder& builder, Syscall::SC_realpath_params* params_p)
{
auto params = copy_from_process(params_p).release_value_but_fixme_should_propagate_errors();
builder.add_arguments(StringArgument { params.path }, StringArgument { { params.buffer.data, params.buffer.size } });
}
static void format_exit(FormattedSyscallBuilder& builder, int status)
{
builder.add_argument(status);
}
struct OpenOptions : BitflagBase {
static constexpr auto options = {
BITFLAG(O_RDWR), BITFLAG(O_RDONLY), BITFLAG(O_WRONLY),
BITFLAG(O_EXEC), BITFLAG(O_CREAT), BITFLAG(O_EXCL), BITFLAG(O_NOCTTY),
BITFLAG(O_TRUNC), BITFLAG(O_APPEND), BITFLAG(O_NONBLOCK), BITFLAG(O_DIRECTORY),
BITFLAG(O_NOFOLLOW), BITFLAG(O_CLOEXEC), BITFLAG(O_DIRECT)
};
};
static void format_open(FormattedSyscallBuilder& builder, Syscall::SC_open_params* params_p)
{
auto params = copy_from_process(params_p).release_value_but_fixme_should_propagate_errors();
if (params.dirfd == AT_FDCWD)
builder.add_argument("AT_FDCWD");
else
builder.add_argument(params.dirfd);
builder.add_arguments(StringArgument { params.path }, OpenOptions { params.options });
if (params.options & O_CREAT)
builder.add_argument("{:04o}", params.mode);
}
static void format_ioctl(FormattedSyscallBuilder& builder, int fd, unsigned request, void* arg)
{
builder.add_arguments(fd, ioctl_request_name(request));
if (request == FIONBIO) {
auto value = copy_from_process(reinterpret_cast<int*>(arg));
builder.add_argument(value);
} else
builder.add_argument(PointerArgument { arg });
}
namespace AK {
template<>
struct Formatter<struct timespec> : StandardFormatter {
ErrorOr<void> format(FormatBuilder& format_builder, struct timespec value)
{
auto& builder = format_builder.builder();
builder.appendff("{{tv_sec={}, tv_nsec={}}}", value.tv_sec, value.tv_nsec);
return {};
}
};
template<>
struct Formatter<struct timeval> : StandardFormatter {
ErrorOr<void> format(FormatBuilder& format_builder, struct timeval value)
{
auto& builder = format_builder.builder();
builder.appendff("{{tv_sec={}, tv_usec={}}}", value.tv_sec, value.tv_usec);
return {};
}
};
template<>
struct Formatter<struct stat> : StandardFormatter {
ErrorOr<void> format(FormatBuilder& format_builder, struct stat value)
{
auto& builder = format_builder.builder();
builder.appendff(
"{{st_dev={}, st_ino={}, st_mode={}, st_nlink={}, st_uid={}, st_gid={}, st_rdev={}, "
"st_size={}, st_blksize={}, st_blocks={}, st_atim={}, st_mtim={}, st_ctim={}}}",
value.st_dev, value.st_ino, value.st_mode, value.st_nlink, value.st_uid, value.st_gid, value.st_rdev,
value.st_size, value.st_blksize, value.st_blocks, value.st_atim, value.st_mtim, value.st_ctim);
return {};
}
};
}
static void format_fstat(FormattedSyscallBuilder& builder, int fd, struct stat* buf_p)
{
auto buf = copy_from_process(buf_p);
builder.add_arguments(fd, buf);
}
static void format_stat(FormattedSyscallBuilder& builder, Syscall::SC_stat_params* params_p)
{
auto params = copy_from_process(params_p).release_value_but_fixme_should_propagate_errors();
if (params.dirfd == AT_FDCWD)
builder.add_argument("AT_FDCWD");
else
builder.add_argument(params.dirfd);
builder.add_arguments(StringArgument { params.path }, copy_from_process(params.statbuf), params.follow_symlinks);
}
static void format_lseek(FormattedSyscallBuilder& builder, int fd, off_t offset, int whence)
{
builder.add_arguments(fd, offset, whence_name(whence));
}
static void format_read(FormattedSyscallBuilder& builder, int fd, void* buf, size_t nbyte)
{
builder.add_arguments(fd, buf, nbyte);
}
static void format_write(FormattedSyscallBuilder& builder, int fd, void* buf, size_t nbyte)
{
builder.add_arguments(fd, buf, nbyte);
}
static void format_close(FormattedSyscallBuilder& builder, int fd)
{
builder.add_arguments(fd);
}
static void format_poll(FormattedSyscallBuilder& builder, Syscall::SC_poll_params* params_p)
{
// TODO: format fds and sigmask properly
auto params = copy_from_process(params_p).release_value_but_fixme_should_propagate_errors();
builder.add_arguments(
params.nfds,
PointerArgument { params.fds },
copy_from_process(params.timeout),
PointerArgument { params.sigmask });
}
namespace AK {
template<>
struct Formatter<struct sockaddr> : StandardFormatter {
ErrorOr<void> format(FormatBuilder& format_builder, struct sockaddr address)
{
auto& builder = format_builder.builder();
builder.append("{sa_family=");
builder.append(domain_name(address.sa_family));
if (address.sa_family == AF_INET) {
auto* address_in = (const struct sockaddr_in*)&address;
builder.appendff(
", sin_port={}, sin_addr={}",
address_in->sin_port,
IPv4Address(address_in->sin_addr.s_addr).to_string());
} else if (address.sa_family == AF_UNIX) {
auto* address_un = (const struct sockaddr_un*)&address;
builder.appendff(
", sun_path={}",
address_un->sun_path);
}
builder.append('}');
return {};
}
};
}
static void format_socket(FormattedSyscallBuilder& builder, int domain, int type, int protocol)
{
// TODO: show additional options in type
builder.add_arguments(domain_name(domain), socket_type_name(type & SOCK_TYPE_MASK), protocol_name(protocol));
}
static void format_connect(FormattedSyscallBuilder& builder, int socket, const struct sockaddr* address_p, socklen_t address_len)
{
builder.add_arguments(socket, copy_from_process(address_p), address_len);
}
struct MsgOptions : BitflagBase {
static constexpr auto options = {
BITFLAG(MSG_TRUNC), BITFLAG(MSG_CTRUNC), BITFLAG(MSG_PEEK),
BITFLAG(MSG_OOB), BITFLAG(MSG_DONTROUTE), BITFLAG(MSG_WAITALL),
BITFLAG(MSG_DONTWAIT)
};
};
static void format_recvmsg(FormattedSyscallBuilder& builder, int socket, struct msghdr* message, int flags)
{
// TODO: format message
builder.add_arguments(socket, message, MsgOptions { flags });
}
struct MmapFlags : BitflagBase {
static constexpr auto options = {
BITFLAG(MAP_SHARED), BITFLAG(MAP_PRIVATE), BITFLAG(MAP_FIXED), BITFLAG(MAP_ANONYMOUS),
BITFLAG(MAP_RANDOMIZED), BITFLAG(MAP_STACK), BITFLAG(MAP_NORESERVE), BITFLAG(MAP_PURGEABLE),
BITFLAG(MAP_FIXED_NOREPLACE)
};
static constexpr StringView default_ = "MAP_FILE";
};
struct MemoryProtectionFlags : BitflagBase {
static constexpr auto options = {
BITFLAG(PROT_READ), BITFLAG(PROT_WRITE), BITFLAG(PROT_EXEC)
};
static constexpr StringView default_ = "PROT_NONE";
};
static void format_mmap(FormattedSyscallBuilder& builder, Syscall::SC_mmap_params* params_p)
{
auto params = copy_from_process(params_p).release_value_but_fixme_should_propagate_errors();
builder.add_arguments(params.addr, params.size, MemoryProtectionFlags { params.prot }, MmapFlags { params.flags }, params.fd, params.offset, params.alignment, StringArgument { params.name });
}
static void format_munmap(FormattedSyscallBuilder& builder, void* addr, size_t size)
{
builder.add_arguments(addr, size);
}
static void format_mprotect(FormattedSyscallBuilder& builder, void* addr, size_t size, int prot)
{
builder.add_arguments(addr, size, MemoryProtectionFlags { prot });
}
static void format_set_mmap_name(FormattedSyscallBuilder& builder, Syscall::SC_set_mmap_name_params* params_p)
{
auto params = copy_from_process(params_p).release_value_but_fixme_should_propagate_errors();
builder.add_arguments(params.addr, params.size, StringArgument { params.name });
}
static void format_clock_gettime(FormattedSyscallBuilder& builder, clockid_t clockid, struct timespec* time)
{
builder.add_arguments(clockid_name(clockid), copy_from_process(time));
}
static void format_dbgputstr(FormattedSyscallBuilder& builder, char* characters, size_t size)
{
builder.add_argument(StringArgument { { characters, size }, "\0\n"sv });
}
static void format_get_process_name(FormattedSyscallBuilder& builder, char* buffer, size_t buffer_size)
{
builder.add_argument(StringArgument { { buffer, buffer_size }, "\0"sv });
}
static void format_syscall(FormattedSyscallBuilder& builder, Syscall::Function syscall_function, syscall_arg_t arg1, syscall_arg_t arg2, syscall_arg_t arg3, syscall_arg_t res)
{
enum ResultType {
Int,
Ssize,
VoidP,
Void
};
ResultType result_type { Int };
switch (syscall_function) {
case SC_clock_gettime:
format_clock_gettime(builder, (clockid_t)arg1, (struct timespec*)arg2);
break;
case SC_close:
format_close(builder, (int)arg1);
break;
case SC_connect:
format_connect(builder, (int)arg1, (const struct sockaddr*)arg2, (socklen_t)arg3);
break;
case SC_dbgputstr:
format_dbgputstr(builder, (char*)arg1, (size_t)arg2);
break;
case SC_exit:
format_exit(builder, (int)arg1);
result_type = Void;
break;
case SC_fstat:
format_fstat(builder, (int)arg1, (struct stat*)arg2);
result_type = Ssize;
break;
case SC_get_process_name:
format_get_process_name(builder, (char*)arg1, (size_t)arg2);
break;
case SC_getrandom:
format_getrandom(builder, (void*)arg1, (size_t)arg2, (unsigned)arg3);
break;
case SC_ioctl:
format_ioctl(builder, (int)arg1, (unsigned)arg2, (void*)arg3);
break;
case SC_lseek:
format_lseek(builder, (int)arg1, (off_t)arg2, (int)arg3);
break;
case SC_mmap:
format_mmap(builder, (Syscall::SC_mmap_params*)arg1);
result_type = VoidP;
break;
case SC_mprotect:
format_mprotect(builder, (void*)arg1, (size_t)arg2, (int)arg3);
break;
case SC_munmap:
format_munmap(builder, (void*)arg1, (size_t)arg2);
break;
case SC_open:
format_open(builder, (Syscall::SC_open_params*)arg1);
break;
case SC_poll:
format_poll(builder, (Syscall::SC_poll_params*)arg1);
break;
case SC_read:
format_read(builder, (int)arg1, (void*)arg2, (size_t)arg3);
result_type = Ssize;
break;
case SC_realpath:
format_realpath(builder, (Syscall::SC_realpath_params*)arg1);
break;
case SC_recvmsg:
format_recvmsg(builder, (int)arg1, (struct msghdr*)arg2, (int)arg3);
result_type = Ssize;
break;
case SC_set_mmap_name:
format_set_mmap_name(builder, (Syscall::SC_set_mmap_name_params*)arg1);
break;
case SC_socket:
format_socket(builder, (int)arg1, (int)arg2, (int)arg3);
break;
case SC_stat:
format_stat(builder, (Syscall::SC_stat_params*)arg1);
break;
case SC_write:
format_write(builder, (int)arg1, (void*)arg2, (size_t)arg3);
result_type = Ssize;
break;
case SC_getuid:
case SC_geteuid:
case SC_getgid:
case SC_getegid:
case SC_getpid:
case SC_getppid:
case SC_gettid:
break;
default:
builder.add_arguments((void*)arg1, (void*)arg2, (void*)arg3);
result_type = VoidP;
}
switch (result_type) {
case Int:
builder.format_result((int)res);
break;
case Ssize:
builder.format_result((ssize_t)res);
break;
case VoidP:
builder.format_result((void*)res);
break;
case Void:
builder.format_result();
break;
}
}
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;
const char* exclude_syscalls_option = nullptr;
const char* include_syscalls_option = nullptr;
HashTable<StringView> exclude_syscalls;
HashTable<StringView> include_syscalls;
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_option(exclude_syscalls_option, "Comma-delimited syscalls to exclude", "exclude", 'e', "exclude");
parser.add_option(include_syscalls_option, "Comma-delimited syscalls to include", "include", 'i', "include");
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();
}
auto parse_syscalls = [](const char* option, auto& hash_table) {
if (option != nullptr) {
for (auto syscall : StringView(option).split_view(','))
hash_table.set(syscall);
}
};
parse_syscalls(exclude_syscalls_option, exclude_syscalls);
parse_syscalls(include_syscalls_option, include_syscalls);
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, &regs, 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, &regs, 0) == -1) {
perror("getregs");
return 1;
}
#if ARCH(I386)
u32 res = regs.eax;
#else
u64 res = regs.rax;
#endif
auto syscall_function = (Syscall::Function)syscall_index;
auto syscall_name = to_string(syscall_function);
if (exclude_syscalls.contains(syscall_name))
continue;
if (!include_syscalls.is_empty() && !include_syscalls.contains(syscall_name))
continue;
FormattedSyscallBuilder builder(syscall_name);
format_syscall(builder, syscall_function, arg1, arg2, arg3, res);
if (!trace_file->write(builder.string_view())) {
warnln("write: {}", trace_file->error_string());
return 1;
}
}
}