From 30be283e15e146baabcce9ba022b6bb792a6f1cc Mon Sep 17 00:00:00 2001 From: York Jasper Niebuhr Date: Sat, 11 Apr 2026 11:31:50 +0200 Subject: [PATCH] Added patchcompile module handling --- patchcompile/accumulation.cpp | 133 ++++++++++++++++++++++++++++++++-- patchcompile/accumulation.h | 4 +- patchcompile/emit.cpp | 97 +++++++++++++++---------- patchcompile/emit.h | 2 +- patchcompile/patchcompile.cpp | 84 +++++++++++++++------ selfpatch/include/spslr.h | 16 ++++ subject/CMakeLists.txt | 19 ++++- subject/main.c | 40 ++++++++++ 8 files changed, 324 insertions(+), 71 deletions(-) diff --git a/patchcompile/accumulation.cpp b/patchcompile/accumulation.cpp index 6669b11..8c5569b 100644 --- a/patchcompile/accumulation.cpp +++ b/patchcompile/accumulation.cpp @@ -48,12 +48,15 @@ static bool global_target_cmp(const TARGET& a, const TARGET& b) { return true; } -static std::size_t accumulate_global_target(TARGET&& target) { +static std::size_t accumulate_global_target(TARGET&& target, bool& was_new) { + was_new = false; for (const auto& [guid, gtarget] : targets) { if (global_target_cmp(gtarget, target)) return guid; } + was_new = true; + std::size_t guid = next_global_target_uid++; targets.emplace(guid, std::move(target)); return guid; @@ -75,7 +78,7 @@ dpin dpin ... */ -static bool accumulate_file(const fs::path& path) { +static bool accumulate_file(const fs::path& path, bool no_new_targets) { std::ifstream infile(path); if (!infile) return false; @@ -169,7 +172,14 @@ static bool accumulate_file(const fs::path& path) { fit++; } - std::size_t global_target_uid = accumulate_global_target(std::move(target)); + bool was_new = false; + std::size_t global_target_uid = accumulate_global_target(std::move(target), was_new); + + if (no_new_targets && was_new) { + std::cerr << "Encountered new target but --no-new-targets is set!" << std::endl; + return false; + } + cu.local_targets.emplace(local_uid, global_target_uid); continue; } else if (type == "ipin") { @@ -223,7 +233,7 @@ static bool accumulate_file(const fs::path& path) { return true; } -bool accumulate(const std::vector& spslr_files) { +bool accumulate(const std::vector& spslr_files, bool no_new_targets) { for (const std::string& spslr_file : spslr_files) { fs::path p { spslr_file }; @@ -237,7 +247,7 @@ bool accumulate(const std::vector& spslr_files) { return false; } - if (!accumulate_file(p)) { + if (!accumulate_file(p, no_new_targets)) { std::cerr << "Failed to parse metadata file " << p << "!" << std::endl; return false; } @@ -245,3 +255,116 @@ bool accumulate(const std::vector& spslr_files) { return true; } + +bool dump_target_map(const std::string& path) { + std::filesystem::path p{path}; + if (p.has_parent_path()) { + std::error_code ec; + std::filesystem::create_directories(p.parent_path(), ec); + if (ec) { + std::cerr << "Failed to create target-map directory: " << ec.message() << "\n"; + return false; + } + } + + std::ofstream out(p); + if (!out) + return false; + + out << "SPSLR_TARGETS 1\n"; + + for (const auto& [uid, t] : targets) { + out << "target " << t.name << " " << uid << " " << t.size << " " << t.fields.size() << "\n"; + + for (const auto& [off, f] : t.fields) { + (void)off; + out << "f " << f.offset << " " << f.size << " " << f.alignment << " " << f.flags << "\n"; + } + } + + return !!out; +} + +bool load_target_map(const std::string& path) { + std::ifstream in(path); + if (!in) + return false; + + std::string magic; + std::size_t version = 0; + if (!(in >> magic >> version) || magic != "SPSLR_TARGETS" || version != 1) { + std::cerr << "Invalid target map header\n"; + return false; + } + + std::string line; + std::getline(in, line); // consume rest of header line + + std::size_t max_uid = 0; + bool have_any = false; + + while (std::getline(in, line)) { + if (line.empty()) + continue; + + std::istringstream iss(line); + std::string tag; + iss >> tag; + + if (tag != "target") { + std::cerr << "Expected target entry in target map\n"; + return false; + } + + TARGET t{}; + std::size_t uid = 0; + std::size_t field_count = 0; + + if (!(iss >> t.name >> uid >> t.size >> field_count)) { + std::cerr << "Malformed target entry in target map\n"; + return false; + } + + for (std::size_t i = 0; i < field_count; ++i) { + std::string fline; + if (!std::getline(in, fline)) { + std::cerr << "Missing field entry in target map\n"; + return false; + } + + std::istringstream fiss(fline); + std::string ftag; + FIELD f{}; + + if (!(fiss >> ftag) || ftag != "f" || !(fiss >> f.offset >> f.size >> f.alignment >> f.flags)) { + std::cerr << "Malformed field entry in target map\n"; + return false; + } + + f.idx = i; + if (t.fields.contains(f.offset)) { + std::cerr << "Duplicate field offset in target map\n"; + return false; + } + t.fields.emplace(f.offset, f); + } + + if (targets.contains(uid)) { + std::cerr << "Duplicate target uid in target map\n"; + return false; + } + + targets.emplace(uid, std::move(t)); + + if (!have_any || uid > max_uid) { + max_uid = uid; + have_any = true; + } + } + + if (have_any) + next_global_target_uid = (max_uid + 1); + + return true; +} + diff --git a/patchcompile/accumulation.h b/patchcompile/accumulation.h index b3cef64..610baf8 100644 --- a/patchcompile/accumulation.h +++ b/patchcompile/accumulation.h @@ -49,4 +49,6 @@ struct CU { extern std::unordered_map targets; extern std::unordered_map units; -bool accumulate(const std::vector& spslr_files); +bool accumulate(const std::vector& spslr_files, bool no_new_targets); +bool dump_target_map(const std::string& path); +bool load_target_map(const std::string& path); diff --git a/patchcompile/emit.cpp b/patchcompile/emit.cpp index eccd231..0bfdb5d 100644 --- a/patchcompile/emit.cpp +++ b/patchcompile/emit.cpp @@ -101,6 +101,13 @@ static bool emit_header(std::ostream& out) { return !!out; } +static bool emit_module_header(std::ostream& out) { + // Shared objects with read-only relocations cause warnings + out << ".section .data.rel.ro.spslr,\"aw\",@progbits\n"; + out << ".balign 8\n"; + return !!out; +} + static bool emit_u32_object(std::ostream& out, const char* name, uint32_t value) { out << ".globl " << name << "\n"; out << ".type " << name << ", @object\n"; @@ -112,6 +119,9 @@ static bool emit_u32_object(std::ostream& out, const char* name, uint32_t value) } static bool emit_targets(std::ostream& out, const std::vector& targets) { + if (!emit_u32_object(out, "spslr_target_cnt", static_cast(targets.size()))) + return false; + out << ".globl spslr_targets\n"; out << ".type spslr_targets, @object\n"; out << ".balign 4\n"; @@ -128,6 +138,9 @@ static bool emit_targets(std::ostream& out, const std::vector& targe } static bool emit_target_fields(std::ostream& out, const std::vector& fields) { + if (!emit_u32_object(out, "spslr_target_field_cnt", static_cast(fields.size()))) + return false; + out << ".globl spslr_target_fields\n"; out << ".type spslr_target_fields, @object\n"; out << ".balign 4\n"; @@ -145,6 +158,9 @@ static bool emit_target_fields(std::ostream& out, const std::vector& ipins) { + if (!emit_u32_object(out, "spslr_ipin_cnt", static_cast(ipins.size()))) + return false; + out << ".globl spslr_ipins\n"; out << ".type spslr_ipins, @object\n"; out << ".balign 8\n"; @@ -161,6 +177,9 @@ static bool emit_ipins(std::ostream& out, const std::vector& ipins) { } static bool emit_ipin_ops(std::ostream& out, const std::vector& ops) { + if (!emit_u32_object(out, "spslr_ipin_op_cnt", static_cast(ops.size()))) + return false; + out << ".globl spslr_ipin_ops\n"; out << ".type spslr_ipin_ops, @object\n"; out << ".balign 4\n"; @@ -177,6 +196,9 @@ static bool emit_ipin_ops(std::ostream& out, const std::vector& ops } static bool emit_dpins(std::ostream& out, const std::vector& dpins) { + if (!emit_u32_object(out, "spslr_dpin_cnt", static_cast(dpins.size()))) + return false; + out << ".globl spslr_dpins\n"; out << ".type spslr_dpins, @object\n"; out << ".balign 8\n"; @@ -193,37 +215,45 @@ static bool emit_dpins(std::ostream& out, const std::vector& dpins) { } -bool emit_patcher_program_asm(std::ostream& out) { - if (!emit_header(out)) - return false; +bool emit_patcher_program_asm(std::ostream& out, bool is_module) { + if (!is_module) { + if (!emit_header(out)) + return false; + } else { + if (!emit_module_header(out)) + return false; + } // Important: target UID == index in spslr_targets[] for the new interface. std::vector target_recs(targets.size()); std::vector field_recs; - field_recs.reserve(64); - for (uint32_t uid = 0; uid < static_cast(targets.size()); ++uid) { - if (!targets.contains(uid)) - return false; + if (!is_module) { + field_recs.reserve(64); - const TARGET& target = targets.at(uid); + for (uint32_t uid = 0; uid < static_cast(targets.size()); ++uid) { + if (!targets.contains(uid)) + return false; - TARGET_REC trec{}; - trec.size = static_cast(target.size); - trec.fieldoff = static_cast(field_recs.size()); - trec.fieldcnt = static_cast(target.fields.size()); + const TARGET& target = targets.at(uid); - for (const auto& [off, field] : target.fields) { - (void)off; - field_recs.push_back(TARGET_FIELD_REC{ - .offset = static_cast(field.offset), - .size = static_cast(field.size), - .alignment = static_cast(field.alignment), - .flags = static_cast(field.flags), - }); + TARGET_REC trec{}; + trec.size = static_cast(target.size); + trec.fieldoff = static_cast(field_recs.size()); + trec.fieldcnt = static_cast(target.fields.size()); + + for (const auto& [off, field] : target.fields) { + (void)off; + field_recs.push_back(TARGET_FIELD_REC{ + .offset = static_cast(field.offset), + .size = static_cast(field.size), + .alignment = static_cast(field.alignment), + .flags = static_cast(field.flags), + }); + } + + target_recs[uid] = trec; } - - target_recs[uid] = trec; } std::vector ipin_recs; @@ -289,28 +319,17 @@ bool emit_patcher_program_asm(std::ostream& out) { } } - if (!emit_u32_object(out, "spslr_target_cnt", static_cast(target_recs.size()))) - return false; - if (!emit_targets(out, target_recs)) - return false; + if (!is_module) { + if (!emit_targets(out, target_recs)) + return false; + if (!emit_target_fields(out, field_recs)) + return false; + } - if (!emit_u32_object(out, "spslr_target_field_cnt", static_cast(field_recs.size()))) - return false; - if (!emit_target_fields(out, field_recs)) - return false; - - if (!emit_u32_object(out, "spslr_ipin_cnt", static_cast(ipin_recs.size()))) - return false; if (!emit_ipins(out, ipin_recs)) return false; - - if (!emit_u32_object(out, "spslr_ipin_op_cnt", static_cast(ipin_ops.size()))) - return false; if (!emit_ipin_ops(out, ipin_ops)) return false; - - if (!emit_u32_object(out, "spslr_dpin_cnt", static_cast(dpin_recs.size()))) - return false; if (!emit_dpins(out, dpin_recs)) return false; diff --git a/patchcompile/emit.h b/patchcompile/emit.h index a6f1612..1cc8c7d 100644 --- a/patchcompile/emit.h +++ b/patchcompile/emit.h @@ -1,4 +1,4 @@ #pragma once #include -bool emit_patcher_program_asm(std::ostream& out); +bool emit_patcher_program_asm(std::ostream& out, bool is_module); diff --git a/patchcompile/patchcompile.cpp b/patchcompile/patchcompile.cpp index 5b497fb..95b81ec 100644 --- a/patchcompile/patchcompile.cpp +++ b/patchcompile/patchcompile.cpp @@ -14,58 +14,91 @@ Notes: Between CUs, types with the same name HAVE TO HAVE the same layout -> randomized together */ +struct OPTIONS { + std::string out_file; + std::string load_targets_file; + std::string dump_targets_file; + std::vector spslr_files; + bool no_new_targets = false; + bool is_module = false; +}; + int main(int argc, char** argv) { static option long_options[] = { { "help", no_argument, 0, 0 }, { "out", required_argument, 0, 0 }, + { "load-targets", required_argument, 0, 0 }, + { "dump-targets", required_argument, 0, 0 }, + { "no-new-targets", no_argument, 0, 0 }, + { "module", no_argument, 0, 0 }, { 0, 0, 0, 0 } }; + OPTIONS opts{}; int option_index = 0; int c; - std::vector spslr_files; - std::string out_file; - while ((c = getopt_long(argc, argv, "h:o:", long_options, &option_index)) == 0) { const option& opt = long_options[option_index]; std::string optname { opt.name }; if (optname == "help") { - std::cout << "To use spslr_patchcompile, supply these arguments:" << std::endl; - std::cout << " --out= (the compiled asm file to be written)" << std::endl; - std::cout << " ... (one or more .spslr metadata files)" << std::endl; + std::cout + << "Usage:\n" + << " spslr_patchcompile --out= [options] ...\n\n" + << "Options:\n" + << " --read-targets=\n" + << " --emit-targets=\n" + << " --no-new-targets\n" + << " --module\n"; return 0; } else if (optname == "out") { - out_file = std::string{ optarg }; + opts.out_file = optarg; + } else if (optname == "load-targets") { + opts.load_targets_file = optarg; + } else if (optname == "dump-targets") { + opts.dump_targets_file = optarg; + } else if (optname == "no-new-targets") { + opts.no_new_targets = true; + } else if (optname == "module") { + opts.is_module = true; } else { - std::cerr << "Invalid option, try \"--help\"!" << std::endl; + std::cerr << "Invalid option, try \"--help\"!\n"; return 1; } } - if (out_file.empty()) { - std::cerr << "Missing output file path, supply it via --out=!" << std::endl; - return 1; - } - for (int i = optind; i < argc; ++i) - spslr_files.emplace_back(argv[i]); + opts.spslr_files.emplace_back(argv[i]); - if (spslr_files.empty()) { - std::cerr << "Missing spslr files! Pass one or more .spslr files as positional arguments." << std::endl; + if (opts.out_file.empty()) { + std::cerr << "Missing output file path, supply it via --out=!\n"; return 1; } - if (!accumulate(spslr_files)) { + if (opts.spslr_files.empty()) { + std::cerr << "Missing spslr files! Pass one or more .spslr metadata files as positional arguments.\n"; + return 1; + } + + if (opts.no_new_targets && opts.load_targets_file.empty()) { + std::cerr << "--no-new-targets requires --load-targets\n"; + return 1; + } + + if (!opts.load_targets_file.empty()) { + if (!load_target_map(opts.load_targets_file)) { + std::cerr << "Failed to load target map: " << opts.load_targets_file << "\n"; + return 1; + } + } + + if (!accumulate(opts.spslr_files, opts.no_new_targets)) { std::cerr << "Failed to accumulate data from spslr directory!" << std::endl; return 1; } - std::cout << "Gathered a total of " << targets.size() << " distinct targets from " - << units.size() << " compilation units!" << std::endl; - - std::filesystem::path out_path { out_file }; + std::filesystem::path out_path { opts.out_file }; if (out_path.has_parent_path()) { std::error_code ec; std::filesystem::create_directories(out_path.parent_path(), ec); @@ -83,11 +116,18 @@ int main(int argc, char** argv) { return 1; } - if (!emit_patcher_program_asm(out)) { + if (!emit_patcher_program_asm(out, opts.is_module)) { std::cerr << "Failed to write emit patcher program!" << std::endl; return 1; } + if (!opts.dump_targets_file.empty()) { + if (!dump_target_map(opts.dump_targets_file)) { + std::cerr << "Failed to write target map: " << opts.dump_targets_file << "\n"; + return 1; + } + } + return 0; } diff --git a/selfpatch/include/spslr.h b/selfpatch/include/spslr.h index b472d55..27e68b3 100644 --- a/selfpatch/include/spslr.h +++ b/selfpatch/include/spslr.h @@ -1,6 +1,22 @@ #ifndef SPSLR_SELFPATCH_H #define SPSLR_SELFPATCH_H +#define SPSLR_MODULE_SYM_IPIN_CNT "spslr_ipin_cnt" +#define SPSLR_MODULE_SYM_IPINS "spslr_ipins" +#define SPSLR_MODULE_SYM_IPIN_OP_CNT "spslr_ipin_op_cnt" +#define SPSLR_MODULE_SYM_IPIN_OPS "spslr_ipin_ops" +#define SPSLR_MODULE_SYM_DPIN_CNT "spslr_dpin_cnt" +#define SPSLR_MODULE_SYM_DPINS "spslr_dpins" + +struct spslr_module { + const void* ipin_cnt; + const void* ipins; + const void* ipin_op_cnt; + const void* ipin_ops; + const void* dpin_cnt; + const void* dpins; +}; + void spslr_selfpatch(void); #endif diff --git a/subject/CMakeLists.txt b/subject/CMakeLists.txt index 97c0a9d..86b769c 100644 --- a/subject/CMakeLists.txt +++ b/subject/CMakeLists.txt @@ -32,17 +32,25 @@ endforeach() set(SUBJECT_SPSLR_ASM "${CMAKE_CURRENT_BINARY_DIR}/subject_spslr_program.S") set(SUBJECT_SPSLR_OBJ "${CMAKE_CURRENT_BINARY_DIR}/subject_spslr_program.o") +set(SUBJECT_TARGET_MAP "${CMAKE_CURRENT_BINARY_DIR}/subject.spslr_targets") + add_custom_command( - OUTPUT "${SUBJECT_SPSLR_ASM}" + OUTPUT "${SUBJECT_SPSLR_ASM}" "${SUBJECT_TARGET_MAP}" COMMAND $ --out=${SUBJECT_SPSLR_ASM} + --dump-targets=${SUBJECT_TARGET_MAP} ${SUBJECT_SPSLR_FILES} DEPENDS spslr_patchcompile - $ + subject_objs + ${SUBJECT_SPSLR_FILES} VERBATIM ) +add_custom_target(subject_spslr_metadata + DEPENDS "${SUBJECT_SPSLR_ASM}" "${SUBJECT_TARGET_MAP}" +) + add_custom_command( OUTPUT "${SUBJECT_SPSLR_OBJ}" COMMAND ${CMAKE_C_COMPILER} @@ -104,10 +112,15 @@ add_custom_command( OUTPUT "${MODULE_SPSLR_ASM}" COMMAND $ --out=${MODULE_SPSLR_ASM} + --load-targets=${SUBJECT_TARGET_MAP} + --no-new-targets + --module ${MODULE_SPSLR_FILES} DEPENDS spslr_patchcompile - $ + subject_spslr_metadata + spslr_module_objs + ${MODULE_SPSLR_FILES} VERBATIM ) diff --git a/subject/main.c b/subject/main.c index 81cadcc..9ada419 100644 --- a/subject/main.c +++ b/subject/main.c @@ -22,6 +22,37 @@ struct task_struct global = { .pid = 42, .comm = "main_global", .arrfun = { EXPORT_SYMBOL(global); +static int fetch_module_spslr_symbols(void* handle, struct spslr_module* mod) { + if (!handle || !mod) + return -1; + + mod->ipin_cnt = dlsym(handle, SPSLR_MODULE_SYM_IPIN_CNT); + if (!mod->ipin_cnt) + return -1; + + mod->ipins = dlsym(handle, SPSLR_MODULE_SYM_IPINS); + if (!mod->ipins) + return -1; + + mod->ipin_op_cnt = dlsym(handle, SPSLR_MODULE_SYM_IPIN_OP_CNT); + if (!mod->ipin_op_cnt) + return -1; + + mod->ipin_ops = dlsym(handle, SPSLR_MODULE_SYM_IPIN_OPS); + if (!mod->ipin_ops) + return -1; + + mod->dpin_cnt = dlsym(handle, SPSLR_MODULE_SYM_DPIN_CNT); + if (!mod->dpin_cnt) + return -1; + + mod->dpins = dlsym(handle, SPSLR_MODULE_SYM_DPINS); + if (!mod->dpins) + return -1; + + return 0; +} + static int do_module_test_access_pid(const char *path, const struct task_struct *t) { typedef int (*module_test_access_fn)(const struct task_struct *t); @@ -33,6 +64,15 @@ static int do_module_test_access_pid(const char *path, const struct task_struct dlerror(); + struct spslr_module mod; + if (fetch_module_spslr_symbols(handle, &mod) < 0) { + fprintf(stderr, "failed to fetch spslr symbols in test module\n"); + dlclose(handle); + return -1; + } + + // TODO -> Patch module + module_test_access_fn fn = (module_test_access_fn)dlsym(handle, "module_test_access_pid"); const char *err = dlerror();