Refactor
This commit is contained in:
parent
84e37b572b
commit
e11c3d5a03
@ -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()
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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");
|
||||
}
|
@ -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)
|
@ -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;
|
||||
}
|
||||
}
|
@ -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)
|
@ -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++
|
||||
|
@ -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
22
lib/src/lib.cpp
Normal 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
41
lib/src/lib.h
Normal 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
71
lib/src/pipe.cpp
Normal 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
14
lib/src/syscall.cpp
Normal 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;
|
||||
}
|
@ -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;
|
||||
+ }
|
||||
+
|
||||
|
@ -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);;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
@ -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());
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user