Compare commits

..

5 Commits

Author SHA1 Message Date
fb5508e369 Rebrand! Rebrand! 2024-06-08 05:28:22 -04:00
54b79db457 Remove PTrace 2024-06-08 04:46:07 -04:00
50d94d5299 Speed! 2024-06-08 04:34:20 -04:00
06e9f4d7c8 WIP Forever 2024-06-08 04:23:03 -04:00
027e58ee9b WIP 2024-06-08 03:09:44 -04:00
15 changed files with 78 additions and 258 deletions

View File

@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 3.17.0) cmake_minimum_required(VERSION 3.17.0)
# Start Project # Start Project
project(trampoline) project(runtime)
# Headers # Headers
add_library(trampoline-headers INTERFACE) add_library(trampoline-headers INTERFACE)
@ -11,21 +11,23 @@ target_include_directories(trampoline-headers INTERFACE include)
if(NOT TRAMPOLINE_HEADERS_ONLY) if(NOT TRAMPOLINE_HEADERS_ONLY)
# Check Architecture # Check Architecture
include(CheckSymbolExists) include(CheckSymbolExists)
check_symbol_exists("__aarch64__" "" USE_NATIVE_TRAMPOLINE) check_symbol_exists("__aarch64__" "" USE_NATIVE_RUNTIME)
check_symbol_exists("__x86_64__" "" USE_QEMU_TRAMPOLINE) check_symbol_exists("__x86_64__" "" USE_QEMU_RUNTIME)
# Include Correct Sub-Project # Include Correct Sub-Project
if(USE_NATIVE_TRAMPOLINE) set(RUNTIME_RPATH "$ORIGIN/../lib/native")
set(RUNTIME_EXTRA_LINK_FLAG "--disable-new-dtags")
target_compile_definitions(trampoline-headers INTERFACE MCPI_BUILD_RUNTIME)
if(USE_NATIVE_RUNTIME)
add_subdirectory(native) add_subdirectory(native)
target_compile_definitions(trampoline-headers INTERFACE MCPI_USE_NATIVE_TRAMPOLINE) elseif(USE_QEMU_RUNTIME)
elseif(USE_QEMU_TRAMPOLINE)
add_subdirectory(qemu) add_subdirectory(qemu)
target_compile_definitions(trampoline-headers INTERFACE MCPI_USE_QEMU) target_compile_definitions(trampoline-headers INTERFACE MCPI_RUNTIME_IS_QEMU)
else() else()
message(FATAL_ERROR "Unsupported Architecture") message(FATAL_ERROR "Unsupported Architecture")
endif() endif()
else() else()
# No-Op Install Function # No-Op Install Function
function(install_trampoline) function(install_runtime)
endfunction() endfunction()
endif() endif()

19
README.md Normal file
View File

@ -0,0 +1,19 @@
# MCPI Runtime
**Fact:** MCPI is a 32-bit ARM binary.
**Another fact:** Most modern computers do not use 32-bit ARM and therefore cannot run MCPI natively.
**Solution:** This project allows MCPI to run on modern computers.
## How
This project works differently depending on the host system's architecture.
### 64-Bit x86 Host
On this platform, a patched version of QEMU is used.
QEMU emulates ARM code so MCPI can run on x86 hardware. And the patch adds a system-call which allows MCPI to run graphics code on the host. This prevents the need for emulated GPU drivers.
### 64-Bit ARM Host
QEMU is not necessary on this platform because it can already run 32-bit ARM code natively.
Instead, the runtime is implemented as two processes: a parent and a child. The child becomes MCPI and can send graphics commands to the parent. And because the parent is 64-bit, 32-bit drivers are not needed.

View File

