diff --git a/CMakeLists.txt b/CMakeLists.txt index 2b177be..1eac7f7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,6 +19,7 @@ add_executable(runtime ) # QEMU +include(CheckSymbolExists) check_symbol_exists("__x86_64__" "" USE_QEMU) if(USE_QEMU) add_subdirectory(qemu) @@ -39,9 +40,13 @@ target_link_libraries(runtime trampoline-headers ) +# External Library +set(TRAMPOLINE_LIBRARY_NAME "trampoline" CACHE STRING "Trampoline Library That The Runtime Uses") +target_compile_definitions(runtime PRIVATE "TRAMPOLINE_LIBRARY=\"lib${TRAMPOLINE_LIBRARY_NAME}.so\"") + # Install -if(DEFINED MCPI_BIN_DIR) - install(TARGETS runtime DESTINATION "${MCPI_BIN_DIR}") +if(DEFINED MCPI_LIB_DIR) + install(TARGETS runtime DESTINATION "${MCPI_LIB_DIR}") # License install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE" DESTINATION "${MCPI_LEGAL_DIR}/${PROJECT_NAME}") endif() \ No newline at end of file diff --git a/README.md b/README.md index 4ba1389..998c8d6 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,13 @@ # Reborn Runtime This is a simple program allowing ARM32 code to easily call "native" code. -By running an ARM32 program inside this runtime, it gains the ability to call `raw_trampoline`. This function copies its arguments and passes them to the `trampoline` function inside `libmedia-layer-trampoline.so`. +By running an ARM32 program inside this runtime, it gains the ability to call `raw_trampoline`. This function copies its arguments and passes them to the `trampoline` function inside `libtrampoline.so`. The runtime also automatically uses QEMU on x86_64 systems. ## Terminology -- "Guest" code is the main ARM32 program. -- "Host" code is the native code located in `libmedia-layer-trampoline.so`. +- "Guest" code is the main ARM32 program. It is running inside the runtime. +- "Host" code is the native code located in `libtrampoline.so`. It is running "alongside" the runtime. ## Example There is a simple C example [here](./example). It sends a given string to the host, which returns the string's length multiplied by two. @@ -15,7 +15,7 @@ There is a simple C example [here](./example). It sends a given string to the ho ## Early Returning `raw_trampoline` supports returning before the host code has finished executing. This can be enabled by passing `true` to `allow_early_return`. However, this is only supported in certain circumstances, can cause race-conditions, and prevents the guest code from receiving the host's return value. -## Syscall Versus Pipe Trampolines +## Syscall Vs. Pipe Trampolines The runtime supports two methods of passing data between the guest and host. - System Call - Data is passed through a custom system-call added to QEMU. diff --git a/example/build.sh b/example/build.sh index ff0fa4f..df2799a 100755 --- a/example/build.sh +++ b/example/build.sh @@ -13,30 +13,21 @@ out 'set -e' chmod +x "${RUN}" # Create Build Directory -rm -rf build dir() { - mkdir "$1" + mkdir -p "$1" cd "$1" } dir build -# Build Runtime -dir runtime -build() { - cmake -GNinja "../../$1" - cmake --build . -} -build ../ -out "export PATH=\"$(pwd):\${PATH}\"" -cd ../ - # Build Host Component build_example_part() { dir "$1" - build "$1" + cmake -GNinja "../../$1" + cmake --build . } build_example_part host out "export LD_LIBRARY_PATH=\"$(pwd):\${LD_LIBRARY_PATH}\"" +out "export PATH=\"$(pwd)/runtime:\${PATH}\"" cd ../ # Build Guest Component diff --git a/example/guest/CMakeLists.txt b/example/guest/CMakeLists.txt index 3e76e41..28aaf37 100644 --- a/example/guest/CMakeLists.txt +++ b/example/guest/CMakeLists.txt @@ -10,7 +10,7 @@ set(CMAKE_SYSTEM_PROCESSOR "arm") project(guest) # Build Library -add_subdirectory(../../lib lib) +add_subdirectory(../../ runtime) # Build add_executable(example src/example.c) diff --git a/example/guest/src/example.c b/example/guest/src/example.c index 6f6400c..df0158e 100644 --- a/example/guest/src/example.c +++ b/example/guest/src/example.c @@ -1,19 +1,28 @@ #include #include -#include #include +// Call Trampoline With String void run(uint32_t cmd, const char *str) { - // Arguments Must Be Modifiable - unsigned char *args = (unsigned char *) strdup(str); - // Sned Command + unsigned char *args = (unsigned char *) str; + // Send Command fprintf(stderr, "Guest Is Sending: %u: %s\n", cmd, str); - uint32_t ret = raw_trampoline(cmd, 0, strlen(str) + 1, args); + uint32_t ret; + raw_trampoline(cmd, &ret, strlen(str) + 1, args); fprintf(stderr, "Guest Has Received: %u\n", ret); - free(args); } + +// Main +#define INFO(str) fprintf(stderr, "==== %s ====\n", str) int main() { + // Normal Calls + INFO("Testing Normal Trampoline Calls"); run(0, "Hello World!"); run(1, "Bye World!"); + // Calls without a return value *may* return control + // to guest code before the call has finished. + INFO("Testing Trampoline Call That May Return Early"); + raw_trampoline(100, NULL, 0, NULL); + fprintf(stderr, "Control Returned To Guest\n"); } \ No newline at end of file diff --git a/example/host/CMakeLists.txt b/example/host/CMakeLists.txt index 03b248d..b475450 100644 --- a/example/host/CMakeLists.txt +++ b/example/host/CMakeLists.txt @@ -4,8 +4,8 @@ cmake_minimum_required(VERSION 3.17.0) project(host) # Build Library -add_subdirectory(../../lib lib) +add_subdirectory(../../ runtime) # Build -add_library(media-layer-trampoline SHARED src/trampoline.c) -target_link_libraries(media-layer-trampoline trampoline-headers) \ No newline at end of file +add_library("${TRAMPOLINE_LIBRARY_NAME}" SHARED src/trampoline.c) +target_link_libraries("${TRAMPOLINE_LIBRARY_NAME}" trampoline-headers) \ No newline at end of file diff --git a/example/host/src/trampoline.c b/example/host/src/trampoline.c index 5360daf..fea6c7b 100644 --- a/example/host/src/trampoline.c +++ b/example/host/src/trampoline.c @@ -1,5 +1,6 @@ #include #include +#include #include @@ -8,7 +9,16 @@ // args: Pointer To Command Arguments // Return Value: Returned To The Guest (Unless Early Return Is Enabled) uint32_t trampoline(trampoline_writer_t writer, uint32_t id, const unsigned char *args) { - const char *str = (const char *) args; - fprintf(stderr, "Host Has Recieved: %u: %s\n", id, str); - return strlen(str) * 2; + if (id == 100 /* Defined In ../../guest/src/example.c */) { + // Early Return Allowed + fprintf(stderr, "Early Return Call Started\n"); + sleep(1); + fprintf(stderr, "Early Return Call Done\n"); + return 0; + } else { + // Normal Call + const char *str = (const char *) args; + fprintf(stderr, "Host Has Received: %u: %s\n", id, str); + return strlen(str) * 2; + } } \ No newline at end of file diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index d7ca7b6..2d174f6 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -1,6 +1,3 @@ -cmake_minimum_required(VERSION 3.17.0) - -# Start Project project(trampoline) # Headers @@ -16,5 +13,9 @@ if(NOT TRAMPOLINE_IS_GUEST) endif() # Library To Call Trampoline -add_library(trampoline OBJECT src/guest.cpp) +add_library(trampoline OBJECT + src/lib.cpp + src/syscall.cpp + src/pipe.cpp +) target_link_libraries(trampoline trampoline-headers) \ No newline at end of file diff --git a/lib/include/trampoline/types.h b/lib/include/trampoline/types.h index 0078936..053c028 100644 --- a/lib/include/trampoline/types.h +++ b/lib/include/trampoline/types.h @@ -38,7 +38,7 @@ typedef trampoline_raw_t *trampoline_t; // Call Trampoline From Guest Code #ifndef MCPI_BUILD_RUNTIME -uint32_t raw_trampoline(uint32_t id, int allow_early_return, uint32_t length, unsigned char *args); +uint32_t raw_trampoline(uint32_t id, uint32_t *ret /* Can Be Null */, uint32_t length, const unsigned char *args); #endif // C++ diff --git a/lib/src/guest.cpp b/lib/src/guest.cpp deleted file mode 100644 index ece1315..0000000 --- a/lib/src/guest.cpp +++ /dev/null @@ -1,86 +0,0 @@ -#include -#include -#include - -#include - -// Logging -#define ERR(format, ...) \ - ({ \ - fprintf(stderr, "TRAMPOLINE ERROR: " format "\n", ##__VA_ARGS__); \ - exit(EXIT_FAILURE); \ - }) - -// Syscall Method -static uint32_t trampoline_syscall(const uint32_t id, unsigned char *args) { - // Make Syscall - const long ret = syscall(TRAMPOLINE_SYSCALL, id, args); // This Modifies Arguments - if (ret == -1) { - // Error - ERR("System Call Error: %s", strerror(errno)); - } - // Return - return *(uint32_t *) args; -} - -// Pipe Method -static int get_pipe(const char *env) { - const char *value = getenv(env); - if (value == nullptr) { - ERR("Missing Variable: %s", env); - } - const std::string str = value; - return std::stoi(str); -} -static uint32_t trampoline_pipe(const uint32_t id, const bool allow_early_return, const uint32_t length, const unsigned char *args) { - // Get Pipes - static int arguments_pipe = -1; - static int return_value_pipe = -1; - if (arguments_pipe == -1) { - arguments_pipe = get_pipe(_MCPI_TRAMPOLINE_ARGUMENTS_ENV); - return_value_pipe = get_pipe(_MCPI_TRAMPOLINE_RETURN_VALUE_ENV); - } - // Write Command - const trampoline_pipe_arguments cmd = { - .id = id, - .allow_early_return = allow_early_return, - .length = length - }; - if (write(arguments_pipe, &cmd, sizeof(trampoline_pipe_arguments)) != sizeof(trampoline_pipe_arguments)) { - ERR("Unable To Write Command"); - } - // Write Arguments - size_t position = 0; - while (position < length) { - const ssize_t ret = write(arguments_pipe, args + position, length - position); - if (ret == -1) { - ERR("Unable To Write Arguments"); - } else { - position += ret; - } - } - if (allow_early_return) { - return 0; - } - // Return - uint32_t ret; - if (read(return_value_pipe, &ret, sizeof(uint32_t)) != sizeof(uint32_t)) { - ERR("Unable To Read Return Value"); - } - return ret; -} - -// Main Function -uint32_t raw_trampoline(const uint32_t id, const int allow_early_return, const uint32_t length, unsigned char *args) { - if (length > MAX_TRAMPOLINE_ARGS_SIZE) { - ERR("Command Too Big"); - } - // Configure Method - static bool use_syscall = getenv(MCPI_USE_PIPE_TRAMPOLINE_ENV) == nullptr; - // Use Correct Method - if (use_syscall) { - return trampoline_syscall(id, args); - } else { - return trampoline_pipe(id, allow_early_return, length, args); - } -} \ No newline at end of file diff --git a/lib/src/lib.cpp b/lib/src/lib.cpp new file mode 100644 index 0000000..5b6c134 --- /dev/null +++ b/lib/src/lib.cpp @@ -0,0 +1,22 @@ +#include "lib.h" + +// Call +static Trampoline::Error trampoline(const uint32_t id, uint32_t *ret, const uint32_t length, const unsigned char *args) { + // Check Arguments Length + if (length > MAX_TRAMPOLINE_ARGS_SIZE) { + return Trampoline::Error::COMMAND_TOO_BIG; + } + // Configure Method + static bool use_syscall = SyscallTrampoline::should_use(); + // Use Correct Method + if (use_syscall) { + static SyscallTrampoline syscall; + return syscall.call(id, ret, length, args); + } else { + static PipeTrampoline pipe; + return pipe.call(id, ret, length, args); + } +} +uint32_t raw_trampoline(const uint32_t id, uint32_t *ret, const uint32_t length, const unsigned char *args) { + return uint32_t(trampoline(id, ret, length, args)); +} \ No newline at end of file diff --git a/lib/src/lib.h b/lib/src/lib.h new file mode 100644 index 0000000..d085b9b --- /dev/null +++ b/lib/src/lib.h @@ -0,0 +1,41 @@ +#pragma once + +#include + +#include + +// Common +struct Trampoline { + virtual ~Trampoline() = default; + // Error Codes + enum class Error : uint32_t { + NONE = 0, + // Generic + COMMAND_TOO_BIG, + // System Call + SYSCALL, + // Pipe + MISSING_PIPE, + INVALID_PIPE, + PIPE_WRITE, + PIPE_READ + }; + // Call + virtual Error call(uint32_t id, uint32_t *ret_ptr, uint32_t length, const unsigned char *args) = 0; +}; + +// Syscall Method +struct SyscallTrampoline final : Trampoline { + static bool should_use(); + Error call(uint32_t id, uint32_t *ret_ptr, uint32_t length, const unsigned char *args) override; +}; + +// Pipe Method +struct PipeTrampoline final : Trampoline { + PipeTrampoline(); + Error call(uint32_t id, uint32_t *ret_ptr, uint32_t length, const unsigned char *args) override; +private: + Error status; + int arguments_pipe; + int return_value_pipe; +}; \ No newline at end of file diff --git a/lib/src/pipe.cpp b/lib/src/pipe.cpp new file mode 100644 index 0000000..34d280c --- /dev/null +++ b/lib/src/pipe.cpp @@ -0,0 +1,71 @@ +#include +#include +#include +#include +#include + +#include "lib.h" + +// Load Pipes +static std::variant get_pipe(const char *env) { + const char *value = getenv(env); + if (value == nullptr) { + return Trampoline::Error::MISSING_PIPE; + } + try { + const int val = std::stoi(value); + if (val >= 0) { + return val; + } + } catch (...) { + } + return Trampoline::Error::INVALID_PIPE; +} +#define set(var, env) \ + val = get_pipe(env); \ + if (std::holds_alternative(val)) { \ + status = std::get(val); \ + return; \ + } \ + var = std::get(val) +PipeTrampoline::PipeTrampoline() { + std::variant val; + set(arguments_pipe, _MCPI_TRAMPOLINE_ARGUMENTS_ENV); + set(return_value_pipe, _MCPI_TRAMPOLINE_RETURN_VALUE_ENV); + // Success + status = Error::NONE; +} +#undef set + +// Call +Trampoline::Error PipeTrampoline::call(const uint32_t id, uint32_t *ret_ptr, const uint32_t length, const unsigned char *args) { + // Check + if (status != Error::NONE) { + return status; + } + // Write Command + const trampoline_pipe_arguments cmd = { + .id = id, + .allow_early_return = ret_ptr == nullptr, + .length = length + }; + if (write(arguments_pipe, &cmd, sizeof(trampoline_pipe_arguments)) != sizeof(trampoline_pipe_arguments)) { + return Error::PIPE_WRITE; + } + // Write Arguments + size_t position = 0; + while (position < length) { + const ssize_t ret = write(arguments_pipe, args + position, length - position); + if (ret == -1) { + return Error::PIPE_WRITE; + } else { + position += ret; + } + } + // Return Value + if (ret_ptr != nullptr && read(return_value_pipe, ret_ptr, sizeof(uint32_t)) != sizeof(uint32_t)) { + return Error::PIPE_READ; + } + // Success + return Error::NONE; +} diff --git a/lib/src/syscall.cpp b/lib/src/syscall.cpp new file mode 100644 index 0000000..0241ce3 --- /dev/null +++ b/lib/src/syscall.cpp @@ -0,0 +1,14 @@ +#include +#include + +#include "lib.h" + +// Check +bool SyscallTrampoline::should_use() { + return getenv(MCPI_USE_PIPE_TRAMPOLINE_ENV) == nullptr; +} + +// Call +Trampoline::Error SyscallTrampoline::call(const uint32_t id, uint32_t *ret_ptr, uint32_t length, const unsigned char *args) { + return syscall(TRAMPOLINE_SYSCALL, id, ret_ptr, args) != 0 ? Error::SYSCALL : Error::NONE; +} diff --git a/qemu/patches/trampoline-syscall.patch b/qemu/patches/trampoline-syscall.patch index 40a9ccb..b4af982 100644 --- a/qemu/patches/trampoline-syscall.patch +++ b/qemu/patches/trampoline-syscall.patch @@ -13,7 +13,7 @@ int, __to_dfd, const char *, __to_pathname, unsigned int, flag) #endif -+extern int trampoline_handle_syscall(int32_t num, uint32_t arg1, uint32_t arg2); ++extern int trampoline_handle_syscall(int32_t num, uint32_t arg1, uint32_t arg2, uint32_t arg3); + /* This is an internal helper for do_syscall so that it is easier * to have a single return point, so that actions, such as logging @@ -22,7 +22,7 @@ #endif void *p; -+ if (trampoline_handle_syscall(num, arg1, arg2)) { ++ if (trampoline_handle_syscall(num, arg1, arg2, arg3)) { + return 0; + } + diff --git a/src/qemu/qemu.cpp b/src/qemu/qemu.cpp index b5aa2f7..3bf66c8 100644 --- a/src/qemu/qemu.cpp +++ b/src/qemu/qemu.cpp @@ -7,6 +7,9 @@ // Access QEMU's Memory void *QEMU::guest_to_host(const uint32_t guest_addr) { + if (guest_addr == 0) { + return nullptr; + } return (void *) (uintptr_t) (guest_addr + QEMU_GUEST_BASE);; } diff --git a/src/qemu/qemu.h b/src/qemu/qemu.h index 0ef342c..cd3f6c0 100644 --- a/src/qemu/qemu.h +++ b/src/qemu/qemu.h @@ -9,7 +9,7 @@ // QEMU API extern "C" { // Called By Patched QEMU - int trampoline_handle_syscall(int32_t num, uint32_t arg1, uint32_t arg2); + int trampoline_handle_syscall(int32_t num, uint32_t arg1, uint32_t arg2, uint32_t arg3); // Main int qemu_main(int argc, char **argv, char **envp); } diff --git a/src/syscall/handler.cpp b/src/syscall/handler.cpp index a1e6bd0..d07f828 100644 --- a/src/syscall/handler.cpp +++ b/src/syscall/handler.cpp @@ -4,18 +4,23 @@ #include "main.h" // Handle Syscall -int trampoline_handle_syscall(const int32_t num, const uint32_t arg1, uint32_t arg2) { +int trampoline_handle_syscall(const int32_t num, const uint32_t arg1, const uint32_t arg2, const uint32_t arg3) { // Check Syscall if (num != TRAMPOLINE_SYSCALL) { return 0; } // Run - SyscallImplementation::instance->handle_syscall(arg1, arg2); + SyscallImplementation::instance->handle_syscall(arg1, arg2, arg3); return 1; } -void SyscallImplementation::handle_syscall(const uint32_t arg1, const uint32_t arg2) const { +void SyscallImplementation::handle_syscall(const uint32_t arg1, const uint32_t arg2, const uint32_t arg3) const { + // Get Arguments const uint32_t id = arg1; - const unsigned char *args = (const unsigned char *) QEMU::guest_to_host(arg2); + uint32_t *ret_ptr = (uint32_t *) QEMU::guest_to_host(arg2); + const unsigned char *args = (const unsigned char *) QEMU::guest_to_host(arg3); + // Call const uint32_t ret = trampoline(id, args); - *(uint32_t *) args = ret; + if (ret_ptr != nullptr) { + *ret_ptr = ret; + } } diff --git a/src/syscall/main.h b/src/syscall/main.h index 77cb861..d836640 100644 --- a/src/syscall/main.h +++ b/src/syscall/main.h @@ -5,6 +5,6 @@ struct SyscallImplementation final : Implementation { int main(int argc, char *argv[]) override; void memory_writer(uint32_t guest_addr, const void *data, uint32_t size) const override; - void handle_syscall(uint32_t arg1, uint32_t arg2) const; + void handle_syscall(uint32_t arg1, uint32_t arg2, uint32_t arg3) const; static SyscallImplementation *instance; }; \ No newline at end of file diff --git a/src/trampoline.cpp b/src/trampoline.cpp index af52795..71ace60 100644 --- a/src/trampoline.cpp +++ b/src/trampoline.cpp @@ -20,11 +20,11 @@ uint32_t Trampoline::operator()(const uint32_t id, const unsigned char *args) co void Trampoline::init(const Implementation *impl) { stored_impl = impl; // Open Library - void *handle = dlopen("libmedia-layer-trampoline.so", RTLD_NOW); + void *handle = dlopen(TRAMPOLINE_LIBRARY, RTLD_NOW); if (handle != nullptr) { func = (trampoline_t) dlsym(handle, "trampoline"); } if (func == nullptr) { - ERR("Unable To Load Media Layer Trampoline: %s", dlerror()); + ERR("Unable To Trampoline: %s", dlerror()); } } \ No newline at end of file