From bfb9cabeb8c4d9b56e31366a57bf518a0a509e61 Mon Sep 17 00:00:00 2001 From: Tony Wasserka Date: Fri, 10 Dec 2021 11:24:57 +0100 Subject: [PATCH] Thunks/gen: Add support for generation of host library functions --- ThunkLibs/Generator/gen.cpp | 94 ++++++++++++++++++++++++ ThunkLibs/Generator/interface.h | 6 ++ ThunkLibs/Generator/main.cpp | 10 ++- ThunkLibs/include/common/Host.h | 15 ++++ unittests/ThunkLibs/generator.cpp | 115 ++++++++++++++++++++++++++++-- 5 files changed, 232 insertions(+), 8 deletions(-) diff --git a/ThunkLibs/Generator/gen.cpp b/ThunkLibs/Generator/gen.cpp index 0485506b6..1b876f576 100644 --- a/ThunkLibs/Generator/gen.cpp +++ b/ThunkLibs/Generator/gen.cpp @@ -141,6 +141,35 @@ void FrontendAction::EndSourceFileAction() { } }; + auto format_struct_members = [](const FunctionParams& params, const char* indent) { + std::string ret; + for (std::size_t idx = 0; idx < params.param_types.size(); ++idx) { + ret += indent + format_decl(params.param_types[idx].getUnqualifiedType(), "a_" + std::to_string(idx)) + ";\n"; + } + return ret; + }; + + auto format_function_args = [](const FunctionParams& params) { + std::string ret; + for (std::size_t idx = 0; idx < params.param_types.size(); ++idx) { + ret += "args->a_" + std::to_string(idx) + ", "; + } + // drop trailing ", " + ret.resize(ret.size() > 2 ? ret.size() - 2 : 0); + return ret; + }; + + auto format_function_params = [](const FunctionParams& params) { + std::string ret; + for (std::size_t idx = 0; idx < params.param_types.size(); ++idx) { + auto& type = params.param_types[idx]; + ret += format_decl(type, "a_" + std::to_string(idx)) + ", "; + } + // drop trailing ", " + ret.resize(ret.size() > 2 ? ret.size() - 2 : 0); + return ret; + }; + auto get_sha256 = [this](const std::string& function_name) { std::string sha256_message = libname + ":" + function_name; std::vector sha256(SHA256_DIGEST_LENGTH); @@ -223,6 +252,71 @@ void FrontendAction::EndSourceFileAction() { } file << "}\n"; } + + if (!output_filenames.function_unpacks.empty()) { + std::ofstream file(output_filenames.function_unpacks); + + file << "extern \"C\" {\n"; + for (auto& thunk : thunks) { + const auto& function_name = thunk.function_name; + bool is_void = thunk.return_type->isVoidType(); + + file << "struct fexfn_packed_args_" << libname << "_" << function_name << " {\n"; + file << format_struct_members(thunk, " "); + if (!is_void) { + file << " " << format_decl(thunk.return_type, "rv") << ";\n"; + } else if (thunk.param_types.size() == 0) { + // Avoid "empty struct has size 0 in C, size 1 in C++" warning + file << " char force_nonempty;\n"; + } + file << "};\n"; + + file << "static void fexfn_unpack_" << libname << "_" << function_name << "(fexfn_packed_args_" << libname << "_" << function_name << "* args) {\n"; + file << (is_void ? " " : " args->rv = ") << "fexldr_ptr_" << libname << "_" << function_name << "(" << format_function_args(thunk) << ");\n"; + file << "}\n"; + } + + file << "}\n"; + } + + if (!output_filenames.tab_function_unpacks.empty()) { + std::ofstream file(output_filenames.tab_function_unpacks); + + for (auto& thunk : thunks) { + const auto& function_name = thunk.function_name; + auto sha256 = get_sha256(function_name); + + file << "{(uint8_t*)\""; + for (auto c : sha256) { + file << "\\x" << std::hex << std::setw(2) << std::setfill('0') << +c; + } + file << "\", &fexfn_type_erased_unpack}, // " << libname << ":" << function_name << "\n"; + } + } + + if (!output_filenames.ldr.empty()) { + std::ofstream file(output_filenames.ldr); + + file << "static void* fexldr_ptr_" << libname << "_so;\n"; + file << "extern \"C\" bool fexldr_init_" << libname << "() {\n"; + file << " fexldr_ptr_" << libname << "_so = dlopen(\"" << libname << ".so\", RTLD_LOCAL | RTLD_LAZY);\n"; + file << " if (!fexldr_ptr_" << libname << "_so) { return false; }\n\n"; + for (auto& import : thunked_api) { + file << " (void*&)fexldr_ptr_" << libname << "_" << import.function_name << " = dlsym(fexldr_ptr_" << libname << "_so, \"" << import.function_name << "\");\n"; + } + file << " return true;\n"; + file << "}\n"; + } + + if (!output_filenames.ldr_ptrs.empty()) { + std::ofstream file(output_filenames.ldr_ptrs); + + for (auto& import : thunked_api) { + const auto& function_name = import.function_name; + file << "using fexldr_type_" << libname << "_" << function_name << " = auto " << "(" << format_function_params(import) << ") -> " << import.return_type.getAsString() << ";\n"; + file << "static fexldr_type_" << libname << "_" << function_name << " *fexldr_ptr_" << libname << "_" << function_name << ";\n"; + } + } } std::unique_ptr FrontendAction::CreateASTConsumer(clang::CompilerInstance&, clang::StringRef) { diff --git a/ThunkLibs/Generator/interface.h b/ThunkLibs/Generator/interface.h index 7e6f43ce0..c67e9a9da 100644 --- a/ThunkLibs/Generator/interface.h +++ b/ThunkLibs/Generator/interface.h @@ -4,6 +4,12 @@ #include struct OutputFilenames { + // Host + std::string function_unpacks; + std::string tab_function_unpacks; + std::string ldr; + std::string ldr_ptrs; + // Guest std::string thunks; std::string function_packs; diff --git a/ThunkLibs/Generator/main.cpp b/ThunkLibs/Generator/main.cpp index 7e24bc4cd..d7fde39b5 100644 --- a/ThunkLibs/Generator/main.cpp +++ b/ThunkLibs/Generator/main.cpp @@ -52,7 +52,15 @@ int main(int argc, char* argv[]) { while (arg < last_internal_arg) { auto target = std::string { *arg++ }; auto out_filename = *arg++; - if (target == "-thunks") { + if (target == "-function_unpacks") { + output_filenames.function_unpacks = out_filename; + } else if (target == "-tab_function_unpacks") { + output_filenames.tab_function_unpacks = out_filename; + } else if (target == "-ldr") { + output_filenames.ldr = out_filename; + } else if (target == "-ldr_ptrs") { + output_filenames.ldr_ptrs = out_filename; + } else if (target == "-thunks") { output_filenames.thunks = out_filename; } else if (target == "-function_packs") { output_filenames.function_packs = out_filename; diff --git a/ThunkLibs/include/common/Host.h b/ThunkLibs/include/common/Host.h index 00b18a96e..05c9d84d7 100644 --- a/ThunkLibs/include/common/Host.h +++ b/ThunkLibs/include/common/Host.h @@ -7,6 +7,21 @@ $end_info$ #pragma once #include +template +struct function_traits; +template +struct function_traits { + using result_t = Result; + using arg_t = Arg; +}; + +template +static typename function_traits::result_t +fexfn_type_erased_unpack(void* argsv) { + using args_t = typename function_traits::arg_t; + return Fn(reinterpret_cast(argsv)); +} + struct ExportEntry { uint8_t* sha256; void(*fn)(void *); }; typedef void fex_call_callback_t(uintptr_t callback, void *arg0, void* arg1); diff --git a/unittests/ThunkLibs/generator.cpp b/unittests/ThunkLibs/generator.cpp index 21ed2b903..fb222779c 100644 --- a/unittests/ThunkLibs/generator.cpp +++ b/unittests/ThunkLibs/generator.cpp @@ -16,6 +16,10 @@ struct Fixture { tmpdir = std::tmpnam(nullptr); std::filesystem::create_directory(tmpdir); output_filenames = { + tmpdir + "/function_unpacks", + tmpdir + "/tab_function_unpacks", + tmpdir + "/ldr", + tmpdir + "/ldr_ptrs", tmpdir + "/thunks", tmpdir + "/function_packs", tmpdir + "/function_packs_public" @@ -26,7 +30,14 @@ struct Fixture { std::filesystem::remove_all(tmpdir); } + struct GenOutput { + std::string guest; + std::string host; + }; + std::string run_thunkgen_guest(std::string_view code, bool silent = false); + std::string run_thunkgen_host(std::string_view code, bool silent = false); + GenOutput run_thunkgen(std::string_view code, bool silent = false); const std::string libname = "libtest"; std::string tmpdir; @@ -210,21 +221,85 @@ std::string Fixture::run_thunkgen_guest(std::string_view code, bool silent) { return result; } +/** + * Generates host thunk library code from the given input + */ +std::string Fixture::run_thunkgen_host(std::string_view code, bool silent) { + run_tool(std::make_unique(libname, output_filenames), code, silent); + + std::string result = + "#include \n" + "#include \n" + "template\n" + "struct function_traits;\n" + "template\n" + "struct function_traits {\n" + " using result_t = Result;\n" + " using arg_t = Arg;\n" + "};\n" + "template\n" + "static typename function_traits::result_t\n" + "fexfn_type_erased_unpack(void* argsv) {\n" + " using args_t = typename function_traits::arg_t;\n" + " return Fn(reinterpret_cast(argsv));\n" + "}\n"; + for (auto& filename : { + output_filenames.ldr_ptrs, + output_filenames.function_unpacks, + output_filenames.tab_function_unpacks, + output_filenames.ldr, + }) { + bool tab_function_unpacks = (filename == output_filenames.tab_function_unpacks); + if (tab_function_unpacks) { + result += "struct ExportEntry { uint8_t* sha256; void(*fn)(void *); };\n"; + result += "static ExportEntry exports[] = {\n"; + } + + std::ifstream file(filename); + const auto current_size = result.size(); + const auto new_data_size = std::filesystem::file_size(filename); + result.resize(result.size() + new_data_size); + file.read(result.data() + current_size, result.size()); + + if (tab_function_unpacks) { + result += " { nullptr, nullptr }\n"; + result += "};\n"; + } + } + return result; +} + +Fixture::GenOutput Fixture::run_thunkgen(std::string_view code, bool silent) { + return { run_thunkgen_guest(code, silent), + run_thunkgen_host(code, silent) }; +} + TEST_CASE_METHOD(Fixture, "Trivial") { - const auto output = run_thunkgen_guest( + const auto output = run_thunkgen( "#include \n" "void func();\n" "template struct fex_gen_config {};\n" "template<> struct fex_gen_config {};\n"); - CHECK_THAT(output, DefinesPublicFunction("func")); + // Guest code + CHECK_THAT(output.guest, DefinesPublicFunction("func")); - CHECK_THAT(output, + CHECK_THAT(output.guest, matches(functionDecl( hasName("fexfn_pack_func"), returns(asString("void")), parameterCountIs(0) ))); + + // Host code + CHECK_THAT(output.host, + matches(varDecl( + hasName("exports"), + hasType(constantArrayType(hasElementType(asString("struct ExportEntry")), hasSize(2))), + hasInitializer(initListExpr(hasInit(0, expr()), + hasInit(1, initListExpr(hasInit(0, implicitCastExpr()), hasInit(1, implicitCastExpr()))))) + // TODO: check null termination + ))); } // Function pointer parameters trigger an error @@ -238,17 +313,19 @@ TEST_CASE_METHOD(Fixture, "FunctionPointerParameter") { TEST_CASE_METHOD(Fixture, "MultipleParameters") { const std::string prelude = "struct TestStruct { int member; };\n"; - auto output = run_thunkgen_guest( + auto output = run_thunkgen( prelude + "void func(int arg, char, unsigned long, TestStruct);\n" "template struct fex_gen_config {};\n" "template<> struct fex_gen_config {};\n"); - output = prelude + output; + output.guest = prelude + output.guest; + output.host = prelude + output.host; - CHECK_THAT(output, DefinesPublicFunction("func")); + // Guest code + CHECK_THAT(output.guest, DefinesPublicFunction("func")); - CHECK_THAT(output, + CHECK_THAT(output.guest, matches(functionDecl( hasName("fexfn_pack_func"), returns(asString("void")), @@ -258,6 +335,30 @@ TEST_CASE_METHOD(Fixture, "MultipleParameters") { hasParameter(2, hasType(asString("unsigned long"))), hasParameter(3, hasType(asString("struct TestStruct"))) ))); + + // Host code + CHECK_THAT(output.host, + matches(varDecl( + hasName("exports"), + hasType(constantArrayType(hasElementType(asString("struct ExportEntry")), hasSize(2))), + hasInitializer(initListExpr(hasInit(0, expr()), + hasInit(1, initListExpr(hasInit(0, implicitCastExpr()), hasInit(1, implicitCastExpr()))))) + // TODO: check null termination + ))); + + CHECK_THAT(output.host, + matches(functionDecl( + hasName("fexfn_unpack_libtest_func"), + // Packed argument struct should contain all parameters + parameterCountIs(1), + hasParameter(0, hasType(pointerType(pointee( + recordType(hasDeclaration(decl( + has(fieldDecl(hasType(asString("int")))), + has(fieldDecl(hasType(asString("char")))), + has(fieldDecl(hasType(asString("unsigned long")))), + has(fieldDecl(hasType(asString("struct TestStruct")))) + ))))))) + ))); } // Returning a function pointer should trigger an error