This commit is contained in:
TheBrokenRail 2025-02-14 23:10:29 -05:00
parent 84e37b572b
commit e11c3d5a03
20 changed files with 220 additions and 134 deletions

View File

@ -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()

View File

@ -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.

View File

@ -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

View File

@ -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)

View File

@ -1,19 +1,28 @@
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <trampoline/types.h>
// 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");
}

View File

@ -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)
add_library("${TRAMPOLINE_LIBRARY_NAME}" SHARED src/trampoline.c)
target_link_libraries("${TRAMPOLINE_LIBRARY_NAME}" trampoline-headers)

View File

@ -1,5 +1,6 @@
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <trampoline/types.h>
@ -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) {
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 Recieved: %u: %s\n", id, str);
fprintf(stderr, "Host Has Received: %u: %s\n", id, str);
return strlen(str) * 2;
}
}

View File

@ -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)

View File

@ -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++

View File

@ -1,86 +0,0 @@
#include <unistd.h>
#include <string>
#include <cstring>
#include <trampoline/types.h>
// 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);
}
}

22
lib/src/lib.cpp Normal file
View File

@ -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));
}

41
lib/src/lib.h Normal file
View File

@ -0,0 +1,41 @@
#pragma once
#include <cstdint>
#include <trampoline/types.h>
// 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;
};

71
lib/src/pipe.cpp Normal file
View File

@ -0,0 +1,71 @@
#include <utility>
#include <variant>
#include <cstdlib>
#include <string>
#include <unistd.h>
#include "lib.h"
// Load Pipes
static std::variant<int, Trampoline::Error> 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<Error>(val)) { \
status = std::get<Error>(val); \
return; \
} \
var = std::get<int>(val)
PipeTrampoline::PipeTrampoline() {
std::variant<int, Error> 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;
}

14
lib/src/syscall.cpp Normal file
View File

@ -0,0 +1,14 @@
#include <unistd.h>
#include <cstdlib>
#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;
}

View File

@ -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;
+ }
+

View File

@ -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);;
}

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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;
};

View File

@ -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());
}
}