From 8047786c7ebdc93d4a94de5c74bddb5be84e3d10 Mon Sep 17 00:00:00 2001 From: Hanlu Li Date: Tue, 8 Sep 2026 09:33:01 +0800 Subject: [PATCH] LATX, fix: Release AOT merge and segment resources AOT merging retains input buffers and index allocations after completion or failure. Mapping invalidation only releases the first overlapping segment, leaving other segment-owned cache mappings alive. Release merge inputs and their vector on all exit paths, destroy borrowed indexes before their input buffers, and free owned TB-index values on replacement and destruction. Clean up failed directory scans and invalidate every overlapping segment after successful unmap, remap or replacement. Keep the existing reusable ELF cache policy and cache format unchanged. Add TU/non-TU merge ownership tests and a segment-unmap test using real host mappings. The unfixed production paths fail the ownership checks; the fixed isolated branch passed 27 lat-pr-fast tests. The merge tests stub generated code publication, and unavailable guest integration tests were skipped. Signed-off-by: Hanlu Li --- linux-user/mmap.c | 20 +- meson.build | 35 ++ target/i386/latx/include/segment.h | 1 + target/i386/latx/sbt/aot_merge.c | 27 +- target/i386/latx/sbt/file_ctx.c | 2 + target/i386/latx/sbt/segment.c | 20 +- .../latx/sbt/tests/aot-merge-memory-test.c | 315 ++++++++++++++++++ .../latx/sbt/tests/aot-segment-unmap-test.c | 143 ++++++++ 8 files changed, 534 insertions(+), 29 deletions(-) create mode 100644 target/i386/latx/sbt/tests/aot-merge-memory-test.c create mode 100644 target/i386/latx/sbt/tests/aot-segment-unmap-test.c diff --git a/linux-user/mmap.c b/linux-user/mmap.c index fd2ba037dce..9a254961236 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 1898a101112..3827e5f8d63 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 93a5e14822f..27f4f71a59a 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 310761316fb..12658ddb8bc 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 0ce7b35f969..d51e96f5137 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 061a7592d16..d430d56e678 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 00000000000..f02fc43f2af --- /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 00000000000..ca87b30a3e1 --- /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; +}