mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-01-24 02:03:06 -05:00
d16d805d96
It makes much more sense to have these actions being performed via the prctl syscall, as they both require 2 plain arguments to be passed to the syscall layer, and in contrast to most syscalls, we don't get in these removed syscalls an automatic representation of Userspace<T>, but two FlatPtr(s) to perform casting on them in the prctl syscall which is suited to what has been done in the removed syscalls. Also, it makes sense to have these actions in the prctl syscall, because they are strongly related to the process control concept of the prctl syscall.
929 lines
27 KiB
C++
929 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/HashTable.h>
|
|
#include <AK/IPv4Address.h>
|
|
#include <AK/StdLibExtras.h>
|
|
#include <AK/Types.h>
|
|
#include <Kernel/API/SyscallString.h>
|
|
#include <LibCore/ArgsParser.h>
|
|
#include <LibCore/File.h>
|
|
#include <LibCore/System.h>
|
|
#include <LibMain/Main.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <netinet/in.h>
|
|
#include <signal.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <sys/arch/regs.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##sv;
|
|
#define VALUES_TO_NAMES(FUNC_NAME) \
|
|
static DeprecatedString FUNC_NAME(int value) \
|
|
{ \
|
|
switch (value) {
|
|
#define END_VALUES_TO_NAMES() \
|
|
} \
|
|
return DeprecatedString::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(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(GRAPHICS_IOCTL_GET_PROPERTIES)
|
|
HANDLE(GRAPHICS_IOCTL_SET_HEAD_MODE_SETTING)
|
|
HANDLE(GRAPHICS_IOCTL_GET_HEAD_MODE_SETTING)
|
|
HANDLE(GRAPHICS_IOCTL_SET_HEAD_VERTICAL_OFFSET_BUFFER)
|
|
HANDLE(GRAPHICS_IOCTL_GET_HEAD_VERTICAL_OFFSET_BUFFER)
|
|
HANDLE(GRAPHICS_IOCTL_FLUSH_HEAD_BUFFERS)
|
|
HANDLE(GRAPHICS_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;
|
|
|
|
using syscall_arg_t = u64;
|
|
|
|
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(void const* source, Bytes target)
|
|
{
|
|
return Core::System::ptrace_peekbuf(g_pid, const_cast<void*>(source), target);
|
|
}
|
|
|
|
static ErrorOr<ByteBuffer> copy_from_process(void const* source, size_t length)
|
|
{
|
|
auto buffer = TRY(ByteBuffer::create_uninitialized(length));
|
|
TRY(copy_from_process(source, buffer.bytes()));
|
|
return buffer;
|
|
}
|
|
|
|
template<typename T>
|
|
static ErrorOr<T> copy_from_process(T const* 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##sv \
|
|
}
|
|
|
|
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(" | "sv));
|
|
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(" | "sv));
|
|
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"sv));
|
|
}
|
|
|
|
return {};
|
|
}
|
|
};
|
|
}
|
|
|
|
struct PointerArgument {
|
|
void const* 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"sv);
|
|
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"sv);
|
|
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(") = "sv);
|
|
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"sv);
|
|
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"sv);
|
|
}
|
|
|
|
StringView string_view()
|
|
{
|
|
return m_builder.string_view();
|
|
}
|
|
|
|
private:
|
|
void add_argument_separator()
|
|
{
|
|
if (!m_first_arg) {
|
|
m_builder.append(", "sv);
|
|
}
|
|
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 ErrorOr<void> format_realpath(FormattedSyscallBuilder& builder, Syscall::SC_realpath_params* params_p, size_t length)
|
|
{
|
|
auto params = TRY(copy_from_process(params_p));
|
|
builder.add_arguments(StringArgument { params.path }, StringArgument { { params.buffer.data, min(params.buffer.size, length) } });
|
|
return {};
|
|
}
|
|
|
|
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 ErrorOr<void> format_open(FormattedSyscallBuilder& builder, Syscall::SC_open_params* params_p)
|
|
{
|
|
auto params = TRY(copy_from_process(params_p));
|
|
|
|
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);
|
|
return {};
|
|
}
|
|
|
|
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.release_value_but_fixme_should_propagate_errors());
|
|
} 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_chdir(FormattedSyscallBuilder& builder, char const* path_p, size_t length)
|
|
{
|
|
auto buf = copy_from_process(path_p, length);
|
|
if (buf.is_error())
|
|
builder.add_arguments(buf.error());
|
|
else
|
|
builder.add_arguments(StringView { buf.value().data(), buf.value().size() });
|
|
}
|
|
|
|
static void format_fstat(FormattedSyscallBuilder& builder, int fd, struct stat* buf_p)
|
|
{
|
|
auto buf = copy_from_process(buf_p);
|
|
builder.add_arguments(fd, buf.release_value_but_fixme_should_propagate_errors());
|
|
}
|
|
|
|
static ErrorOr<void> format_stat(FormattedSyscallBuilder& builder, Syscall::SC_stat_params* params_p)
|
|
{
|
|
auto params = TRY(copy_from_process(params_p));
|
|
if (params.dirfd == AT_FDCWD)
|
|
builder.add_argument("AT_FDCWD");
|
|
else
|
|
builder.add_argument(params.dirfd);
|
|
builder.add_arguments(StringArgument { params.path }, TRY(copy_from_process(params.statbuf)), params.follow_symlinks);
|
|
return {};
|
|
}
|
|
|
|
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 ErrorOr<void> format_poll(FormattedSyscallBuilder& builder, Syscall::SC_poll_params* params_p)
|
|
{
|
|
// TODO: format fds and sigmask properly
|
|
auto params = TRY(copy_from_process(params_p));
|
|
builder.add_arguments(
|
|
params.nfds,
|
|
PointerArgument { params.fds },
|
|
TRY(copy_from_process(params.timeout)),
|
|
PointerArgument { params.sigmask });
|
|
return {};
|
|
}
|
|
|
|
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="sv);
|
|
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_deprecated_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).release_value_but_fixme_should_propagate_errors(), 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"sv;
|
|
};
|
|
|
|
struct MemoryProtectionFlags : BitflagBase {
|
|
static constexpr auto options = {
|
|
BITFLAG(PROT_READ), BITFLAG(PROT_WRITE), BITFLAG(PROT_EXEC)
|
|
};
|
|
static constexpr StringView default_ = "PROT_NONE"sv;
|
|
};
|
|
|
|
static ErrorOr<void> format_mmap(FormattedSyscallBuilder& builder, Syscall::SC_mmap_params* params_p)
|
|
{
|
|
auto params = TRY(copy_from_process(params_p));
|
|
builder.add_arguments(params.addr, params.size, MemoryProtectionFlags { params.prot }, MmapFlags { params.flags }, params.fd, params.offset, params.alignment, StringArgument { params.name });
|
|
return {};
|
|
}
|
|
|
|
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 ErrorOr<void> format_set_mmap_name(FormattedSyscallBuilder& builder, Syscall::SC_set_mmap_name_params* params_p)
|
|
{
|
|
auto params = TRY(copy_from_process(params_p));
|
|
builder.add_arguments(params.addr, params.size, StringArgument { params.name });
|
|
return {};
|
|
}
|
|
|
|
static void format_clock_gettime(FormattedSyscallBuilder& builder, clockid_t clockid, struct timespec* time)
|
|
{
|
|
builder.add_arguments(clockid_name(clockid), copy_from_process(time).release_value_but_fixme_should_propagate_errors());
|
|
}
|
|
|
|
static void format_dbgputstr(FormattedSyscallBuilder& builder, char* characters, size_t size)
|
|
{
|
|
builder.add_argument(StringArgument { { characters, size }, "\0\n"sv });
|
|
}
|
|
|
|
static ErrorOr<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_chdir:
|
|
format_chdir(builder, (char const*)arg1, (size_t)arg2);
|
|
result_type = Int;
|
|
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:
|
|
TRY(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:
|
|
TRY(format_open(builder, (Syscall::SC_open_params*)arg1));
|
|
break;
|
|
case SC_poll:
|
|
TRY(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:
|
|
TRY(format_realpath(builder, (Syscall::SC_realpath_params*)arg1, (size_t)res));
|
|
break;
|
|
case SC_recvmsg:
|
|
format_recvmsg(builder, (int)arg1, (struct msghdr*)arg2, (int)arg3);
|
|
result_type = Ssize;
|
|
break;
|
|
case SC_set_mmap_name:
|
|
TRY(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:
|
|
TRY(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;
|
|
}
|
|
return {};
|
|
}
|
|
|
|
ErrorOr<int> serenity_main(Main::Arguments arguments)
|
|
{
|
|
TRY(Core::System::pledge("stdio rpath wpath cpath proc exec ptrace sigaction"));
|
|
|
|
Vector<StringView> child_argv;
|
|
|
|
StringView output_filename;
|
|
StringView exclude_syscalls_option;
|
|
StringView include_syscalls_option;
|
|
HashTable<StringView> exclude_syscalls;
|
|
HashTable<StringView> include_syscalls;
|
|
|
|
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(arguments);
|
|
|
|
auto trace_file = output_filename.is_empty()
|
|
? TRY(Core::File::standard_error())
|
|
: TRY(Core::File::open(output_filename, Core::File::OpenMode::Write));
|
|
|
|
auto parse_syscalls = [](StringView option, auto& hash_table) {
|
|
if (!option.is_empty()) {
|
|
for (auto syscall : option.split_view(','))
|
|
hash_table.set(syscall);
|
|
}
|
|
};
|
|
parse_syscalls(exclude_syscalls_option, exclude_syscalls);
|
|
parse_syscalls(include_syscalls_option, include_syscalls);
|
|
|
|
TRY(Core::System::pledge("stdio rpath proc exec ptrace sigaction"));
|
|
|
|
int status;
|
|
if (g_pid == -1) {
|
|
if (child_argv.is_empty())
|
|
return Error::from_string_literal("Expected either a pid or some arguments");
|
|
|
|
auto pid = TRY(Core::System::fork());
|
|
|
|
if (!pid) {
|
|
TRY(Core::System::ptrace(PT_TRACE_ME, 0, 0, 0));
|
|
TRY(Core::System::exec(child_argv.first(), child_argv, Core::System::SearchInPath::Yes));
|
|
VERIFY_NOT_REACHED();
|
|
}
|
|
|
|
g_pid = pid;
|
|
if (waitpid(pid, &status, WSTOPPED | WEXITED) != pid || !WIFSTOPPED(status)) {
|
|
perror("waitpid");
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
struct sigaction sa = {};
|
|
sa.sa_handler = handle_sigint;
|
|
TRY(Core::System::sigaction(SIGINT, &sa, nullptr));
|
|
|
|
TRY(Core::System::ptrace(PT_ATTACH, g_pid, 0, 0));
|
|
if (waitpid(g_pid, &status, WSTOPPED | WEXITED) != g_pid || !WIFSTOPPED(status)) {
|
|
perror("waitpid");
|
|
return 1;
|
|
}
|
|
|
|
for (;;) {
|
|
TRY(Core::System::ptrace(PT_SYSCALL, g_pid, 0, 0));
|
|
|
|
if (waitpid(g_pid, &status, WSTOPPED | WEXITED) != g_pid || !WIFSTOPPED(status)) {
|
|
perror("wait_pid");
|
|
return 1;
|
|
}
|
|
PtraceRegisters regs = {};
|
|
TRY(Core::System::ptrace(PT_GETREGS, g_pid, ®s, 0));
|
|
#if ARCH(X86_64)
|
|
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;
|
|
#elif ARCH(AARCH64)
|
|
syscall_arg_t syscall_index = 0; // FIXME
|
|
syscall_arg_t arg1 = 0; // FIXME
|
|
syscall_arg_t arg2 = 0; // FIXME
|
|
syscall_arg_t arg3 = 0; // FIXME
|
|
TODO_AARCH64();
|
|
#else
|
|
# error Unknown architecture
|
|
#endif
|
|
|
|
TRY(Core::System::ptrace(PT_SYSCALL, g_pid, 0, 0));
|
|
if (waitpid(g_pid, &status, WSTOPPED | WEXITED) != g_pid || !WIFSTOPPED(status)) {
|
|
perror("wait_pid");
|
|
return 1;
|
|
}
|
|
|
|
TRY(Core::System::ptrace(PT_GETREGS, g_pid, ®s, 0));
|
|
|
|
#if ARCH(X86_64)
|
|
u64 res = regs.rax;
|
|
#elif ARCH(AARCH64)
|
|
u64 res = 0; // FIXME
|
|
TODO_AARCH64();
|
|
#else
|
|
# error Unknown architecture
|
|
#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);
|
|
TRY(format_syscall(builder, syscall_function, arg1, arg2, arg3, res));
|
|
|
|
TRY(trace_file->write_until_depleted(builder.string_view().bytes()));
|
|
}
|
|
}
|