@ -2,26 +2,21 @@
#include <stdint.h> #include <stdint.h>
// Switch Between Pipe?PTrace Mode // Maximum Arguments Length
#ifdef MCPI_USE_NATIVE_TRAMPOLINE #define MAX_TRAMPOLINE_ARGS_SIZE 2097152 // 2 MiB
#define TRAMPOLINE_USE_PTRACE_ENV "_MCPI_TRAMPOLINE_USE_PTRACE"
#endif
// System Call Constants // System Call Constants
#define MAX_TRAMPOLINE_ARGS_SIZE 2097152 // 2 MiB
#define TRAMPOLINE_SYSCALL 0x1337 #define TRAMPOLINE_SYSCALL 0x1337
// Pipe Constants // Pipe Constants
#ifdef MCPI_USE_NATIVE_TRAMPOLINE
#define TRAMPOLINE_ARGUMENTS_PIPE_ENV "_MCPI_TRAMPOLINE_ARGUMENTS" #define TRAMPOLINE_ARGUMENTS_PIPE_ENV "_MCPI_TRAMPOLINE_ARGUMENTS"
#define TRAMPOLINE_RETURN_VALUE_PIPE_ENV "_MCPI_TRAMPOLINE_RETURN_VALUE" #define TRAMPOLINE_RETURN_VALUE_PIPE_ENV "_MCPI_TRAMPOLINE_RETURN_VALUE"
struct trampoline_pipe_arguments { struct trampoline_pipe_arguments {
uint32_t id; uint32_t id;
uint32_t allow_early_return;
uint32_t length; uint32_t length;
uint32_t args_addr;
}; };
#endif
// Function Types // Function Types
typedef void (*trampoline_writer_t)(uint32_t guest_addr, void *data, uint32_t size); typedef void (*trampoline_writer_t)(uint32_t guest_addr, const void *data, uint32_t size);
typedef uint32_t (*trampoline_t)(trampoline_writer_t writer, uint32_t id, const unsigned char *args); typedef uint32_t (*trampoline_t)(trampoline_writer_t writer, uint32_t id, const unsigned char *args);

View File

@ -1,27 +1,25 @@
project(native-trampoline) project(native-runtime)
# Build # Build
add_executable(trampoline add_executable(runtime
src/memory.cpp src/memory.cpp
src/main.cpp src/main.cpp
src/trampoline.cpp src/trampoline.cpp
src/ptrace/loop.cpp src/pipe.cpp
src/ptrace/init.cpp
src/pipe/pipe.cpp
src/signals.cpp src/signals.cpp
) )
# Warnings # Warnings
target_compile_options(trampoline PRIVATE -Wall -Wextra -Werror -Wpointer-arith -Wshadow -Wnull-dereference) target_compile_options(runtime PRIVATE -Wall -Wextra -Werror -Wpointer-arith -Wshadow -Wnull-dereference)
# Link # Link
target_link_libraries(trampoline dl trampoline-headers) target_link_libraries(runtime dl rt trampoline-headers)
# RPath # RPath
set_target_properties(trampoline PROPERTIES INSTALL_RPATH "$ORIGIN/../lib/native") set_target_properties(runtime PROPERTIES INSTALL_RPATH "${RUNTIME_RPATH}")
target_link_options(trampoline PRIVATE "LINKER:--disable-new-dtags") target_link_options(runtime PRIVATE "LINKER:${RUNTIME_EXTRA_LINK_FLAG}")
# Install # Install
function(install_trampoline bin_dir) function(install_runtime bin_dir)
install(TARGETS trampoline DESTINATION "${bin_dir}") install(TARGETS runtime DESTINATION "${bin_dir}")
endfunction() endfunction()

View File

