From 5e8f2dfcfb7039bee38f25fe9c8437d705f472fa Mon Sep 17 00:00:00 2001 From: Hanlu Li Date: Tue, 8 Sep 2026 09:33:01 +0800 Subject: [PATCH 1/2] LATX-KZT, fix: Reuse duplicate library search paths Repeated prepends of the same normalized library-search path allocate new entries indefinitely. Simply ignoring duplicates would change lookup precedence when an existing path must become the first search location. Reuse the normalized entry and move it to the front when prepending it again. Preserve the relative order of the remaining paths. Add path-collection regression coverage for normalized duplicates and prepend order. This test passed in the loader-lifetime branch's 26-test lat-pr-fast suite. Signed-off-by: Hanlu Li --- target/i386/latx/context/pathcoll.c | 11 +++ tests/unit/kzt/test_kzt_path_collection.c | 106 ++++++++++++++++++++++ tests/unit/meson.build | 11 +++ 3 files changed, 128 insertions(+) create mode 100644 tests/unit/kzt/test_kzt_path_collection.c diff --git a/target/i386/latx/context/pathcoll.c b/target/i386/latx/context/pathcoll.c index 27fd7e8dbc1..0927610a4bd 100755 --- a/target/i386/latx/context/pathcoll.c +++ b/target/i386/latx/context/pathcoll.c @@ -112,6 +112,17 @@ void PrependPath(const char* path, path_collection_t* collection, int folder) if(l) { if(folder && tmp[l-1]!='/') strcat(tmp, "/"); + for (int i = 0; i < collection->size; i++) { + if (!strcmp(tmp, collection->paths[i])) { + char *existing = collection->paths[i]; + + /* Preserve precedence without accumulating reload duplicates. */ + memmove(collection->paths + 1, collection->paths, + i * sizeof(*collection->paths)); + collection->paths[0] = existing; + return; + } + } if(collection->size==collection->cap) { collection->cap += 4; collection->paths = (char**)box_realloc(collection->paths, collection->cap*sizeof(char*)); diff --git a/tests/unit/kzt/test_kzt_path_collection.c b/tests/unit/kzt/test_kzt_path_collection.c new file mode 100644 index 00000000000..e288a3a5a30 --- /dev/null +++ b/tests/unit/kzt/test_kzt_path_collection.c @@ -0,0 +1,106 @@ +/* + * SPDX-FileCopyrightText: 2026 LAT Project Authors + * SPDX-License-Identifier: GPL-2.0-only + */ + +#include "qemu/osdep.h" +#include "debug.h" +#include "pathcoll.h" + +char *box_strdup(const char *s) +{ + return strdup(s); +} + +static void test_repeated_runpath(void) +{ + path_collection_t paths = { 0 }; + + ParseList("/system:/fallback", &paths, 1); + for (int i = 0; i < 2000; i++) { + /* The same normalized RUNPATH is processed on each library reload. */ + PrependList(&paths, "/application:/system", 1); + } + g_test_message("paths after 2000 repeated RUNPATH registrations: %d", + paths.size); + g_assert_cmpint(paths.size, ==, 3); + g_assert_cmpstr(paths.paths[0], ==, "/application/"); + g_assert_cmpstr(paths.paths[1], ==, "/system/"); + g_assert_cmpstr(paths.paths[2], ==, "/fallback/"); + FreeCollection(&paths); + g_assert_cmpint(paths.size, ==, 0); + g_assert_cmpint(paths.cap, ==, 0); + g_assert(paths.paths == NULL); +} + +static void test_precedence_and_pointer_ownership(void) +{ + path_collection_t paths = { 0 }; + char *system; + char *fallback; + char *application; + int capacity; + + ParseList("/system:/fallback", &paths, 1); + system = paths.paths[0]; + fallback = paths.paths[1]; + PrependList(&paths, "/application:/system/", 1); + g_assert_cmpint(paths.size, ==, 3); + application = paths.paths[0]; + capacity = paths.cap; + g_assert(paths.paths[1] == system); + g_assert(paths.paths[2] == fallback); + + /* Existing paths move to the new priority rather than being ignored. */ + PrependList(&paths, "/fallback:/application", 1); + g_assert_cmpint(paths.size, ==, 3); + g_assert_cmpint(paths.cap, ==, capacity); + g_assert(paths.paths[0] == fallback); + g_assert(paths.paths[1] == application); + g_assert(paths.paths[2] == system); + + PrependPath(paths.paths[0], &paths, 1); + PrependList(&paths, "::", 1); + PrependList(&paths, NULL, 1); + g_assert_cmpint(paths.size, ==, 3); + g_assert(paths.paths[0] == fallback); + FreeCollection(&paths); +} + +static void test_literal_names_and_existing_duplicates(void) +{ + path_collection_t paths = { 0 }; + char *first; + char *second; + + /* With folder=false, a trailing slash remains part of a literal name. */ + ParseList("name:name/:other", &paths, 0); + first = paths.paths[0]; + second = paths.paths[1]; + PrependPath("name/", &paths, 0); + g_assert_cmpint(paths.size, ==, 3); + g_assert(paths.paths[0] == second); + g_assert(paths.paths[1] == first); + FreeCollection(&paths); + + /* Do not invalidate strings already owned by the initial collection. */ + ParseList("/same:/same:/other", &paths, 1); + first = paths.paths[0]; + second = paths.paths[1]; + PrependPath("/same", &paths, 1); + g_assert_cmpint(paths.size, ==, 3); + g_assert(paths.paths[0] == first); + g_assert(paths.paths[1] == second); + FreeCollection(&paths); +} + +int main(int argc, char **argv) +{ + g_test_init(&argc, &argv, NULL); + g_test_add_func("/kzt-path/repeated-runpath", test_repeated_runpath); + g_test_add_func("/kzt-path/precedence-and-ownership", + test_precedence_and_pointer_ownership); + g_test_add_func("/kzt-path/literal-names-and-existing-duplicates", + test_literal_names_and_existing_duplicates); + return g_test_run(); +} diff --git a/tests/unit/meson.build b/tests/unit/meson.build index 1843ee81dc4..829685ba4df 100644 --- a/tests/unit/meson.build +++ b/tests/unit/meson.build @@ -188,3 +188,14 @@ test( test_kzt_address_policy, suite: 'lat-pr-fast', ) + +test_kzt_path_collection = executable( + 'test-kzt-path-collection', + files( + 'kzt/test_kzt_path_collection.c', + '../../target/i386/latx/context/pathcoll.c', + ) + genh, + include_directories: include_directories('../../target/i386/latx/include'), + dependencies: [glib], +) +test('test-kzt-path-collection', test_kzt_path_collection, suite: 'lat-pr-fast') From 3789458e2d374495c89cbc7c76a4e3c75278b0a1 Mon Sep 17 00:00:00 2001 From: Hanlu Li Date: Tue, 8 Sep 2026 09:33:01 +0800 Subject: [PATCH 2/2] LATX-KZT, fix: Reclaim unloaded public-loader metadata Repeated public-loader dlopen/dlclose cycles leave stale ELF metadata in the context even after its guest mappings disappear. Freeing it immediately would invalidate metadata still borrowed by native frames or RCU readers. At a guest-execution safe point, scan under the mapping lock using a fresh loader snapshot and retire unowned metadata through RCU. Retain the main executable, attached libraries, malloc-backed borrowers, live link-map entries and ELFs with surviving load segments. Reuse vacant context slots without shifting other library indexes. Busy or stale snapshots and an exclusive-barrier timeout conservatively defer reclamation. Add lifecycle regressions for retained owners, snapshots, slot reuse and deferred destruction. The branch passed 26 lat-pr-fast tests and sanitizer checks. A 500-cycle libbz2 loader workload kept elfsize at 7; retaining a handle kept it at 8. This does not bound translation caches or establish full application memory stability; unavailable integration tests were skipped. Requires the queued-RCU fork fix in PR #474 before merging, so a child preserves inherited metadata retirements. Signed-off-by: Hanlu Li --- linux-user/i386/cpu_loop.c | 1 + target/i386/latx/context/box64context.c | 6 + target/i386/latx/context/myalign.c | 121 +++++++++++ target/i386/latx/include/elfloader_private.h | 3 + target/i386/latx/include/kzt-runtime.h | 1 + tests/unit/kzt/test-kzt-elf-lifecycle.py | 213 +++++++++++++++++++ tests/unit/meson.build | 29 ++- 7 files changed, 363 insertions(+), 11 deletions(-) create mode 100644 tests/unit/kzt/test-kzt-elf-lifecycle.py diff --git a/linux-user/i386/cpu_loop.c b/linux-user/i386/cpu_loop.c index 98c0afcdcca..cf619d1bbab 100644 --- a/linux-user/i386/cpu_loop.c +++ b/linux-user/i386/cpu_loop.c @@ -221,6 +221,7 @@ void cpu_loop(CPUX86State *env) cpu_exec_end(cs); process_queued_cpu_work(cs); #if defined(CONFIG_LATX_KZT) + kzt_reclaim_unloaded_headers(); if (latx_kzt_runtime_enabled() && trapnr == 0xCC) break; #endif diff --git a/target/i386/latx/context/box64context.c b/target/i386/latx/context/box64context.c index c052561d035..fd832a187ec 100755 --- a/target/i386/latx/context/box64context.c +++ b/target/i386/latx/context/box64context.c @@ -142,6 +142,12 @@ int AddKztDebugInfo(box64context_t* ctx, struct latx_kzt_debug* debuginfo) int AddElfHeader(box64context_t* ctx, elfheader_t* head) { int idx = ctx->elfsize; + for (int i = 0; i < ctx->elfsize; i++) { + if (!ctx->elfs[i]) { + ctx->elfs[i] = head; + return i; + } + } if(idx==ctx->elfcap) { // resize... ctx->elfcap += 16; diff --git a/target/i386/latx/context/myalign.c b/target/i386/latx/context/myalign.c index 3950b17d290..e1b8b235fef 100644 --- a/target/i386/latx/context/myalign.c +++ b/target/i386/latx/context/myalign.c @@ -2131,6 +2131,7 @@ static kzt_public_loader_observer_t kzt_public_loader_observer; static int kzt_main_relocated_before_relro; static int kzt_main_fallback_reported; static int kzt_observer_failure_reported; +static bool kzt_header_cleanup_pending; static uint32 kzt_public_r_brk_inst[2]; extern void* x86free; extern void* x86realloc; @@ -2373,6 +2374,121 @@ static int kzt_loader_snapshot_visit( return 0; } +static bool kzt_public_header_is_unmapped(const elfheader_t *head) +{ + bool has_load = false; + + for (size_t i = 0; i < head->numPHEntries; i++) { + const Elf64_Phdr *phdr = &head->PHEntries[i]; + uintptr_t start, last, address; + + if (phdr->p_type != PT_LOAD || !phdr->p_memsz) { + continue; + } + has_load = true; + if (phdr->p_vaddr > UINTPTR_MAX - head->public_load_bias) { + return false; + } + start = head->public_load_bias + phdr->p_vaddr; + if (phdr->p_memsz - 1 > UINTPTR_MAX - start) { + return false; + } + last = (start + phdr->p_memsz - 1) & TARGET_PAGE_MASK; + for (address = start & TARGET_PAGE_MASK; ; + address += TARGET_PAGE_SIZE) { + if (page_get_flags(address) & PAGE_VALID) { + return false; + } + if (address == last) { + break; + } + } + } + return has_load; +} + +static bool kzt_public_header_can_retire( + const elfheader_t *head, + const kzt_public_loader_observer_t *snapshot) +{ + if (!head || !head->public_link_map || head == elf_header || head->lib || + kzt_public_loader_observer_has_map(snapshot, head->public_link_map)) { + return false; + } + for (int i = 0; i < my_context->mallocmapsize; i++) { + if (my_context->mallocmaps[i]->h == head) { + return false; + } + } + return kzt_public_header_is_unmapped(head); +} + +typedef struct KZTRetiredElfHeader { + struct rcu_head rcu; + elfheader_t *head; +} KZTRetiredElfHeader; + +static void kzt_free_retired_header(KZTRetiredElfHeader *retired) +{ + FreeElfHeader(&retired->head); + g_free(retired); +} + +/* Called outside cpu_exec, with no mmap lock held. */ +void kzt_reclaim_unloaded_headers(void) +{ + kzt_public_loader_observer_t snapshot; + kzt_public_loader_result_t result; + + if (!qatomic_read(&kzt_header_cleanup_pending) || + !qatomic_xchg(&kzt_header_cleanup_pending, false)) { + return; + } + /* Native calls can wait indefinitely. Retry after a later loader event + * rather than blocking guest execution for optional reclamation. */ + if (!start_exclusive_timeout(10)) { + return; + } + mmap_lock(); + snapshot = kzt_public_loader_observer; + result = kzt_public_loader_observer_refresh( + &snapshot, &kzt_public_loader_reader, kzt_loader_snapshot_visit, NULL); + if (result == KZT_PUBLIC_LOADER_OK) { + for (int i = 0; i < my_context->elfsize; i++) { + elfheader_t *head = my_context->elfs[i]; + + if (kzt_public_header_can_retire(head, &snapshot)) { + KZTRetiredElfHeader *retired = g_new(KZTRetiredElfHeader, 1); + + retired->head = head; + my_context->elfs[i] = NULL; + /* A nested guest callback can leave an outer native frame + * borrowing the header. Its cpu_exec RCU read section must + * finish before the ELF metadata itself is freed. */ + call_rcu(retired, kzt_free_retired_header, rcu); + } + } + while (my_context->elfsize > 0 && + !my_context->elfs[my_context->elfsize - 1]) { + my_context->elfsize--; + } + } + mmap_unlock(); + end_exclusive(); +} + +static void kzt_request_header_cleanup(CPUX86State *env) +{ + for (int i = 0; i < my_context->elfsize; i++) { + if (kzt_public_header_can_retire( + my_context->elfs[i], &kzt_public_loader_observer)) { + qatomic_set(&kzt_header_cleanup_pending, true); + cpu_exit(env_cpu(env)); + break; + } + } +} + uintptr_t kzt_resolve_guest_symbol(const char *name) { kzt_public_loader_observer_t snapshot; @@ -2714,6 +2830,8 @@ static int kzt_try_bind_loaded_object( box_free(writes); kzt_report_object_recovery_if_needed(object, name_copy); + h->public_link_map = object->link_map_addr; + h->public_load_bias = object->load_bias; AddElfHeader(my_context, h); collectX86free(h); if (!x86free && !strcmp(rbasename, libcName)) { @@ -2970,6 +3088,9 @@ static void kzt_dynamic_library_change_callback(CPUX86State *env) result = kzt_public_loader_observer_refresh( &kzt_public_loader_observer, &kzt_public_loader_reader, kzt_try_bind_observed_object, NULL); + if (result == KZT_PUBLIC_LOADER_OK) { + kzt_request_header_cleanup(env); + } mmap_unlock(); if (result != KZT_PUBLIC_LOADER_OK && result != KZT_PUBLIC_LOADER_BUSY) { diff --git a/target/i386/latx/include/elfloader_private.h b/target/i386/latx/include/elfloader_private.h index 933be08830a..8b1f4e38f0d 100755 --- a/target/i386/latx/include/elfloader_private.h +++ b/target/i386/latx/include/elfloader_private.h @@ -102,6 +102,9 @@ struct elfheader_s { int had_RelocateElf; int latx_type; int latx_hasfix; + /* Only public-loader headers participate in unload-time retirement. */ + uintptr_t public_link_map; + uintptr_t public_load_bias; }; int LoadSHNative(int fd, Elf64_Shdr *s, void** SH, const char* name, uint32_t type); int LoadSH(FILE *f, Elf64_Shdr *s, void** SH, const char* name, uint32_t type); diff --git a/target/i386/latx/include/kzt-runtime.h b/target/i386/latx/include/kzt-runtime.h index 906cb7fd55f..f3372589f4a 100644 --- a/target/i386/latx/include/kzt-runtime.h +++ b/target/i386/latx/include/kzt-runtime.h @@ -22,6 +22,7 @@ */ extern int option_kzt; extern uint32_t kzt_effective_groups; +void kzt_reclaim_unloaded_headers(void); static inline bool latx_kzt_runtime_enabled(void) { diff --git a/tests/unit/kzt/test-kzt-elf-lifecycle.py b/tests/unit/kzt/test-kzt-elf-lifecycle.py new file mode 100644 index 00000000000..b6b689bd728 --- /dev/null +++ b/tests/unit/kzt/test-kzt-elf-lifecycle.py @@ -0,0 +1,213 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-only +"""Test the production public-ELF reclamation bodies at an ownership seam. + +The mock RCU queue deliberately retains callbacks until the test drains it. +Guest dlopen/close tests separately exercise the real loader and RCU runtime. +""" +import argparse +from pathlib import Path +import shlex +import subprocess +import tempfile + + +def function(source, signature): + start = source.index(signature) + return source[start:source.index('\n}', start) + 2] + '\n' + + +PRELUDE = r''' +#include +#include +#include +#include +#include +#include +#define CHECK(x) do { if (!(x)) { fprintf(stderr, "%d: %s\n", \ + __LINE__, #x); exit(1); } } while (0) +#define TARGET_PAGE_SIZE UINT64_C(4096) +#define TARGET_PAGE_MASK (~(TARGET_PAGE_SIZE - 1)) +#define PAGE_VALID 8 +#define LOG_INFO 0 +#define printf_log(...) ((void)0) +#define g_new(t, n) ((t *)calloc(n, sizeof(t))) +#define g_free free +#define box_realloc realloc +#define qatomic_xchg(p, v) __atomic_exchange_n(p, v, __ATOMIC_SEQ_CST) +#define qatomic_read(p) __atomic_load_n(p, __ATOMIC_SEQ_CST) +#define qatomic_set(p, v) __atomic_store_n(p, v, __ATOMIC_SEQ_CST) +struct rcu_head { void *next; }; +typedef struct elfheader_s { + size_t numPHEntries; + Elf64_Phdr *PHEntries; + uintptr_t public_link_map, public_load_bias; + void *lib; +} elfheader_t; +struct malloc_map { elfheader_t *h; }; +typedef struct { + elfheader_t **elfs; + int elfcap, elfsize, mallocmapsize; + struct malloc_map **mallocmaps; +} box64context_t; +typedef struct { int running; } CPUX86State; +typedef struct { uintptr_t live_maps[8]; unsigned live_map_count; } + kzt_public_loader_observer_t; +typedef int kzt_public_loader_result_t; +#define KZT_PUBLIC_LOADER_OK 0 +#define KZT_PUBLIC_LOADER_BUSY 1 +static kzt_public_loader_observer_t kzt_public_loader_observer; +static int kzt_public_loader_reader; +static int snapshot_result, allow_exclusive = 1, exclusive, mmap_locked; +static int kicked, freed, delayed_count; +static uintptr_t mapped_page; +static box64context_t context, *my_context = &context; +static elfheader_t *elf_header; +static bool kzt_header_cleanup_pending; +static void *delayed[32]; +static void (*delayed_fn[32])(void *); +static bool kzt_public_loader_observer_has_map( + const kzt_public_loader_observer_t *o, uintptr_t key) { + for (unsigned i = 0; i < o->live_map_count; i++) { + if (o->live_maps[i] == key) return true; + } + return false; +} +static int page_get_flags(uintptr_t address) { + CHECK(mmap_locked); return address == mapped_page ? PAGE_VALID : 0; +} +static bool start_exclusive_timeout(int timeout) { + CHECK(!mmap_locked && !exclusive && timeout > 0); + if (!allow_exclusive) return false; + exclusive = 1; return true; +} +static void end_exclusive(void) { + CHECK(exclusive && !mmap_locked); exclusive = 0; +} +static void mmap_lock(void) { CHECK(!mmap_locked); mmap_locked = 1; } +static void mmap_unlock(void) { CHECK(mmap_locked); mmap_locked = 0; } +static int kzt_loader_snapshot_visit(void *object, void *opaque) { return 0; } +static int kzt_public_loader_observer_refresh( + kzt_public_loader_observer_t *o, void *reader, void *visit, void *opaque) { + CHECK(exclusive && mmap_locked); return snapshot_result; +} +static CPUX86State *env_cpu(CPUX86State *env) { return env; } +static void cpu_exit(CPUX86State *env) { CHECK(mmap_locked); kicked++; } +static void FreeElfHeader(elfheader_t **head) { + CHECK(*head); free((*head)->PHEntries); free(*head); *head = NULL; freed++; +} +static void defer(void *item, void (*fn)(void *)) { + CHECK(exclusive && mmap_locked && delayed_count < 32); + delayed[delayed_count] = item; delayed_fn[delayed_count++] = fn; +} +#define call_rcu(p, fn, member) defer(p, (void (*)(void *))fn) +static void drain(void) { + for (int i = 0; i < delayed_count; i++) delayed_fn[i](delayed[i]); + delayed_count = 0; +} +''' + +TEST = r''' +static elfheader_t *new_header(uintptr_t key, uintptr_t page) { + elfheader_t *h = calloc(1, sizeof(*h)); + CHECK(h); + h->public_link_map = key; h->public_load_bias = page; + h->numPHEntries = 1; h->PHEntries = calloc(1, sizeof(Elf64_Phdr)); + CHECK(h->PHEntries); + h->PHEntries[0].p_type = PT_LOAD; + h->PHEntries[0].p_memsz = TARGET_PAGE_SIZE * 2; + AddElfHeader(my_context, h); + return h; +} +static void request(CPUX86State *env) { + mmap_lock(); kzt_request_header_cleanup(env); mmap_unlock(); +} +int main(void) { + CPUX86State cpu = {0}; + elfheader_t *main_h = new_header(0, 0x10000); + elfheader_t *live_h = new_header(1, 0x20000); + elfheader_t *dead_h = new_header(2, 0x30000); + elfheader_t *lib_h = new_header(3, 0x40000); + elfheader_t *malloc_h = new_header(4, 0x50000); + struct malloc_map malloc_owner = { .h = malloc_h }; + struct malloc_map *owners[] = { &malloc_owner }; + elf_header = main_h; lib_h->lib = (void *)1; + context.mallocmaps = owners; context.mallocmapsize = 1; + kzt_public_loader_observer.live_maps[0] = 1; + kzt_public_loader_observer.live_map_count = 1; + + /* A partly mapped object must retain its PLT metadata. */ + mapped_page = 0x31000; + request(&cpu); CHECK(!kzt_header_cleanup_pending); + mapped_page = 0; + request(&cpu); CHECK(kzt_header_cleanup_pending && kicked == 1); + allow_exclusive = 0; + kzt_reclaim_unloaded_headers(); + CHECK(!kzt_header_cleanup_pending && !freed && !delayed_count); + /* Another loader event retries existing retired candidates. */ + request(&cpu); allow_exclusive = 1; + snapshot_result = KZT_PUBLIC_LOADER_BUSY; + kzt_reclaim_unloaded_headers(); + CHECK(!freed && !delayed_count && context.elfs[2] == dead_h); + request(&cpu); snapshot_result = KZT_PUBLIC_LOADER_OK; + /* The fresh safe-point snapshot may show link_map address reuse. */ + kzt_public_loader_observer.live_maps[1] = 2; + kzt_public_loader_observer.live_map_count = 2; + kzt_reclaim_unloaded_headers(); + CHECK(!freed && !delayed_count && context.elfs[2] == dead_h); + kzt_public_loader_observer.live_map_count = 1; + request(&cpu); kzt_reclaim_unloaded_headers(); + CHECK(context.elfs[2] == NULL && delayed_count == 1 && freed == 0); + CHECK(dead_h->PHEntries[0].p_type == PT_LOAD); /* Borrow valid until RCU. */ + CHECK(context.elfs[0] == main_h && context.elfs[1] == live_h); + CHECK(context.elfs[3] == lib_h && context.elfs[4] == malloc_h); + drain(); CHECK(freed == 1); + for (unsigned i = 0; i < 2000; i++) { + elfheader_t *h = new_header(100 + i, 0x60000); + CHECK(context.elfsize == 5 && context.elfs[2] == h); + request(&cpu); kzt_reclaim_unloaded_headers(); drain(); + CHECK(context.elfs[2] == NULL && context.elfsize == 5); + } + CHECK(context.elfcap == 16 && freed == 2001); + for (int i = 0; i < context.elfsize; i++) { + if (context.elfs[i]) FreeElfHeader(&context.elfs[i]); + } + free(context.elfs); + puts("KZT ELF retirement: live/borrowed guards, safe-point retry, RCU and slot reuse passed"); + return 0; +} +''' + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument('repo', type=Path) + parser.add_argument('--cc', default='cc') + args = parser.parse_args() + source = (args.repo / 'target/i386/latx/context/myalign.c').read_text() + context = (args.repo / 'target/i386/latx/context/box64context.c').read_text() + fields = source[source.index('typedef struct KZTRetiredElfHeader'):] + fields = fields[:fields.index('} KZTRetiredElfHeader;') + + len('} KZTRetiredElfHeader;')] + '\n' + body = PRELUDE + fields + for signature in ('static bool kzt_public_header_is_unmapped(', + 'static bool kzt_public_header_can_retire(', + 'static void kzt_free_retired_header(', + 'void kzt_reclaim_unloaded_headers(', + 'static void kzt_request_header_cleanup('): + body += function(source, signature) + body += function(context, 'int AddElfHeader(') + TEST + with tempfile.TemporaryDirectory(prefix='latx-elf-lifecycle-') as temp: + fixture = Path(temp) / 'fixture.c' + fixture.write_text(body) + for release in (False, True): + output = Path(temp) / ('release' if release else 'debug') + command = shlex.split(args.cc) + ['-std=gnu11', '-Wall', '-O2'] + if release: + command += ['-DNDEBUG'] + subprocess.run(command + [str(fixture), '-o', str(output)], check=True) + subprocess.run([str(output)], check=True) + + +if __name__ == '__main__': + main() diff --git a/tests/unit/meson.build b/tests/unit/meson.build index 829685ba4df..7a2c07692b7 100644 --- a/tests/unit/meson.build +++ b/tests/unit/meson.build @@ -140,6 +140,24 @@ if 'CONFIG_LATX' in config_host ) endif +test_kzt_path_collection = executable( + 'test-kzt-path-collection', + files( + 'kzt/test_kzt_path_collection.c', + '../../target/i386/latx/context/pathcoll.c', + ) + genh, + include_directories: include_directories('../../target/i386/latx/include'), + dependencies: [glib], +) +test('test-kzt-path-collection', test_kzt_path_collection, suite: 'lat-pr-fast') + +test( + 'test-kzt-elf-lifecycle', + python, + args: [files('kzt/test-kzt-elf-lifecycle.py'), project_source_root], + suite: 'lat-pr-fast', +) + test_kzt_public_loader_observer = executable( 'test-kzt-public-loader-observer', files( @@ -188,14 +206,3 @@ test( test_kzt_address_policy, suite: 'lat-pr-fast', ) - -test_kzt_path_collection = executable( - 'test-kzt-path-collection', - files( - 'kzt/test_kzt_path_collection.c', - '../../target/i386/latx/context/pathcoll.c', - ) + genh, - include_directories: include_directories('../../target/i386/latx/include'), - dependencies: [glib], -) -test('test-kzt-path-collection', test_kzt_path_collection, suite: 'lat-pr-fast')