diff --git a/linux-user/mmap.c b/linux-user/mmap.c index fd2ba037dc..9a25496123 100644 --- a/linux-user/mmap.c +++ b/linux-user/mmap.c @@ -1180,6 +1180,9 @@ abi_long target_mmap(abi_ulong start, abi_ulong len, int target_prot, #ifdef CONFIG_LATX_AOT wine_sec_info * wine_sec = NULL; uint64_t aot_offset = offset; + if (option_aot) { + segment_tree_remove_range(start, start + len); + } if (option_aot_wine && option_aot) { wine_dll_track_sections(start, len, offset, fd); wine_sec = wine_sec_tree_lookup(start); @@ -1318,10 +1321,7 @@ int target_munmap(abi_ulong start, abi_ulong len, int rlimit_as_account) if (ret == 0) { #ifdef CONFIG_LATX_AOT if (option_aot) { - seg_info *seg = segment_tree_lookup2(start, start + len); - if (seg) { - segment_tree_remove(seg); - } + segment_tree_remove_range(start, start + len); } #endif page_set_flags(start, start + len, 0); @@ -1693,18 +1693,10 @@ abi_long target_mremap(abi_ulong old_addr, abi_ulong old_size, #ifdef CONFIG_LATX_AOT if (option_aot) { - seg_info *seg; - if (!keep_old || (flags & MREMAP_DONTUNMAP)) { - seg = segment_tree_lookup2(old_addr, old_addr + old_size); - if (seg) { - segment_tree_remove(seg); - } - } - seg = segment_tree_lookup2(new_addr, new_addr + new_size); - if (seg) { - segment_tree_remove(seg); + segment_tree_remove_range(old_addr, old_addr + old_size); } + segment_tree_remove_range(new_addr, new_addr + new_size); } #endif } diff --git a/meson.build b/meson.build index 1898a10111..3827e5f8d6 100644 --- a/meson.build +++ b/meson.build @@ -755,6 +755,41 @@ foreach target : target_dirs aot_pe_load_address_test, suite: 'lat-pr-fast', ) + + aot_segment_unmap_test = executable( + 'test-latx-aot-segment-unmap', + files( + 'target/i386/latx/sbt/tests/aot-segment-unmap-test.c', + 'target/i386/latx/sbt/segment.c', + 'target/i386/latx/sbt/aot_lib.c' + ) + genh + tcg_trace_genh + [syscall_nr_generated], + c_args: smc_reload_test_c_args, + dependencies: [glib], + include_directories: target_inc, + link_args: smc_reload_test_link_args + ) + test('latx-aot-segment-unmap', aot_segment_unmap_test, + suite: 'lat-pr-fast') + + foreach variant : ['', '-no-tu'] + aot_merge_memory_test = executable( + 'test-latx-aot-merge-memory' + variant, + files( + 'target/i386/latx/sbt/tests/aot-merge-memory-test.c', + 'target/i386/latx/sbt/file_ctx.c' + ) + genh + tcg_trace_genh + [syscall_nr_generated], + c_args: smc_reload_test_c_args + + (variant == '' ? [] : ['-DAOT_MERGE_TEST_NO_TU']), + dependencies: [glib], + include_directories: target_inc, + link_args: smc_reload_test_link_args + [ + '-Wl,--wrap=malloc', '-Wl,--wrap=free', + '-Wl,--wrap=get_aot_path', '-Wl,--wrap=opendir' + ] + ) + test('latx-aot-merge-memory' + variant, aot_merge_memory_test, + suite: 'lat-pr-fast') + endforeach endif execs = [{ diff --git a/target/i386/latx/include/segment.h b/target/i386/latx/include/segment.h index 93a5e14822..27f4f71a59 100644 --- a/target/i386/latx/include/segment.h +++ b/target/i386/latx/include/segment.h @@ -86,6 +86,7 @@ void segment_tree_insert(char *name, target_ulong offset, target_ulong begin, seg_info *segment_tree_lookup(target_ulong pc); seg_info *segment_tree_lookup2(target_ulong begin, target_ulong end); void segment_tree_remove(seg_info *val); +void segment_tree_remove_range(target_ulong begin, target_ulong end); bool segment_tree_winepe_lookup(target_ulong pc); gint get_segment_num(void); void do_segment_record(seg_info **seg_info_vector); diff --git a/target/i386/latx/sbt/aot_merge.c b/target/i386/latx/sbt/aot_merge.c index 310761316f..12658ddb8b 100644 --- a/target/i386/latx/sbt/aot_merge.c +++ b/target/i386/latx/sbt/aot_merge.c @@ -105,7 +105,7 @@ static gint merge_tb_cmp(gconstpointer a, gconstpointer b) static void merge_tb_tree_init(GTree **tree) { *tree = g_tree_new_full((GCompareDataFunc)merge_tb_cmp, - NULL, NULL, NULL); + NULL, NULL, free); lsassert(*tree); } @@ -687,6 +687,16 @@ typedef enum AOTLoadResult { AOT_LOAD_INVALID_BASE, } AOTLoadResult; +static void aot_merge_release_buffers(void) +{ + for (int i = 0; i < aot_buffer_all_num; i++) { + free(aot_buffer_all[i].p); + } + g_free(aot_buffer_all); + aot_buffer_all = NULL; + aot_buffer_all_num = 0; +} + static AOTLoadResult aot_load_no_lock(char *lib_name) { void *buffer; @@ -803,12 +813,7 @@ static AOTLoadResult aot_load_no_lock(char *lib_name) load_error: result = AOT_LOAD_ERROR; release_buffers: - for (int i = 0; i < j; i++) { - free(aot_buffer_all[i].p); - } - g_free(aot_buffer_all); - aot_buffer_all = NULL; - aot_buffer_all_num = 0; + aot_merge_release_buffers(); return result; } @@ -1054,17 +1059,19 @@ AOTMergeResult aot2_merge(char *curr_lib_name, int first_seg_id, return AOT_MERGE_ERROR; } if (aot_buffer_all_num < 2) { + aot_merge_release_buffers(); return AOT_MERGE_ERROR; } #ifdef CONFIG_LATX_TU result = aot2_merge_tu(curr_lib_name, first_seg_id, last_seg_id, cpu); #else - g_tree_destroy(merge_segment_tree); - merge_segment_tree_init(); merge_rel_entry_num = 0; result = do_merge_seg_aot() ? AOT_MERGE_READY : AOT_MERGE_ERROR; + /* Tree entries borrow segment and TB data from the input buffers. */ + g_tree_destroy(merge_segment_tree); + merge_segment_tree_init(); #endif - aot_buffer_all_num = 0; + aot_merge_release_buffers(); return result; } #endif diff --git a/target/i386/latx/sbt/file_ctx.c b/target/i386/latx/sbt/file_ctx.c index 0ce7b35f96..d51e96f513 100644 --- a/target/i386/latx/sbt/file_ctx.c +++ b/target/i386/latx/sbt/file_ctx.c @@ -417,6 +417,8 @@ int aot_file_ctx(uint64_t maxSize, uint64_t leftMinSize) p_dir = opendir(aot_dir); if (p_dir == NULL) { qemu_log_mask(LAT_LOG_AOT, "---->can\'t open %s\n", aot_dir); + free(f_info); + free(aot_dir); return -1; } while ((p_dirent = readdir(p_dir))) { diff --git a/target/i386/latx/sbt/segment.c b/target/i386/latx/sbt/segment.c index 061a7592d1..d430d56e67 100644 --- a/target/i386/latx/sbt/segment.c +++ b/target/i386/latx/sbt/segment.c @@ -205,11 +205,7 @@ void segment_tree_insert(char *name, target_ulong offset, target_ulong begin, if (is_elf_file(name)) { seg->seg_flag = IS_ELF_SEG; } - seg_info * old_seg_info = segment_tree_lookup2(begin, end); - while(old_seg_info) { - segment_tree_remove(old_seg_info); - old_seg_info = segment_tree_lookup2(begin, end); - } + segment_tree_remove_range(begin, end); /* Now insert this new segment into segment_tree */ g_tree_replace(segment_tree, seg, seg); } @@ -389,6 +385,20 @@ void segment_tree_remove(seg_info* val) g_tree_remove(segment_tree, val); } +void segment_tree_remove_range(target_ulong begin, target_ulong end) +{ + seg_info *seg; + + if (begin >= end) { + return; + } + /* A segment describes one mapping identity; partial replacement also + * invalidates that identity, as in segment_tree_insert(). */ + while ((seg = segment_tree_lookup2(begin, end))) { + segment_tree_remove(seg); + } +} + static bool check_winepe_segment(seg_info * res) { char *file_name = basename(res->file_name); for (int i = 0; i < 100; i++) { diff --git a/target/i386/latx/sbt/tests/aot-merge-memory-test.c b/target/i386/latx/sbt/tests/aot-merge-memory-test.c new file mode 100644 index 0000000000..f02fc43f2a --- /dev/null +++ b/target/i386/latx/sbt/tests/aot-merge-memory-test.c @@ -0,0 +1,315 @@ +/* + * SPDX-FileCopyrightText: 2026 LAT Project Authors + * + * SPDX-License-Identifier: GPL-2.0-only + */ + +#include "qemu-def.h" + +#ifdef AOT_MERGE_TEST_NO_TU +#undef CONFIG_LATX_TU +#endif + +/* Include the implementation to cover the private merge-tree lifecycle too. */ +#include "../aot_merge.c" + +__thread CPUState *thread_cpu; +int qemu_loglevel; +int option_debug_aot; +time_t aot_st_ctime; +aot_file_info *aot_buffer_all; +int aot_buffer_all_num; +seg_info **seg_info_vector; +static char path_buffer[PATH_MAX]; +char *aot_file_path = path_buffer; +static char lock_buffer[PATH_MAX]; +char *aot_file_lock = lock_buffer; + +static char *test_dir; +static bool track_allocations; +static size_t live_bytes; +static size_t allocation_count; +static unsigned int translate_count; +static unsigned int generate_count; +static bool generate_success; +static bool fail_opendir; +static struct { + void *ptr; + size_t size; +} allocations[128]; + +void *__real_malloc(size_t size); +void __real_free(void *ptr); +void *__wrap_malloc(size_t size); +void __wrap_free(void *ptr); +int __wrap_get_aot_path(const char *name, char *path, size_t size); +DIR *__real_opendir(const char *name); +DIR *__wrap_opendir(const char *name); + +void *__wrap_malloc(size_t size) +{ + void *ptr = __real_malloc(size); + + if (track_allocations && ptr) { + size_t i; + + for (i = 0; i < G_N_ELEMENTS(allocations); i++) { + if (!allocations[i].ptr) { + allocations[i].ptr = ptr; + allocations[i].size = size; + live_bytes += size; + allocation_count++; + break; + } + } + g_assert_cmpuint(i, <, G_N_ELEMENTS(allocations)); + } + return ptr; +} + +void __wrap_free(void *ptr) +{ + if (ptr) { + for (size_t i = 0; i < G_N_ELEMENTS(allocations); i++) { + if (allocations[i].ptr == ptr) { + live_bytes -= allocations[i].size; + allocations[i].ptr = NULL; + break; + } + } + } + __real_free(ptr); +} + +int __wrap_get_aot_path(const char *name, char *path, size_t size) +{ + int len = snprintf(path, size, "%s/%s.aot", test_dir, name); + + return len < 0 || (size_t)len >= size ? -ENAMETOOLONG : 0; +} + +DIR *__wrap_opendir(const char *name) +{ + if (fail_opendir) { + errno = ENOENT; + return NULL; + } + return __real_opendir(name); +} + +int aot_get_file_name(char *file G_GNUC_UNUSED, char *buf G_GNUC_UNUSED, + int index G_GNUC_UNUSED) +{ + g_assert_not_reached(); +} + +int qemu_log(const char *fmt G_GNUC_UNUSED, ...) +{ + return 0; +} + +void print_stack_trace(void) +{ +} + +void pstrcpy(char *buf, int size, const char *str) +{ + g_strlcpy(buf, str, size); +} + +uint8_t get_file_type(const char *name G_GNUC_UNUSED) +{ + return ELF_AOT_FILE; +} + +void pre_translate(int begin, int end, CPUState *cpu G_GNUC_UNUSED, + tb_tmp_message *messages) +{ + g_assert_cmpint(begin, ==, 0); + g_assert_cmpint(end, ==, 1); + g_assert(messages != NULL); + g_assert(aot_buffer_all != NULL); + translate_count++; +} + +int do_generate_aot(int begin, int end) +{ + g_assert_cmpint(begin, ==, 0); + g_assert_cmpint(end, ==, 1); + g_assert(aot_buffer_all != NULL); + generate_count++; + return generate_success; +} + +static void write_cache(const char *path, const char *library, + uint32_t tb_offset, bool footer) +{ + size_t size = MiB; + g_autofree uint8_t *contents = g_malloc0(size); + aot_header *header = (aot_header *)contents; + aot_segment *segment = (aot_segment *)(header + 1); + aot_tb *tb = (aot_tb *)(segment + 1); + uint32_t *code = (uint32_t *)(tb + 1); + char *name = (char *)(code + 1); + + header->aot_file_type = ELF_AOT_FILE; + header->segment_table_offset = (uint8_t *)segment - contents; + header->segments_num = 1; + segment->details.seg_begin = 0x10000; + segment->details.seg_end = 0x11000; + segment->details.file_offset = 0; + segment->segment_tbs_offset = (uint8_t *)tb - contents; + segment->segment_tbs_num = 1; + segment->lib_name_offset = (uint8_t *)name - contents; + tb->offset_in_segment = tb_offset; + tb->tb_cache_offset = (uint8_t *)code - contents; + tb->tb_cache_size = sizeof(*code); + tb->tu_size = sizeof(*code); + tb->rel_start_index = -1; + tb->rel_end_index = -1; + *code = 0x03400000; /* LoongArch nop, copied but never executed. */ + strcpy(name, library); + if (footer) { + memcpy(contents + size - strlen(AOT_VERSION), AOT_VERSION, + strlen(AOT_VERSION)); + } + g_assert(g_file_set_contents(path, (char *)contents, size, NULL)); +} + +static void assert_released(void) +{ + g_test_message("live merge allocation bytes: %zu", live_bytes); + g_assert_cmpuint(live_bytes, ==, 0); + g_assert(aot_buffer_all == NULL); + g_assert_cmpint(aot_buffer_all_num, ==, 0); +} + +static void test_merge_lifetime(void) +{ + g_autofree char *library = g_build_filename(test_dir, "library", NULL); + char base_path[PATH_MAX]; + char tmp_path[PATH_MAX]; + char name[] = "merge"; + CPUState cpu = { 0 }; + TaskState task = { 0 }; + seg_info seg = { + .seg_begin = 0x10000, + .seg_end = 0x11000, + .first_tb_id = -1, + .last_tb_id = -2, + }; + seg_info *segments[] = { &seg }; + + cpu.opaque = &task; + thread_cpu = &cpu; + seg_info_vector = segments; + seg.file_name = library; + g_assert(g_file_set_contents(library, "library", -1, NULL)); + g_assert_cmpint(__wrap_get_aot_path(name, base_path, sizeof(base_path)), + ==, 0); + g_assert_cmpint(aot_file_get_tmp_path(base_path, tmp_path, + sizeof(tmp_path)), ==, 0); + merge_segment_tree_init(); + + /* Every iteration reads both real artifacts, then traverses the merger. */ + for (int i = 0; i < 16; i++) { + AOTMergeResult result; + + write_cache(base_path, library, 0, true); + write_cache(tmp_path, library, 4, true); + translate_count = generate_count = 0; + generate_success = (i % 2) == 0; + track_allocations = true; + result = aot2_merge(name, 0, 1, &cpu); + track_allocations = false; +#ifdef CONFIG_LATX_TU + g_assert_cmpint(result, ==, generate_success ? AOT_MERGE_READY : + AOT_MERGE_ERROR); + g_assert_cmpuint(translate_count, ==, 1); + g_assert_cmpuint(generate_count, ==, 1); +#else + g_assert_cmpint(result, ==, AOT_MERGE_READY); +#endif + g_assert_cmpuint(allocation_count, >=, 2); + assert_released(); + } + +#ifdef CONFIG_LATX_TU + write_cache(base_path, library, 0, true); + write_cache(tmp_path, library, 0, true); + translate_count = generate_count = 0; + track_allocations = true; + AOTMergeResult result = aot2_merge(name, 0, 1, &cpu); + track_allocations = false; + g_assert_cmpint(result, ==, AOT_MERGE_DUPLICATE); + g_assert_cmpuint(translate_count, ==, 0); + g_assert_cmpuint(generate_count, ==, 0); + assert_released(); +#endif + + /* A successful first read followed by an invalid second file. */ + write_cache(base_path, library, 0, true); + write_cache(tmp_path, library, 0, false); + track_allocations = true; + AOTMergeResult invalid_result = aot2_merge(name, 0, 1, &cpu); + track_allocations = false; + g_assert_cmpint(invalid_result, ==, AOT_MERGE_ERROR); + assert_released(); + + g_tree_destroy(merge_segment_tree); + merge_segment_tree = NULL; + g_assert_cmpint(g_remove(base_path), ==, 0); + g_assert_cmpint(g_remove(tmp_path), ==, 0); + g_assert_cmpint(g_remove(library), ==, 0); + thread_cpu = NULL; + seg_info_vector = NULL; +} + +static void test_merge_tree_lifetime(void) +{ + GTree *tree; + aot_tb first = { 0 }; + aot_tb replacement = { 0 }; + + merge_tb_tree_init(&tree); + track_allocations = true; + merge_tb_tree_insert(tree, 0, &first, NULL, 0); + merge_tb_tree_insert(tree, 0, &replacement, NULL, 0); + track_allocations = false; + g_assert_cmpuint(g_tree_nnodes(tree), ==, 1); + g_assert_cmpuint(live_bytes, ==, sizeof(merge_tb_info)); + g_tree_destroy(tree); + g_assert_cmpuint(live_bytes, ==, 0); +} + +static void test_cache_directory_failure(void) +{ + fail_opendir = true; + for (int i = 0; i < 16; i++) { + int result; + + track_allocations = true; + result = aot_file_ctx(12000, 500); + track_allocations = false; + g_assert_cmpint(result, ==, -1); + g_assert_cmpuint(live_bytes, ==, 0); + } + fail_opendir = false; +} + +int main(int argc, char **argv) +{ + int result; + + g_test_init(&argc, &argv, NULL); + test_dir = g_dir_make_tmp("latx-aot-merge-memory-XXXXXX", NULL); + g_assert(test_dir != NULL); + g_test_add_func("/aot-merge/buffer-lifetime", test_merge_lifetime); + g_test_add_func("/aot-merge/tree-lifetime", test_merge_tree_lifetime); + g_test_add_func("/aot-merge/cache-directory-failure", + test_cache_directory_failure); + result = g_test_run(); + g_assert_cmpint(g_rmdir(test_dir), ==, 0); + g_free(test_dir); + return result; +} diff --git a/target/i386/latx/sbt/tests/aot-segment-unmap-test.c b/target/i386/latx/sbt/tests/aot-segment-unmap-test.c new file mode 100644 index 0000000000..ca87b30a3e --- /dev/null +++ b/target/i386/latx/sbt/tests/aot-segment-unmap-test.c @@ -0,0 +1,143 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#include "qemu/osdep.h" + +#include "aot.h" +#include "aot_lib.h" +#include "segment.h" + +/* Exercise the actual guest munmap entry point with real host mappings. */ +#include "../../../../../linux-user/mmap.c" + +int qemu_loglevel; +int trace_events_enabled_count; +uint16_t _TRACE_TARGET_MUNMAP_DSTATE; +bool message_with_timestamp; +int option_aot = 1; +int option_aot_pe_profile; +int option_prlimit; +const char *aot_process_profile = "browser"; +abi_ulong mmap_min_addr; +unsigned long guest_base; +unsigned long reserved_va; +unsigned long qemu_host_page_size; +intptr_t qemu_host_page_mask; +rlim_t vir_rlimit_as = RLIM_INFINITY; +rlim_t vir_rlimit_as_acc; + +int qemu_log(const char *fmt G_GNUC_UNUSED, ...) +{ + return 0; +} + +void print_stack_trace(void) +{ +} + +int qemu_get_thread_id(void) +{ + return getpid(); +} + +void *page_get_target_data(target_ulong address) +{ + return NULL; +} + +int page_get_flags(target_ulong address) +{ + return 0; +} + +void page_set_flags(target_ulong start, target_ulong end, int flags) +{ +} + +void guest_vma_name_reset(abi_ulong start, abi_ulong len) +{ +} + +uint8_t get_file_type(const char *name) +{ + return PE_AOT_FILE; +} + +uint8_t is_elf_file(const char *name) +{ + return false; +} + +static void add_segment(char *path, uintptr_t address, size_t length, + void **cache) +{ + seg_info *seg; + char name[PATH_MAX]; + + segment_tree_insert(path, 0, address, address + length); + seg = segment_tree_lookup(address); + g_assert(seg != NULL); + *cache = mmap(NULL, qemu_host_page_size, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + g_assert(*cache != MAP_FAILED); + seg->buffer = *cache; + seg->seg_flag |= SEG_AOT_LOADED; + g_assert(segment_get_aot_file_name(seg, name, sizeof(name)) == 0); + g_assert(lib_tree_insert(name, *cache, qemu_host_page_size) != NULL); +} + +int main(void) +{ + char path[] = "/tmp/latx-segment-unmap-XXXXXX"; + void *cache[5]; + unsigned char resident; + size_t page_size = sysconf(_SC_PAGESIZE); + size_t stride = page_size * 2; + void *guest; + int fd; + int ret; + + qemu_host_page_size = page_size; + qemu_host_page_mask = ~(page_size - 1); + segment_tree_init(); + lib_tree_init(); + fd = mkstemp(path); + g_assert(fd >= 0); + close(fd); + guest = mmap(NULL, stride * 5, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + g_assert(guest != MAP_FAILED); + for (unsigned int i = 0; i < G_N_ELEMENTS(cache); i++) { + add_segment(path, (uintptr_t)guest + stride * i, page_size, + &cache[i]); + } + g_assert(get_segment_num() == 5); + g_assert(get_lib_num() == 5); + + /* Invalid input must not discard cache ownership. */ + ret = target_munmap((abi_ulong)guest + 1, stride, 0); + g_assert(ret == -TARGET_EINVAL); + g_assert(get_segment_num() == 5); + + /* One range removes three segments but preserves both neighbours. */ + ret = target_munmap((abi_ulong)guest + stride, stride * 3, 0); + g_assert(ret == 0); + g_assert(get_segment_num() == 2); + g_assert(get_lib_num() == 2); + for (unsigned int i = 1; i < 4; i++) { + errno = 0; + ret = mincore(cache[i], page_size, &resident); + g_assert(ret == -1 && errno == ENOMEM); + } + g_assert(segment_tree_lookup((abi_ulong)guest) != NULL); + g_assert(segment_tree_lookup((abi_ulong)guest + stride * 4) != NULL); + + /* Repeating an already-unmapped range is harmless. */ + ret = target_munmap((abi_ulong)guest + stride, stride * 3, 0); + g_assert(ret == 0); + g_assert(get_segment_num() == 2); + ret = target_munmap((abi_ulong)guest, stride * 5, 0); + g_assert(ret == 0); + g_assert(get_segment_num() == 0); + g_assert(get_lib_num() == 0); + unlink(path); + return 0; +}