@ -5,6 +5,6 @@
#define ERR(format, ...) \ #define ERR(format, ...) \
{ \ { \
fprintf(stderr, "TRAMPOLINE ERROR: " format "\n", ##__VA_ARGS__); \ fprintf(stderr, "RUNTIME ERROR: " format "\n", ##__VA_ARGS__); \
exit(EXIT_FAILURE); \ exit(EXIT_FAILURE); \
} }

View File

@ -2,12 +2,12 @@
#include <cstring> #include <cstring>
#include <cerrno> #include <cerrno>
#include <sys/wait.h> #include <sys/wait.h>
#include <sys/prctl.h>
#include "log.h" #include "log.h"
#include "memory.h" #include "memory.h"
#include "trampoline.h" #include "trampoline.h"
#include "ptrace/ptrace.h" #include "pipe.h"
#include "pipe/pipe.h"
#include "signals.h" #include "signals.h"
// Main // Main
@ -16,11 +16,8 @@ int main(__attribute__((unused)) int argc, char *argv[]) {
if (argc < 2) { if (argc < 2) {
ERR("Invalid Arguments"); ERR("Invalid Arguments");
} }
bool use_ptrace = getenv(TRAMPOLINE_USE_PTRACE_ENV) != nullptr;
// Setup // Setup
if (!use_ptrace) { init_pipe_common();
init_pipe_common();
}
// Fork // Fork
pid_t pid = fork(); pid_t pid = fork();
if (pid == -1) { if (pid == -1) {
@ -28,11 +25,9 @@ int main(__attribute__((unused)) int argc, char *argv[]) {
} else if (pid == 0) { } else if (pid == 0) {
// Setup // Setup
setpgid(0, 0); setpgid(0, 0);
if (use_ptrace) { init_pipe_guest();
init_ptrace_guest(); // Kill Child If Parent Exits First
} else { prctl(PR_SET_PDEATHSIG, SIGKILL);
init_pipe_guest();
}
// Execute Program // Execute Program
execvp(argv[1], (char *const *) &argv[1]); execvp(argv[1], (char *const *) &argv[1]);
ERR("Unable To Execute Program: %s: %s", argv[1], strerror(errno)); ERR("Unable To Execute Program: %s: %s", argv[1], strerror(errno));
@ -44,11 +39,7 @@ int main(__attribute__((unused)) int argc, char *argv[]) {
init_memory(pid); init_memory(pid);
init_trampoline(); init_trampoline();
// Start PTrace // Start Pipes
if (use_ptrace) { init_pipe_host(pid);
init_ptrace_host(pid);
} else {
init_pipe_host(pid);
}
} }
} }

View File

@ -5,38 +5,19 @@
#include "memory.h" #include "memory.h"
#include "log.h" #include "log.h"
// Store PID // Read/Write Memory
static pid_t guest_pid; static pid_t guest_pid;
void init_memory(pid_t pid) { void init_memory(pid_t pid) {
guest_pid = pid; guest_pid = pid;
} }
void memory_writer(uint32_t guest_addr, const void *data, uint32_t size) {
// Read Memory
void memory_reader(uint32_t guest_addr, void *data, uint32_t size) {
if (size == 0) {
return;
}
iovec local[1]; iovec local[1];
local[0].iov_base = data; local[0].iov_base = (void *) data;
local[0].iov_len = size; local[0].iov_len = size;
iovec remote[1]; iovec remote[1];
remote[0].iov_base = (void *) (uint64_t) guest_addr; remote[0].iov_base = (void *) (uint64_t) guest_addr;
remote[0].iov_len = size; remote[0].iov_len = size;
ssize_t ret = process_vm_readv(guest_pid, local, 1, remote, 1, 0); const ssize_t ret = process_vm_writev(guest_pid, local, 1, remote, 1, 0);
if (ret != size) {
ERR("Unable To Read Data: %s", strerror(errno));
}
}
// Write Memory
void memory_writer(uint32_t guest_addr, void *data, uint32_t size) {
iovec local[1];
local[0].iov_base = data;
local[0].iov_len = size;
iovec remote[1];
remote[0].iov_base = (void *) (uint64_t) guest_addr;
remote[0].iov_len = size;
ssize_t ret = process_vm_writev(guest_pid, local, 1, remote, 1, 0);
if (ret != size) { if (ret != size) {
ERR("Unable To Write Data: %s", strerror(errno)); ERR("Unable To Write Data: %s", strerror(errno));
} }

View File

@ -3,6 +3,6 @@
#include <cstdint> #include <cstdint>
#include <sys/types.h> #include <sys/types.h>
void memory_reader(uint32_t guest_addr, void *data, uint32_t size); // Read External Memory
void memory_writer(uint32_t guest_addr, void *data, uint32_t size); void memory_writer(uint32_t guest_addr, const void *data, uint32_t size);
void init_memory(pid_t guest_pid); void init_memory(pid_t guest_pid);

View File

@ -8,9 +8,9 @@
#include "pipe.h" #include "pipe.h"
#include "../log.h" #include "log.h"
#include "../trampoline.h" #include "trampoline.h"
#include "../memory.h" #include "memory.h"
// Setup Pipes // Setup Pipes
#define PIPE_READ 0 #define PIPE_READ 0
@ -47,15 +47,26 @@ void init_pipe_host(pid_t guest_pid) {
trampoline_pipe_arguments cmd = {}; trampoline_pipe_arguments cmd = {};
while (read(arguments_pipe[PIPE_READ], &cmd, sizeof(trampoline_pipe_arguments)) > 0) { while (read(arguments_pipe[PIPE_READ], &cmd, sizeof(trampoline_pipe_arguments)) > 0) {
static unsigned char args[MAX_TRAMPOLINE_ARGS_SIZE]; static unsigned char args[MAX_TRAMPOLINE_ARGS_SIZE];
memory_reader(cmd.args_addr, args, cmd.length); size_t position = 0;
while (position < cmd.length) {
ssize_t ret = read(arguments_pipe[PIPE_READ], args + position, cmd.length - position);
if (ret == -1) {
ERR("Unable To Read Trampoline Arguments");
} else {
position += ret;
}
}
uint32_t ret = trampoline(cmd.id, args); uint32_t ret = trampoline(cmd.id, args);
write(return_value_pipe[PIPE_WRITE], &ret, sizeof(uint32_t)); if (!cmd.allow_early_return) {
write(return_value_pipe[PIPE_WRITE], &ret, sizeof(uint32_t));
}
} }
// Reap Child // Reap Child
int status; int status;
if (waitpid(guest_pid, &status, 0) == -1) { if (waitpid(guest_pid, &status, 0) == -1) {
ERR("Unable To Reap Child: %s", strerror(errno)); ERR("Unable To Reap Child: %s", strerror(errno));
} }
// Exit
if (WIFEXITED(status)) { if (WIFEXITED(status)) {
exit(WEXITSTATUS(status)); exit(WEXITSTATUS(status));
} else { } else {

View File

@ -1,46 +0,0 @@
#include <linux/seccomp.h>
#include <linux/filter.h>
#include <sys/prctl.h>
#include <cstddef>
#include <cerrno>
#include <cstring>
#include <trampoline/types.h>
#include "ptrace.h"
#include "../log.h"
// Init
void init_ptrace_host(pid_t guest_pid) {
// Wait For PTrace
safe_wait(guest_pid, nullptr);
// Configure PTrace
safe_ptrace(PTRACE_SETOPTIONS, guest_pid, nullptr, (void *) (PTRACE_O_EXITKILL | PTRACE_O_TRACESECCOMP));
// Start Loop
loop_ptrace(guest_pid);
}
void init_ptrace_guest() {
// seccomp Filter
sock_filter filter[] = {
// Load Syscall Number To Accumulator
BPF_STMT(BPF_LD + BPF_W + BPF_ABS, offsetof(seccomp_data, nr)),
// If Syscall Does Not Match The Trampoline, Then Skip The Next Instruction
BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, TRAMPOLINE_SYSCALL, 0, 1),
// Trigger PTrace And End Filter
BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_TRACE),
// Allow Syscall And End Filter
BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_ALLOW)
};
sock_fprog prog = {
.len = (unsigned short) (sizeof(filter) / sizeof(filter[0])),
.filter = filter
};
// Setup PTrace
safe_ptrace(PTRACE_TRACEME, 0, nullptr, nullptr);
if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == -1) {
ERR("Unable To Prepare Process For Seccomp: %s", strerror(errno));
}
if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog) == -1) {
ERR("Unable To Set Seccomp Filter: %s", strerror(errno));
}
}

View File

@ -1,117 +0,0 @@
#include <sys/wait.h>
#include <sys/uio.h>
#include <cstring>
#include <cerrno>
#include <elf.h>
#ifndef __aarch64__
#error "Unsupported Host Architecture"
#endif
#include <asm/ptrace.h>
#include "ptrace.h"
#include "../log.h"
#include "../trampoline.h"
#include "../memory.h"
// Helper Functions
void safe_ptrace(__ptrace_request request, pid_t pid, void *addr, void *data) {
if (ptrace(request, pid, addr, data) == -1) {
ERR("PTrace Error: %s", strerror(errno));
}
}
void safe_wait(pid_t guest_pid, int *status) {
int real_status;
if (waitpid(guest_pid, &real_status, 0) == -1) {
ERR("Unable To Wait For Guest");
}
if (WIFEXITED(real_status)) {
// Process Exited
exit(WEXITSTATUS(real_status));
}
if (status != nullptr) {
*status = real_status;
}
}
static void ptrace_wait_syscall(pid_t guest_pid) {
while (true) {
safe_ptrace(PTRACE_CONT, guest_pid, nullptr, nullptr);
int status;
safe_wait(guest_pid, &status);
if ((status >> 8) == (SIGTRAP | (PTRACE_EVENT_SECCOMP << 8))) {
return;
}
}
}
// Registers
struct arm32_pt_regs {
uint32_t regs[18];
};
static bool check_syscall(pid_t guest_pid, arm32_pt_regs *out) {
// Read Registers
arm32_pt_regs regs = {};
iovec io = {
.iov_base = &regs,
.iov_len = sizeof(regs)
};
safe_ptrace(PTRACE_GETREGSET, guest_pid, (void *) NT_PRSTATUS, &io);
if (io.iov_len != sizeof(regs)) {
ERR("Guest Must Be 32-Bit");
}
// Check Syscall
if (regs.regs[7] != TRAMPOLINE_SYSCALL) {
// Not Trampoline
return false;
}
// Block Syscall
int new_syscall = -1;
iovec syscall_io = {
.iov_base = &new_syscall,
.iov_len = sizeof(int),
};
safe_ptrace(PTRACE_SETREGSET, guest_pid, (void *) NT_ARM_SYSTEM_CALL, &syscall_io);
// Export Registers
*out = regs;
// Return
return true;
}
static void get_arguments(arm32_pt_regs *regs, uint32_t *arg1, uint32_t *arg2, uint32_t *arg3) {
*arg1 = regs->regs[0];
*arg2 = regs->regs[1];
*arg3 = regs->regs[2];
}
static void set_syscall_return(pid_t guest_pid, arm32_pt_regs *regs) {
regs->regs[0] = 0;
iovec io = {
.iov_base = regs,
.iov_len = sizeof(arm32_pt_regs)
};
safe_ptrace(PTRACE_SETREGSET, guest_pid, (void *) NT_PRSTATUS, &io);
}
// Main Loop
void loop_ptrace(pid_t guest_pid) {
while (true) {
// Wait For Syscall
ptrace_wait_syscall(guest_pid);
// Handle Syscall
arm32_pt_regs regs = {};
bool is_trampoline = check_syscall(guest_pid, &regs);
if (is_trampoline) {
// Get Arguments
uint32_t id;
uint32_t length;
uint32_t args_addr;
get_arguments(&regs, &id, &length, &args_addr);
static unsigned char args[MAX_TRAMPOLINE_ARGS_SIZE];
memory_reader(args_addr, args, length);
// Run Trampoline
uint32_t ret = trampoline(id, args);
memory_writer(args_addr, &ret, sizeof(uint32_t));
// Set Syscall Return
set_syscall_return(guest_pid, &regs);
}
}
}

View File

@ -1,10 +0,0 @@
#pragma once
#include <sys/types.h>
#include <sys/ptrace.h>
void safe_ptrace(__ptrace_request request, pid_t pid, void *addr, void *data);
void safe_wait(pid_t guest_pid, int *status);
void init_ptrace_host(pid_t guest_pid);
void init_ptrace_guest();
void loop_ptrace(pid_t guest_pid);

View File

@ -2,10 +2,6 @@
#include "signals.h" #include "signals.h"
#include <cstdio>
#include <cstring>
#include <cerrno>
// Signal Handlers // Signal Handlers
static pid_t guest_pid = -1; static pid_t guest_pid = -1;
static void exit_handler(__attribute__((unused)) int signal_id) { static void exit_handler(__attribute__((unused)) int signal_id) {

View File

@ -6,7 +6,7 @@ if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.24.0)
endif() endif()
# Archive # Archive
if(NOT DEFINED TRAMPOLINE_QEMU_ARCHIVE) if(NOT DEFINED RUNTIME_QEMU_ARCHIVE)
message(FATAL_ERROR "Missing QEMU Archive") message(FATAL_ERROR "Missing QEMU Archive")
endif() endif()
@ -24,7 +24,7 @@ if(DEFINED ENV{PKG_CONFIG_LIBDIR})
endif() endif()
set(EXTRA_C_FLAGS "-s -I${CMAKE_CURRENT_SOURCE_DIR}/../include") set(EXTRA_C_FLAGS "-s -I${CMAKE_CURRENT_SOURCE_DIR}/../include")
ExternalProject_Add(qemu ExternalProject_Add(qemu
URL "${TRAMPOLINE_QEMU_ARCHIVE}" URL "${RUNTIME_QEMU_ARCHIVE}"
# Configure Build # Configure Build
CONFIGURE_COMMAND CONFIGURE_COMMAND
"${CMAKE_COMMAND}" "-E" "env" "${CMAKE_COMMAND}" "-E" "env"
@ -36,7 +36,7 @@ ExternalProject_Add(qemu
"--cross-prefix=" "--cross-prefix="
"--cc=${CMAKE_C_COMPILER}" "--cc=${CMAKE_C_COMPILER}"
"--cxx=${CMAKE_CXX_COMPILER}" "--cxx=${CMAKE_CXX_COMPILER}"
"--extra-ldflags=-ldl -Wl,-rpath=$ORIGIN/../lib/native -Wl,--disable-new-dtags" "--extra-ldflags=-ldl -Wl,-rpath=${RUNTIME_RPATH} -Wl,${RUNTIME_EXTRA_LINK_FLAG}"
"--disable-debug-info" "--disable-debug-info"
"--target-list=arm-linux-user" "--target-list=arm-linux-user"
"--without-default-features" "--without-default-features"
@ -54,9 +54,9 @@ ExternalProject_Add(qemu
) )
# Install # Install
function(install_trampoline bin_dir legal_dir) function(install_runtime bin_dir legal_dir)
ExternalProject_Get_property(qemu BINARY_DIR) ExternalProject_Get_property(qemu BINARY_DIR)
install(PROGRAMS "${BINARY_DIR}/qemu-arm" DESTINATION "${bin_dir}" RENAME "trampoline") install(PROGRAMS "${BINARY_DIR}/qemu-arm" DESTINATION "${bin_dir}" RENAME "runtime")
# License # License
ExternalProject_Get_property(qemu SOURCE_DIR) ExternalProject_Get_property(qemu SOURCE_DIR)
install(FILES "${SOURCE_DIR}/COPYING" DESTINATION "${legal_dir}/qemu") install(FILES "${SOURCE_DIR}/COPYING" DESTINATION "${legal_dir}/qemu")