From 6bc69a2e6e2deb89d444a01c8426de4cb37134a2 Mon Sep 17 00:00:00 2001 From: zqz Date: Thu, 13 Aug 2026 11:27:46 +0800 Subject: [PATCH 01/13] LATX, fix: preserve AMCAS cmpxchg result and flags LoongArch AMCAS overwrites its result register with the observed memory value. Reusing the guest accumulator as that register loses the distinction between the expected value, the loaded value, and the address operand. Use a dedicated temporary for the AMCAS result, derive EFLAGS from that observed value, and write it back to the guest accumulator. This preserves x86 CMPXCHG register and condition-code semantics for the fast atomic path. Signed-off-by: zqz --- .../latx/translator/tr-lock-fast-atomic.c | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/target/i386/latx/translator/tr-lock-fast-atomic.c b/target/i386/latx/translator/tr-lock-fast-atomic.c index fc796e5331f..00ca107d67b 100644 --- a/target/i386/latx/translator/tr-lock-fast-atomic.c +++ b/target/i386/latx/translator/tr-lock-fast-atomic.c @@ -387,7 +387,8 @@ bool translate_lock_cmpxchg_fast_atomic(IR1_INST *pir1) IR1_OPND *opnd0 = ir1_get_opnd(pir1, 0); IR1_OPND *opnd1 = ir1_get_opnd(pir1, 1); - IR2_OPND src0, src1; + IR2_OPND src0 = ra_alloc_itemp(); + IR2_OPND src1; IR1_OPND *reg_ir1 = NULL; int opnd0_size = ir1_opnd_size(opnd0); @@ -456,17 +457,13 @@ bool translate_lock_cmpxchg_fast_atomic(IR1_INST *pir1) } #else if (opnd0_size == 32) { - /* amcas always change rd to rj (change gpr_eax_opnd to *mem_addr), - * so we need to generate EFLAGS.ZF by comparing old eax and new eax(src0_mem_addr) */ - IR2_OPND eax_opnd; - if (ir1_need_calculate_any_flag(pir1)) { - eax_opnd = ra_alloc_itemp(); - load_ireg_from_ir1_2(eax_opnd, reg_ir1, SIGN_EXTENSION, false); - } - la_amcas_db_w(gpr_eax_opnd, src1, mem_opnd); - if (ir1_need_calculate_any_flag(pir1)) { - generate_eflag_calculation(src0, eax_opnd, gpr_eax_opnd, pir1, true); - } + /* AMCAS needs a result register distinct from its address and input. */ + IR2_OPND cas_result = ra_alloc_itemp(); + + load_ireg_from_ir1_2(cas_result, reg_ir1, SIGN_EXTENSION, false); + la_amcas_db_w(cas_result, src1, mem_opnd); + generate_eflag_calculation(src0, gpr_eax_opnd, cas_result, pir1, true); + store_ireg_to_ir1(cas_result, reg_ir1, false); return true; } From b4b4e4e251cf54cfd57ac1048987681561f3a59a Mon Sep 17 00:00:00 2001 From: zqz Date: Thu, 13 Aug 2026 11:27:46 +0800 Subject: [PATCH 02/13] LATX, fix: preserve destination adjustment after cache exit A cache-exit path replaces the temporary destination with the architectural zero register. When the instruction also has an explicit destination adjustment, returning immediately drops that adjustment. Apply adjust_dest() before returning from the non-segment cache-exit path so address-size handling and destination semantics remain consistent with the normal path. Signed-off-by: zqz --- target/i386/latx/translator/mem-interface.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/target/i386/latx/translator/mem-interface.c b/target/i386/latx/translator/mem-interface.c index 5ad6bdf96c5..be7514d6eaa 100644 --- a/target/i386/latx/translator/mem-interface.c +++ b/target/i386/latx/translator/mem-interface.c @@ -463,7 +463,11 @@ static IR2_OPND convert_mem_helper(IR1_OPND *opnd1, IR2_OPND *arg_dest_op, case 0: ra_free_temp_auto(dest_op); dest_op = zero_ir2_opnd; - if (!has_seg && !arg_dest_op) { + if (!has_seg) { + if (arg_dest_op) { + return adjust_dest(dest_op, arg_dest_op, dest_size, + ir1_addr_size(pir1)); + } return dest_op; } break; From 3ecbe9af68796e8c4be074ddf5a2303c8f76286d Mon Sep 17 00:00:00 2001 From: zqz Date: Thu, 13 Aug 2026 11:28:04 +0800 Subject: [PATCH 03/13] LATX, fix: translate legacy AMDGPU GEM_VA ioctl Older libdrm builds use the pre-timeline GEM_VA request layout for ioctl number 0x48, while current host DRM headers describe the newer layout. Passing the guest buffer through as the current structure corrupts the host request. Define the legacy target ABI, register it as a special ioctl, and marshal its common GEM_VA fields into the host structure before issuing the host ioctl. Current ABI requests continue to use the existing path. Signed-off-by: zqz --- linux-user/ioctl/ioctl_def/def_amdgpu_drm.h | 14 +++++++- .../ioctl/ioctl_syscall/syscall_amdgpu_drm.c | 32 ++++++++++++++++++- linux-user/ioctl/ioctl_type/type_amdgpu_drm.h | 8 +++++ linux-user/ioctl/ioctls/ioctl_amdgpu_drm.h | 2 ++ 4 files changed, 54 insertions(+), 2 deletions(-) diff --git a/linux-user/ioctl/ioctl_def/def_amdgpu_drm.h b/linux-user/ioctl/ioctl_def/def_amdgpu_drm.h index 0ace0459bb9..6ecee6d75f9 100644 --- a/linux-user/ioctl/ioctl_def/def_amdgpu_drm.h +++ b/linux-user/ioctl/ioctl_def/def_amdgpu_drm.h @@ -16,6 +16,10 @@ TARGET_IOWR('d', 0x47, union drm_amdgpu_gem_wait_idle) #define TARGET_DRM_IOCTL_AMDGPU_GEM_VA \ TARGET_IOWR('d', 0x48, struct drm_amdgpu_gem_va) +/* libdrm built against the pre-timeline GEM_VA UAPI uses this command. */ +#define TARGET_DRM_IOCTL_AMDGPU_GEM_VA_OLD \ + TARGET_IOWR('d', 0x48, struct target_drm_amdgpu_gem_va_old) +#define DRM_IOCTL_AMDGPU_GEM_VA_OLD DRM_IOCTL_AMDGPU_GEM_VA #define TARGET_DRM_IOCTL_AMDGPU_WAIT_CS \ TARGET_IOWR('d', 0x49, union drm_amdgpu_wait_cs) #define TARGET_DRM_IOCTL_AMDGPU_GEM_OP \ @@ -87,4 +91,12 @@ struct target_drm_amdgpu_fence_to_handle_in { abi_uint what; abi_uint pad; }; - +struct target_drm_amdgpu_gem_va_old { + abi_uint handle; + abi_uint _pad; + abi_uint operation; + abi_uint flags; + abi_ullong va_address; + abi_ullong offset_in_bo; + abi_ullong map_size; +}; diff --git a/linux-user/ioctl/ioctl_syscall/syscall_amdgpu_drm.c b/linux-user/ioctl/ioctl_syscall/syscall_amdgpu_drm.c index d006b4fd0c3..5e9e8bc79ab 100644 --- a/linux-user/ioctl/ioctl_syscall/syscall_amdgpu_drm.c +++ b/linux-user/ioctl/ioctl_syscall/syscall_amdgpu_drm.c @@ -39,6 +39,21 @@ static inline abi_long target_to_host_drm_amdgpu_gem_metadata return 0; } +static inline abi_long target_to_host_drm_amdgpu_gem_va_old + (struct drm_amdgpu_gem_va *host_ver, + struct target_drm_amdgpu_gem_va_old *target_ver) +{ + memset(host_ver, 0, sizeof(*host_ver)); + __get_user(host_ver->handle, &target_ver->handle); + __get_user(host_ver->_pad, &target_ver->_pad); + __get_user(host_ver->operation, &target_ver->operation); + __get_user(host_ver->flags, &target_ver->flags); + __get_user(host_ver->va_address, &target_ver->va_address); + __get_user(host_ver->offset_in_bo, &target_ver->offset_in_bo); + __get_user(host_ver->map_size, &target_ver->map_size); + return 0; +} + static inline void host_to_target_drm_amdgpu_gem_metadata (struct target_drm_amdgpu_gem_metadata *target_ver, struct drm_amdgpu_gem_metadata *host_ver) @@ -90,6 +105,8 @@ static abi_long do_ioctl_amdgpu_drm(const IOCTLEntry *ie, struct drm_amdgpu_gem_metadata *drm_amdgpu_gem_metadata; struct target_drm_amdgpu_fence_to_handle_in *target_drm_amdgpu_fence_to_handle_in; union drm_amdgpu_fence_to_handle *drm_amdgpu_fence_to_handle; + struct target_drm_amdgpu_gem_va_old *target_drm_amdgpu_gem_va_old; + struct drm_amdgpu_gem_va *drm_amdgpu_gem_va; switch (ie->host_cmd) { case DRM_IOCTL_AMDGPU_INFO: if (!lock_user_struct(VERIFY_READ, target_drm_amdgpu_info, arg, 0)) { @@ -125,6 +142,20 @@ static abi_long do_ioctl_amdgpu_drm(const IOCTLEntry *ie, } unlock_user_struct(target_drm_amdgpu_gem_metadata, arg, 0); return ret; + case DRM_IOCTL_AMDGPU_GEM_VA: + if (!lock_user_struct(VERIFY_READ, + target_drm_amdgpu_gem_va_old, arg, 0)) { + return -TARGET_EFAULT; + } + drm_amdgpu_gem_va = (struct drm_amdgpu_gem_va *)buf_temp; + ret = target_to_host_drm_amdgpu_gem_va_old( + drm_amdgpu_gem_va, target_drm_amdgpu_gem_va_old); + if (!is_error(ret)) { + ret = get_errno(safe_ioctl(fd, ie->host_cmd, + drm_amdgpu_gem_va)); + } + unlock_user_struct(target_drm_amdgpu_gem_va_old, arg, 0); + return ret; case DRM_IOCTL_AMDGPU_FENCE_TO_HANDLE: if (!lock_user_struct(VERIFY_READ, target_drm_amdgpu_fence_to_handle_in, arg, 0)) { @@ -147,4 +178,3 @@ static abi_long do_ioctl_amdgpu_drm(const IOCTLEntry *ie, } return -TARGET_ENOSYS; } - diff --git a/linux-user/ioctl/ioctl_type/type_amdgpu_drm.h b/linux-user/ioctl/ioctl_type/type_amdgpu_drm.h index 6c5c162ea24..a0905275d64 100644 --- a/linux-user/ioctl/ioctl_type/type_amdgpu_drm.h +++ b/linux-user/ioctl/ioctl_type/type_amdgpu_drm.h @@ -41,6 +41,14 @@ STRUCT(drm_amdgpu_gem_wait_idle_in, TYPE_INT, TYPE_INT, TYPE_ULONGLONG) +STRUCT(drm_amdgpu_gem_va_old, + TYPE_INT, + TYPE_INT, + TYPE_INT, + TYPE_INT, + TYPE_ULONGLONG, + TYPE_ULONGLONG, + TYPE_ULONGLONG) #ifdef CONFIG_AMDGPU_GEN_VA_OLD STRUCT(drm_amdgpu_gem_va, TYPE_INT, diff --git a/linux-user/ioctl/ioctls/ioctl_amdgpu_drm.h b/linux-user/ioctl/ioctls/ioctl_amdgpu_drm.h index fe4a4fa33bd..e5146e01e51 100644 --- a/linux-user/ioctl/ioctls/ioctl_amdgpu_drm.h +++ b/linux-user/ioctl/ioctls/ioctl_amdgpu_drm.h @@ -14,6 +14,8 @@ IOCTL_SPECIAL(DRM_IOCTL_AMDGPU_GEM_METADATA, IOC_RW, do_ioctl_amdgpu_drm, MK_PTR(MK_STRUCT(STRUCT_drm_amdgpu_gem_metadata))) IOCTL(DRM_IOCTL_AMDGPU_GEM_WAIT_IDLE, IOC_RW, MK_PTR(MK_STRUCT(STRUCT_drm_amdgpu_gem_wait_idle_in))) +IOCTL_SPECIAL(DRM_IOCTL_AMDGPU_GEM_VA_OLD, IOC_W, do_ioctl_amdgpu_drm, + MK_PTR(MK_STRUCT(STRUCT_drm_amdgpu_gem_va_old))) IOCTL(DRM_IOCTL_AMDGPU_GEM_VA, IOC_RW, MK_PTR(MK_STRUCT(STRUCT_drm_amdgpu_gem_va))) IOCTL(DRM_IOCTL_AMDGPU_WAIT_CS, IOC_RW, From 1b79e1a09a70c8430bc8144b06b2050d5bd889e5 Mon Sep 17 00:00:00 2001 From: zqz Date: Thu, 13 Aug 2026 11:28:04 +0800 Subject: [PATCH 04/13] LATX, fix: resolve i386 libraries beside PT_INTERP Some 32-bit launchers invoke their dynamic linker with absolute library names that are absent from the configured runtime prefix, even though matching libraries are located beside the selected PT_INTERP loader. Record the interpreter code range during ELF loading and only apply a read-only fallback while execution is inside that i386 dynamic linker. The fallback accepts conventional lib*.so names, probes the interpreter directory, and preserves the original error for all other opens. Signed-off-by: zqz --- linux-user/elfload.c | 2 + linux-user/syscall.c | 92 ++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 86 insertions(+), 8 deletions(-) diff --git a/linux-user/elfload.c b/linux-user/elfload.c index cdb9b714dd1..7fe8c03e663 100644 --- a/linux-user/elfload.c +++ b/linux-user/elfload.c @@ -3480,6 +3480,8 @@ int load_elf_binary(struct linux_binprm *bprm, struct image_info *info) info->load_bias = interp_info.load_bias; info->entry = interp_info.entry; info->interpreter_path = elf_interpreter; + info->interpreter_start_code = interp_info.start_code; + info->interpreter_end_code = interp_info.end_code; //g_free(elf_interpreter); } #if defined(CONFIG_LATX_KZT) diff --git a/linux-user/syscall.c b/linux-user/syscall.c index d65e4930f64..e5ecdc4abbe 100644 --- a/linux-user/syscall.c +++ b/linux-user/syscall.c @@ -12830,6 +12830,60 @@ static int has_x_permission(struct stat *st) { } } +#if defined(CONFIG_LATX) && defined(TARGET_I386) +static bool latx_i386_dynamic_linker(CPUArchState *env) +{ + return info->interpreter_path + && env->eip >= info->interpreter_start_code + && env->eip < info->interpreter_end_code; +} + +static bool latx_i386_shared_library(const char *pathname) +{ + const char *basename = strrchr(pathname, '/'); + const char *so; + + if (!basename || !g_str_has_prefix(++basename, "lib")) { + return false; + } + + so = strstr(basename, ".so"); + return so && (so[3] == '\0' || so[3] == '.'); +} + +static char *latx_i386_interpreter_library(CPUArchState *env, + const char *pathname, int flags) +{ + char *interpreter_path; + char *interpreter_dir; + char *candidate; + const char *basename; + + if (!latx_i386_dynamic_linker(env) || !pathname || pathname[0] != '/' + || (flags & O_ACCMODE) != O_RDONLY || (flags & O_CREAT) + || !latx_i386_shared_library(pathname)) { + return NULL; + } + + interpreter_path = realpath(path(info->interpreter_path), NULL); + if (!interpreter_path) { + return NULL; + } + + interpreter_dir = g_path_get_dirname(interpreter_path); + free(interpreter_path); + basename = strrchr(pathname, '/') + 1; + candidate = g_build_filename(interpreter_dir, basename, NULL); + g_free(interpreter_dir); + + if (access(candidate, F_OK) == 0) { + return candidate; + } + g_free(candidate); + return NULL; +} +#endif + static int do_openat(void *cpu_env, int dirfd, const char *pathname, int flags, mode_t mode) { struct fake_open { @@ -12928,17 +12982,39 @@ static int do_openat(void *cpu_env, int dirfd, const char *pathname, int flags, return fd; } const char *realpath = path(pathname); + char *runtime_path = NULL; struct stat st; + int ret; +#if defined(CONFIG_LATX) && defined(TARGET_I386) + int saved_errno; +#endif if (fstatat(dirfd, realpath, &st, 0) == 0) { if (S_ISDIR(st.st_mode)) { if (!has_x_permission(&st)) { errno = EACCES; - return -1; + ret = -1; + goto out; } } } - return safe_openat(dirfd, path(pathname), flags, mode); + ret = safe_openat(dirfd, realpath, flags, mode); + +#if defined(CONFIG_LATX) && defined(TARGET_I386) + if (ret < 0 && errno == ENOENT && realpath == pathname) { + saved_errno = errno; + runtime_path = latx_i386_interpreter_library(cpu_env, pathname, flags); + if (runtime_path) { + ret = safe_openat(dirfd, runtime_path, flags, mode); + } else { + errno = saved_errno; + } + } +#endif + +out: + g_free(runtime_path); + return ret; } #define MAX_PATH_SIZE 1024 @@ -19357,9 +19433,9 @@ static abi_long do_syscall1(void *cpu_env, int num, abi_long arg1, n = lock_user_string(arg2); if (p && n) { if (num == TARGET_NR_setxattr) { - ret = get_errno(setxattr(p, n, v, arg4, arg5)); + ret = get_errno(setxattr(p, n, v, arg4, arg5)); } else { - ret = get_errno(lsetxattr(p, n, v, arg4, arg5)); + ret = get_errno(lsetxattr(p, n, v, arg4, arg5)); } } else { ret = -TARGET_EFAULT; @@ -19402,9 +19478,9 @@ static abi_long do_syscall1(void *cpu_env, int num, abi_long arg1, n = lock_user_string(arg2); if (p && n) { if (num == TARGET_NR_getxattr) { - ret = get_errno(getxattr(p, n, v, arg4)); + ret = get_errno(getxattr(p, n, v, arg4)); } else { - ret = get_errno(lgetxattr(p, n, v, arg4)); + ret = get_errno(lgetxattr(p, n, v, arg4)); } } else { ret = -TARGET_EFAULT; @@ -19441,9 +19517,9 @@ static abi_long do_syscall1(void *cpu_env, int num, abi_long arg1, n = lock_user_string(arg2); if (p && n) { if (num == TARGET_NR_removexattr) { - ret = get_errno(removexattr(p, n)); + ret = get_errno(removexattr(p, n)); } else { - ret = get_errno(lremovexattr(p, n)); + ret = get_errno(lremovexattr(p, n)); } } else { ret = -TARGET_EFAULT; From 4afe33976a1fa9ebaa96e6ba7f395a37fc81eabd Mon Sep 17 00:00:00 2001 From: zqz Date: Thu, 13 Aug 2026 11:28:21 +0800 Subject: [PATCH 05/13] LATX, fix: apply runtime prefixes to metadata syscalls Several metadata operations bypassed path(), unlike open and related filesystem syscalls. This let a guest inspect or modify host paths instead of the configured x86 runtime tree. Route faccessat, statx, and extended-attribute path arguments through the existing prefix resolver while preserving the existing guest-buffer lifetime and procfs handling. Signed-off-by: zqz --- linux-user/syscall.c | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/linux-user/syscall.c b/linux-user/syscall.c index e5ecdc4abbe..e424e982717 100644 --- a/linux-user/syscall.c +++ b/linux-user/syscall.c @@ -15334,7 +15334,7 @@ static abi_long do_syscall1(void *cpu_env, int num, abi_long arg1, if (!(p = lock_user_string(arg2))) { return -TARGET_EFAULT; } - ret = get_errno(faccessat(arg1, p, arg3, 0)); + ret = get_errno(faccessat(arg1, path(p), arg3, 0)); unlock_user(p, arg2, 0); return ret; #endif @@ -18554,7 +18554,8 @@ static abi_long do_syscall1(void *cpu_env, int num, abi_long arg1, struct target_statx host_stx; int mask = arg4; - ret = get_errno(sys_statx(dirfd, p, flags, mask, &host_stx)); + ret = get_errno(sys_statx(dirfd, path(p), flags, mask, + &host_stx)); if (!is_error(ret)) { #ifdef CONFIG_LATX if (latx_statx_is_proc_self_task(&host_stx)) { @@ -19395,9 +19396,9 @@ static abi_long do_syscall1(void *cpu_env, int num, abi_long arg1, p = lock_user_string(arg1); if (p) { if (num == TARGET_NR_listxattr) { - ret = get_errno(listxattr(p, b, arg3)); + ret = get_errno(listxattr(path(p), b, arg3)); } else { - ret = get_errno(llistxattr(p, b, arg3)); + ret = get_errno(llistxattr(path(p), b, arg3)); } } else { ret = -TARGET_EFAULT; @@ -19433,9 +19434,9 @@ static abi_long do_syscall1(void *cpu_env, int num, abi_long arg1, n = lock_user_string(arg2); if (p && n) { if (num == TARGET_NR_setxattr) { - ret = get_errno(setxattr(p, n, v, arg4, arg5)); + ret = get_errno(setxattr(path(p), n, v, arg4, arg5)); } else { - ret = get_errno(lsetxattr(p, n, v, arg4, arg5)); + ret = get_errno(lsetxattr(path(p), n, v, arg4, arg5)); } } else { ret = -TARGET_EFAULT; @@ -19478,9 +19479,9 @@ static abi_long do_syscall1(void *cpu_env, int num, abi_long arg1, n = lock_user_string(arg2); if (p && n) { if (num == TARGET_NR_getxattr) { - ret = get_errno(getxattr(p, n, v, arg4)); + ret = get_errno(getxattr(path(p), n, v, arg4)); } else { - ret = get_errno(lgetxattr(p, n, v, arg4)); + ret = get_errno(lgetxattr(path(p), n, v, arg4)); } } else { ret = -TARGET_EFAULT; @@ -19517,9 +19518,9 @@ static abi_long do_syscall1(void *cpu_env, int num, abi_long arg1, n = lock_user_string(arg2); if (p && n) { if (num == TARGET_NR_removexattr) { - ret = get_errno(removexattr(p, n)); + ret = get_errno(removexattr(path(p), n)); } else { - ret = get_errno(lremovexattr(p, n)); + ret = get_errno(lremovexattr(path(p), n)); } } else { ret = -TARGET_EFAULT; From ec27eef5d035c73e63d6767c6f54a3e4e114bd1d Mon Sep 17 00:00:00 2001 From: zqz Date: Thu, 13 Aug 2026 11:28:21 +0800 Subject: [PATCH 06/13] LATX, fix: report x86_64 to i386 launchers Steam-era 32-bit launchers select helper binaries based on uname.machine. Reporting the host architecture prevents them from choosing their x86_64 helper even though LATX can execute it. For the LATX i386 target, report x86_64 by default and retain LATX_UNAME_MACHINE as an explicit override for callers that need a different compatibility identity. Signed-off-by: zqz --- linux-user/uname.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/linux-user/uname.c b/linux-user/uname.c index a09ffe1ea7b..4a18a5898a8 100644 --- a/linux-user/uname.c +++ b/linux-user/uname.c @@ -30,6 +30,18 @@ * return here */ const char *cpu_to_uname_machine(void *cpu_env) { +#if defined(CONFIG_LATX) && defined(TARGET_I386) + const char *override = getenv("LATX_UNAME_MACHINE"); + + if (override && override[0]) { + return override; + } +#if !defined(TARGET_X86_64) + /* Let 32-bit launchers select their matching x86_64 helpers. */ + return "x86_64"; +#endif +#endif + #if defined(TARGET_ARM) && !defined(TARGET_AARCH64) /* utsname machine name on linux arm is CPU arch name + endianness, e.g. From d3e4ba008bf45c008b8bd9525d45bff8a2b9c821 Mon Sep 17 00:00:00 2001 From: zqz Date: Thu, 13 Aug 2026 11:29:37 +0800 Subject: [PATCH 07/13] LATX, feat: support Steam pressure-vessel payloads Steam launches x86 payloads through its native pressure-vessel wrapper. That container does not inherit the host binfmt registration, so attempting to execute the wrapper path directly leaves the payload outside LATX. Move pressure-vessel handling out of main.c into a dedicated linux-user module. For recognized non-launcher wrap invocations, parse the supported environment and preload options, build runtime library and data paths, and exec the payload through LATX. Keep steamwebhelper_sniper_wrap.sh on its original host environment and fall back to normal wrapper execution when the runtime is incomplete. Add an integration test through the existing top-level tests/integration/meson.build collection. It uses a temporary runtime and static x86_64 wrapper to cover payload environment propagation, preload precedence, launcher and missing-runtime fallback, WebHelper handling, and both LATX_KZT=0 and LATX_KZT=1. Signed-off-by: zqz --- linux-user/main.c | 20 ++ linux-user/meson.build | 1 + linux-user/pressure-vessel.c | 215 ++++++++++++++++++ linux-user/pressure-vessel.h | 17 ++ tests/integration/meson.build | 14 ++ tests/integration/pressure-vessel-payload.sh | 7 + tests/integration/pressure-vessel-wrap.S | 12 + .../integration/steamwebhelper_sniper_wrap.sh | 4 + .../integration/test-pressure-vessel-wrap.sh | 94 ++++++++ 9 files changed, 384 insertions(+) create mode 100644 linux-user/pressure-vessel.c create mode 100644 linux-user/pressure-vessel.h create mode 100755 tests/integration/pressure-vessel-payload.sh create mode 100644 tests/integration/pressure-vessel-wrap.S create mode 100755 tests/integration/steamwebhelper_sniper_wrap.sh create mode 100755 tests/integration/test-pressure-vessel-wrap.sh diff --git a/linux-user/main.c b/linux-user/main.c index 47281e85906..16171a1813f 100644 --- a/linux-user/main.c +++ b/linux-user/main.c @@ -51,6 +51,9 @@ #include "target_elf.h" #include "cpu_loop-common.h" #include "crypto/init.h" +#if defined(CONFIG_LATX) && defined(TARGET_X86_64) +#include "pressure-vessel.h" +#endif int mydebug = 1; #ifdef CONFIG_LATX @@ -1287,6 +1290,9 @@ int main(int argc, char **argv, char **envp) unsigned int inherited_guest_mdwe = 0; bool inherited_guest_tsc_disabled = false; #endif +#if defined(CONFIG_LATX) && defined(TARGET_X86_64) + char **pressure_vessel_payload; +#endif #if defined(CONFIG_LATX) && defined(__loongarch__) /* Lets check hwcap */ @@ -1448,6 +1454,11 @@ int main(int argc, char **argv, char **envp) /* Scan interp_prefix dir for replacement files. */ init_paths(interp_prefix); +#if defined(CONFIG_LATX) && defined(TARGET_X86_64) + pressure_vessel_payload = latx_pressure_vessel_prepare(exec_path, + target_argv, envlist); +#endif + init_qemu_uname_release(); /* @@ -1546,6 +1557,15 @@ int main(int argc, char **argv, char **envp) target_environ = envlist_to_environ(envlist, NULL); envlist_free(envlist); +#if defined(CONFIG_LATX) && defined(TARGET_X86_64) + if (pressure_vessel_payload) { + execve(pressure_vessel_payload[0], pressure_vessel_payload, + target_environ); + error_report("cannot launch Steam pressure-vessel payload %s: %s", + pressure_vessel_payload[0], strerror(errno)); + } +#endif + #define KERNEL_CONFIG_LSM_MMAP_MIN_ADDR 65536 /* * Read in mmap_min_addr kernel parameter. This value is used diff --git a/linux-user/meson.build b/linux-user/meson.build index 16f21d1af88..09d8ceed19c 100644 --- a/linux-user/meson.build +++ b/linux-user/meson.build @@ -7,6 +7,7 @@ linux_user_ss.add(files( 'linuxload.c', 'main.c', 'mmap.c', + 'pressure-vessel.c', 'safe-syscall.S', 'signal.c', 'strace.c', diff --git a/linux-user/pressure-vessel.c b/linux-user/pressure-vessel.c new file mode 100644 index 00000000000..d48fce7451a --- /dev/null +++ b/linux-user/pressure-vessel.c @@ -0,0 +1,215 @@ +/* + * Steam pressure-vessel compatibility helpers. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/envlist.h" +#include "pressure-vessel.h" + +#if defined(CONFIG_LATX) && defined(TARGET_X86_64) + +static bool latx_steam_webhelper(const char *pathname) +{ + const char *basename = strrchr(pathname, '/'); + + basename = basename ? basename + 1 : pathname; + return !strcmp(basename, "steamwebhelper_sniper_wrap.sh"); +} + +static char *latx_pressure_vessel_runtime_files(void) +{ + const char *runtime_base = getenv("PRESSURE_VESSEL_RUNTIME_BASE"); + const char *runtime = getenv("PRESSURE_VESSEL_RUNTIME"); + char *runtime_files; + + if (!runtime_base || runtime_base[0] != '/' || !runtime || !runtime[0] + || strchr(runtime, '/')) { + return NULL; + } + + runtime_files = g_build_filename(runtime_base, runtime, "files", NULL); + if (!g_file_test(runtime_files, G_FILE_TEST_IS_DIR)) { + g_free(runtime_files); + return NULL; + } + + return runtime_files; +} + +static void latx_pressure_vessel_runtime_paths(envlist_t *envlist, + const char *runtime_files, + const char *app_ld_path, + const char *old_xdg_data_dirs) +{ + static const char *const library_dirs[] = { + "lib/x86_64-linux-gnu", + "lib/i386-linux-gnu", + "lib", + "lib64", + "lib32", + }; + GString *library_path; + char *assignment; + size_t i; + + library_path = g_string_new(NULL); + for (i = 0; i < G_N_ELEMENTS(library_dirs); i++) { + char *directory = g_build_filename(runtime_files, library_dirs[i], + NULL); + + if (g_file_test(directory, G_FILE_TEST_IS_DIR)) { + if (library_path->len) { + g_string_append_c(library_path, ':'); + } + g_string_append(library_path, directory); + } + g_free(directory); + } + if (app_ld_path && app_ld_path[0]) { + if (library_path->len) { + g_string_append_c(library_path, ':'); + } + g_string_append(library_path, app_ld_path); + } + if (library_path->len) { + assignment = g_strdup_printf("LD_LIBRARY_PATH=%s", library_path->str); + (void)envlist_setenv(envlist, assignment); + g_free(assignment); + } + g_string_free(library_path, true); + + assignment = g_strdup_printf("XDG_DATA_DIRS=%s/share%s%s", runtime_files, + old_xdg_data_dirs && old_xdg_data_dirs[0] + ? ":" : "", + old_xdg_data_dirs ? old_xdg_data_dirs : ""); + (void)envlist_setenv(envlist, assignment); + g_free(assignment); +} + +char **latx_pressure_vessel_prepare(const char *program, char **target_argv, + envlist_t *envlist) +{ + static const char env_if_host_prefix[] = "--env-if-host="; + static const char app_ld_prefix[] = + "PRESSURE_VESSEL_APP_LD_LIBRARY_PATH="; + static const char xdg_data_dirs_prefix[] = "XDG_DATA_DIRS="; + static const char ld_preload_prefix[] = "LD_PRELOAD="; + static const char ld_preload_option[] = "--ld-preload="; + static const char ld_preloads_prefix[] = "--ld-preloads="; + const char *basename = strrchr(program, '/'); + const char *app_ld_path = NULL; + const char *host_ld_preload = NULL; + const char *old_xdg_data_dirs = getenv("XDG_DATA_DIRS"); + char *runtime_files = NULL; + GPtrArray *environment; + GString *preloads = NULL; + char **arg; + char **payload = NULL; + bool steam_webhelper; + bool launcher = false; + + basename = basename ? basename + 1 : program; + if (strcmp(basename, "pressure-vessel-wrap")) { + return NULL; + } + + environment = g_ptr_array_new_with_free_func(g_free); + for (arg = target_argv + 1; *arg && strcmp(*arg, "--"); arg++) { + const char *assignment = NULL; + const char *preload = NULL; + + if (!strcmp(*arg, "--env-if-host")) { + if (!arg[1]) { + goto out; + } + assignment = *++arg; + } else if (g_str_has_prefix(*arg, env_if_host_prefix)) { + assignment = *arg + strlen(env_if_host_prefix); + } else if (!strcmp(*arg, "--launcher") || + g_str_has_prefix(*arg, "--launcher=")) { + launcher = true; + } else if (!strcmp(*arg, "--ld-preload") || + !strcmp(*arg, "--ld-preloads")) { + if (!arg[1] || !strcmp(arg[1], "--")) { + goto out; + } + preload = *++arg; + } else if (g_str_has_prefix(*arg, ld_preload_option)) { + preload = *arg + strlen(ld_preload_option); + } else if (g_str_has_prefix(*arg, ld_preloads_prefix)) { + preload = *arg + strlen(ld_preloads_prefix); + } + + if (preload && preload[0]) { + if (!preloads) { + preloads = g_string_new(NULL); + } + if (preloads->len) { + g_string_append_c(preloads, ':'); + } + g_string_append(preloads, preload); + } + + if (assignment) { + if (!assignment[0] || !strchr(assignment, '=')) { + goto out; + } + if (g_str_has_prefix(assignment, app_ld_prefix)) { + app_ld_path = assignment + strlen(app_ld_prefix); + } else if (g_str_has_prefix(assignment, ld_preload_prefix)) { + host_ld_preload = assignment + strlen(ld_preload_prefix); + } else { + if (g_str_has_prefix(assignment, xdg_data_dirs_prefix)) { + old_xdg_data_dirs = assignment + + strlen(xdg_data_dirs_prefix); + } + g_ptr_array_add(environment, g_strdup(assignment)); + } + } + } + if (!*arg || !arg[1] || launcher) { + goto out; + } + + steam_webhelper = latx_steam_webhelper(arg[1]); + runtime_files = latx_pressure_vessel_runtime_files(); + if (!runtime_files && !steam_webhelper) { + goto out; + } + + /* The WebHelper wrapper is a host script and retains its original env. */ + if (!steam_webhelper) { + for (size_t i = 0; i < environment->len; i++) { + (void)envlist_setenv(envlist, g_ptr_array_index(environment, i)); + } + if (host_ld_preload) { + char *assignment = g_strdup_printf("LD_PRELOAD=%s", + host_ld_preload); + + (void)envlist_setenv(envlist, assignment); + g_free(assignment); + } else if (preloads && preloads->len) { + char *assignment = g_strdup_printf("LD_PRELOAD=%s", preloads->str); + + (void)envlist_setenv(envlist, assignment); + g_free(assignment); + } + } + if (runtime_files) { + latx_pressure_vessel_runtime_paths(envlist, runtime_files, app_ld_path, + old_xdg_data_dirs); + } + payload = arg + 1; + +out: + g_free(runtime_files); + if (preloads) { + g_string_free(preloads, true); + } + g_ptr_array_free(environment, true); + return payload; +} + +#endif diff --git a/linux-user/pressure-vessel.h b/linux-user/pressure-vessel.h new file mode 100644 index 00000000000..052df1deb87 --- /dev/null +++ b/linux-user/pressure-vessel.h @@ -0,0 +1,17 @@ +/* + * Steam pressure-vessel compatibility helpers. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef LINUX_USER_PRESSURE_VESSEL_H +#define LINUX_USER_PRESSURE_VESSEL_H + +#include "qemu/envlist.h" + +#if defined(CONFIG_LATX) && defined(TARGET_X86_64) +char **latx_pressure_vessel_prepare(const char *program, char **target_argv, + envlist_t *envlist); +#endif + +#endif diff --git a/tests/integration/meson.build b/tests/integration/meson.build index be475972688..7003f2966b8 100644 --- a/tests/integration/meson.build +++ b/tests/integration/meson.build @@ -10,4 +10,18 @@ if host_machine.cpu_family() == 'loongarch64' subdir('registrations/seccomp') endif +if host_machine.cpu_family() == 'loongarch64' and \ + 'x86_64-linux-user' in target_dirs + latx_integration_tests += [{ + 'name': 'test-pressure-vessel-wrap', + 'runner': find_program('test-pressure-vessel-wrap.sh'), + 'args': [ + emulators['latx-x86_64'], + files('pressure-vessel-wrap.S'), + files('pressure-vessel-payload.sh'), + files('steamwebhelper_sniper_wrap.sh'), + ], + }] +endif + subdir('registrations') diff --git a/tests/integration/pressure-vessel-payload.sh b/tests/integration/pressure-vessel-payload.sh new file mode 100755 index 00000000000..8d7156855ca --- /dev/null +++ b/tests/integration/pressure-vessel-payload.sh @@ -0,0 +1,7 @@ +#!/bin/sh +set -eu + +printf 'LD_LIBRARY_PATH=%s\n' "${LD_LIBRARY_PATH-}" +printf 'PATH=%s\n' "${PATH-}" +printf 'XDG_DATA_DIRS=%s\n' "${XDG_DATA_DIRS-}" +printf 'LD_PRELOAD=%s\n' "${LD_PRELOAD-}" diff --git a/tests/integration/pressure-vessel-wrap.S b/tests/integration/pressure-vessel-wrap.S new file mode 100644 index 00000000000..164c696524a --- /dev/null +++ b/tests/integration/pressure-vessel-wrap.S @@ -0,0 +1,12 @@ +.equ __NR_exit, 60 + +.section .text +.global _start +.type _start, @function +_start: + mov $91, %edi + mov $__NR_exit, %eax + syscall +.size _start, .-_start + +.section .note.GNU-stack,"",@progbits diff --git a/tests/integration/steamwebhelper_sniper_wrap.sh b/tests/integration/steamwebhelper_sniper_wrap.sh new file mode 100755 index 00000000000..300bc718b4a --- /dev/null +++ b/tests/integration/steamwebhelper_sniper_wrap.sh @@ -0,0 +1,4 @@ +#!/bin/sh +set -eu + +test "${PATH-}" = /webhelper-original diff --git a/tests/integration/test-pressure-vessel-wrap.sh b/tests/integration/test-pressure-vessel-wrap.sh new file mode 100755 index 00000000000..729ca805f5e --- /dev/null +++ b/tests/integration/test-pressure-vessel-wrap.sh @@ -0,0 +1,94 @@ +#!/bin/sh +set -eu + +emulator=$1 +source_file=$2 +payload=$3 +webhelper=$4 +workdir=$(mktemp -d) +trap 'rm -rf "$workdir"' EXIT HUP INT TERM + +if command -v clang-19 >/dev/null 2>&1; then + clang=clang-19 +elif command -v clang >/dev/null 2>&1; then + clang=clang +else + echo "SKIP: clang is required to build the x86_64 guest" + exit 77 +fi + +if ! "$clang" --target=x86_64-linux-gnu -fuse-ld=lld -nostdlib -static \ + -Wl,--build-id=none "$source_file" -o "$workdir/pressure-vessel-wrap"; then + echo "SKIP: clang with an x86_64 target and LLD is required" + exit 77 +fi + +runtime="$workdir/runtime" +app_lib="$workdir/app-lib" +mkdir -p "$runtime/files/lib/x86_64-linux-gnu" "$runtime/files/share" "$app_lib" + +expected_ld="LD_LIBRARY_PATH=$runtime/files/lib/x86_64-linux-gnu:$runtime/files/lib:$app_lib" +expected_path='PATH=/test/path' +expected_xdg="XDG_DATA_DIRS=$runtime/files/share:/test/data" +expected_preload='LD_PRELOAD=/first-preload:/second-preload /third-preload' + +for kzt in 0 1; do + output=$(unset LD_PRELOAD LD_LIBRARY_PATH + LATX_KZT="$kzt" \ + PRESSURE_VESSEL_RUNTIME_BASE="$workdir" \ + PRESSURE_VESSEL_RUNTIME=runtime \ + "$emulator" "$workdir/pressure-vessel-wrap" \ + --env-if-host=PRESSURE_VESSEL_APP_LD_LIBRARY_PATH="$app_lib" \ + --env-if-host PATH=/test/path \ + --env-if-host XDG_DATA_DIRS=/test/data \ + --ld-preload=/first-preload \ + --ld-preloads '/second-preload /third-preload' \ + -- "$payload" 2>/dev/null) + + printf '%s\n' "$output" | grep -Fx "$expected_ld" + printf '%s\n' "$output" | grep -Fx "$expected_path" + printf '%s\n' "$output" | grep -Fx "$expected_xdg" + printf '%s\n' "$output" | grep -Fx "$expected_preload" +done + +output=$(unset LD_PRELOAD LD_LIBRARY_PATH + LATX_KZT=0 \ + PRESSURE_VESSEL_RUNTIME_BASE="$workdir" \ + PRESSURE_VESSEL_RUNTIME=runtime \ + "$emulator" "$workdir/pressure-vessel-wrap" \ + --ld-preload=/first-preload \ + --env-if-host=LD_PRELOAD=/host-preload \ + -- "$payload" 2>/dev/null) +printf '%s\n' "$output" | grep -Fx 'LD_PRELOAD=/host-preload' + +set +e +unset LD_PRELOAD LD_LIBRARY_PATH +LATX_KZT=0 \ + PRESSURE_VESSEL_RUNTIME_BASE="$workdir" \ + PRESSURE_VESSEL_RUNTIME=runtime \ + "$emulator" "$workdir/pressure-vessel-wrap" \ + --launcher -- "$payload" +launcher_status=$? + +unset LD_PRELOAD LD_LIBRARY_PATH +LATX_KZT=0 \ + PRESSURE_VESSEL_RUNTIME_BASE="$workdir/missing" \ + PRESSURE_VESSEL_RUNTIME=runtime \ + "$emulator" "$workdir/pressure-vessel-wrap" \ + -- "$payload" +missing_runtime_status=$? + +unset LD_PRELOAD LD_LIBRARY_PATH +LATX_KZT=0 \ + PATH=/webhelper-original \ + "$emulator" "$workdir/pressure-vessel-wrap" \ + --env-if-host=PATH=/webhelper-replaced \ + -- "$webhelper" +webhelper_status=$? +set -e + +test "$launcher_status" -eq 91 +test "$missing_runtime_status" -eq 91 +test "$webhelper_status" -eq 0 + +echo "PASS: pressure-vessel direct payload and fallback paths" From 72c334be8b560808c3fb9f4af4efb3fce76751a6 Mon Sep 17 00:00:00 2001 From: zqz Date: Thu, 13 Aug 2026 11:29:53 +0800 Subject: [PATCH 08/13] LATX, fix: preserve guest futex compatibility state Guest robust-list registrations cannot be passed to the host kernel because their linked-list pointers live in guest address space. Track the target ABI state per TaskState and answer get_robust_list from the translated task set instead. Private FUTEX_WAIT and FUTEX_WAIT_BITSET can also return a transient EFAULT on LoongArch despite a valid guest word. Retry only those private wait operations, only while the guest page is valid, and cap retries so all other futex failures remain visible to the guest. Signed-off-by: zqz --- linux-user/qemu.h | 2 + linux-user/syscall.c | 108 ++++++++++++++++++++++++++++++++++--------- 2 files changed, 87 insertions(+), 23 deletions(-) diff --git a/linux-user/qemu.h b/linux-user/qemu.h index cba8f748b37..4201f5350e5 100644 --- a/linux-user/qemu.h +++ b/linux-user/qemu.h @@ -183,6 +183,8 @@ typedef struct TaskState { abi_ulong child_tidptr; /* A detached helper must not outlive an isolated IPC namespace. */ bool ipc_namespace_isolated; + abi_ulong robust_list_head; + abi_ulong robust_list_len; /* Immutable seccomp filter chain inherited by guest threads. */ struct GuestSeccompFilter *seccomp_filter; /* A seccomp errno result must not be treated as an internal restart. */ diff --git a/linux-user/syscall.c b/linux-user/syscall.c index e424e982717..f044d8bceb4 100644 --- a/linux-user/syscall.c +++ b/linux-user/syscall.c @@ -11024,6 +11024,36 @@ static int do_safe_futex(int *uaddr, int op, int val, return -TARGET_ENOSYS; } +static inline int do_safe_futex_wait(CPUState *cpu, target_ulong uaddr, + int op, int val, + const struct timespec *timeout, + int val3, int base_op) +{ + int ret = do_safe_futex(g2h(cpu, uaddr), op, val, timeout, NULL, val3); +#if defined(__loongarch__) + unsigned int retry_count = 0; +#endif + +#if defined(__loongarch__) + /* + * A valid private wait can transiently fault on the LoongArch host. + * Keep the workaround bounded and leave all other futex results alone. + */ + while (ret == -TARGET_EFAULT + && retry_count < 8 + && (base_op == FUTEX_WAIT || base_op == FUTEX_WAIT_BITSET) + && (op & FUTEX_PRIVATE_FLAG) + && page_check_range(uaddr, sizeof(uint32_t), PAGE_VALID)) { + retry_count++; + ret = do_safe_futex(g2h(cpu, uaddr), op, val, timeout, NULL, val3); + } +#else + (void)base_op; +#endif + + return ret; +} + /* ??? Using host futex calls even when target atomic operations are not really atomic probably breaks things. However implementing futexes locally would make futexes shared between multiple processes @@ -11052,9 +11082,9 @@ static int do_futex(CPUState *cpu, target_ulong uaddr, int op, int val, } else { pts = NULL; } - return do_safe_futex(g2h(cpu, uaddr), - op, tswap32(val), pts, NULL, val3); -#ifdef TARGET_X86_64 + return do_safe_futex_wait(cpu, uaddr, op, tswap32(val), pts, val3, + base_op); +#if defined(TARGET_X86_64) || defined(TARGET_I386) case FUTEX_LOCK_PI: if (timeout) { pts = &ts; @@ -11069,7 +11099,7 @@ static int do_futex(CPUState *cpu, target_ulong uaddr, int op, int val, case FUTEX_WAKE: return do_safe_futex(g2h(cpu, uaddr), op, val, NULL, NULL, val3); -#ifdef TARGET_X86_64 +#if defined(TARGET_X86_64) || defined(TARGET_I386) case FUTEX_TRYLOCK_PI: case FUTEX_UNLOCK_PI: return do_safe_futex(g2h(cpu, uaddr), @@ -11122,9 +11152,9 @@ static int do_futex_time64(CPUState *cpu, target_ulong uaddr, int op, } else { pts = NULL; } - return do_safe_futex(g2h(cpu, uaddr), op, - tswap32(val), pts, NULL, val3); -#ifdef TARGET_X86_64 + return do_safe_futex_wait(cpu, uaddr, op, tswap32(val), pts, val3, + base_op); +#if defined(TARGET_X86_64) || defined(TARGET_I386) case FUTEX_LOCK_PI: if (timeout) { pts = &ts; @@ -11138,7 +11168,7 @@ static int do_futex_time64(CPUState *cpu, target_ulong uaddr, int op, case FUTEX_WAKE_BITSET: case FUTEX_WAKE: return do_safe_futex(g2h(cpu, uaddr), op, val, NULL, NULL, 0); -#ifdef TARGET_X86_64 +#if defined(TARGET_X86_64) || defined(TARGET_I386) case FUTEX_TRYLOCK_PI: case FUTEX_UNLOCK_PI: return do_safe_futex(g2h(cpu, uaddr), @@ -19726,22 +19756,54 @@ static abi_long do_syscall1(void *cpu_env, int num, abi_long arg1, #ifdef TARGET_NR_set_robust_list case TARGET_NR_set_robust_list: - return get_errno(syscall(__NR_set_robust_list, arg1, arg2)); + { + TaskState *ts = cpu->opaque; + + if (arg2 != 3 * sizeof(abi_ulong)) { + return -TARGET_EINVAL; + } + + /* The target list layout cannot be registered with host Linux. */ + ts->robust_list_head = arg1; + ts->robust_list_len = arg2; + return 0; + } case TARGET_NR_get_robust_list: - /* The ABI for supporting robust futexes has userspace pass - * the kernel a pointer to a linked list which is updated by - * userspace after the syscall; the list is walked by the kernel - * when the thread exits. Since the linked list in QEMU guest - * memory isn't a valid linked list for the host and we have - * no way to reliably intercept the thread-death event, we can't - * support these. Silently return ENOSYS so that guest userspace - * falls back to a non-robust futex implementation (which should - * be OK except in the corner case of the guest crashing while - * holding a mutex that is shared with another process via - * shared memory). - */ - return get_errno(syscall(__NR_get_robust_list, arg1, g2h_untagged(arg2), - g2h_untagged(arg3))); + { + TaskState *current_ts = cpu->opaque; + abi_ulong head = 0; + abi_ulong len = 0; + bool found = false; + + if (arg1 == 0 || arg1 == current_ts->ts_tid) { + head = current_ts->robust_list_head; + len = current_ts->robust_list_len; + found = true; + } else { + CPUState *cpu_iter; + + cpu_list_lock(); + CPU_FOREACH(cpu_iter) { + TaskState *ts = cpu_iter->opaque; + + if (ts && ts->ts_tid == arg1) { + head = ts->robust_list_head; + len = ts->robust_list_len; + found = true; + break; + } + } + cpu_list_unlock(); + } + + if (!found) { + return -TARGET_ESRCH; + } + if (put_user_ual(head, arg2) || put_user_ual(len, arg3)) { + return -TARGET_EFAULT; + } + return 0; + } #endif #if defined(TARGET_NR_utimensat) From 6829f1987020ee1cd1bdb2535a2f9a00cd59630f Mon Sep 17 00:00:00 2001 From: zqz Date: Thu, 13 Aug 2026 11:30:13 +0800 Subject: [PATCH 09/13] LATX, fix: preserve queued target SIGBUS delivery A SIGBUS with SI_KERNEL and a non-null address can already be queued for target handling. Sending it through the host fault interpreter consumes the target signal path and can turn a recoverable guest-visible event into translator failure. Leave that signal queued by releasing the mmap lock and returning to the normal target signal delivery path. Other SIGBUS faults retain the existing host interpretation flow. Signed-off-by: zqz --- accel/tcg/user-exec.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/accel/tcg/user-exec.c b/accel/tcg/user-exec.c index a2c25bc2a76..7f1c7f2f580 100644 --- a/accel/tcg/user-exec.c +++ b/accel/tcg/user-exec.c @@ -1349,6 +1349,11 @@ int cpu_signal_handler(int host_signum, void *pinfo, } } if (info->si_signo == SIGBUS) { + /* Keep a target-queued SIGBUS on the target signal path. */ + if (info->si_code == SI_KERNEL && info->si_addr != NULL) { + mmap_unlock(); + return 0; + } int ret = lock_interpret(info, uc); if (!ret) { mmap_unlock(); From 2078c0dc36dccbf20cdc956bfdb24baa9b4bedc9 Mon Sep 17 00:00:00 2001 From: zqz Date: Thu, 13 Aug 2026 11:30:13 +0800 Subject: [PATCH 10/13] LATX, fix: isolate AOT caches by translation mode AOT cache validation used substring matching for a fixed footer, allowing unrelated trailing data to be accepted. Cache filenames also did not distinguish target ABI or whether KZT translation was enabled. Compare the fixed-width version footer exactly and version cache keys with the target name, cache ABI, and KZT mode. Existing incompatible cache files are rejected instead of being reused across translation modes. Signed-off-by: zqz --- target/i386/latx/include/aot.h | 4 ++-- target/i386/latx/include/segment.h | 2 ++ target/i386/latx/sbt/aot.c | 4 ++-- target/i386/latx/sbt/segment.c | 21 +++++++++++++++++---- 4 files changed, 23 insertions(+), 8 deletions(-) diff --git a/target/i386/latx/include/aot.h b/target/i386/latx/include/aot.h index f72613a814d..81c9dbe6b6d 100644 --- a/target/i386/latx/include/aot.h +++ b/target/i386/latx/include/aot.h @@ -39,9 +39,9 @@ extern const char *aot_left_file_minsize_optarg; * +--------------+ */ #ifdef CONFIG_LATX_DEBUG -#define AOT_VERSION "Version: "LATX_VERSION"-debug" +#define AOT_VERSION "Version: "LATX_VERSION"-"AOT_CACHE_KEY_VERSION"-debug" #else -#define AOT_VERSION "Version: "LATX_VERSION"-release" +#define AOT_VERSION "Version: "LATX_VERSION"-"AOT_CACHE_KEY_VERSION"-release" #endif typedef struct aot_header { uint32_t lib_size; diff --git a/target/i386/latx/include/segment.h b/target/i386/latx/include/segment.h index 93a5e14822f..35705637406 100644 --- a/target/i386/latx/include/segment.h +++ b/target/i386/latx/include/segment.h @@ -9,6 +9,8 @@ #include "qemu-def.h" +#define AOT_CACHE_KEY_VERSION "v3" + typedef struct seg_info { char *file_name; target_ulong file_offset; diff --git a/target/i386/latx/sbt/aot.c b/target/i386/latx/sbt/aot.c index 91d6c19e14d..1554b02161a 100644 --- a/target/i386/latx/sbt/aot.c +++ b/target/i386/latx/sbt/aot.c @@ -914,7 +914,7 @@ static int get_tb_num(char *lib_name, char *aot_file_name, CPUState *cpu) fclose(pf); return 0; } - if (!strstr(aot_version, AOT_VERSION)) { + if (memcmp(aot_version, AOT_VERSION, strlen(AOT_VERSION))) { qemu_log_mask(LAT_LOG_AOT, "aot file is not complete %s\n", lib_name); remove(aot_file_path); return 0; @@ -1321,7 +1321,7 @@ lib_info *aot_load(char *lib_name, char *aot_file_name, || fread(aot_version, strlen(AOT_VERSION), 1, pf) != 1) { goto exit_aot_load; } - if (!strstr(aot_version, AOT_VERSION)) { + if (memcmp(aot_version, AOT_VERSION, strlen(AOT_VERSION))) { qemu_log_mask(LAT_LOG_AOT, "aot file is not complete %s\n", lib_name); remove_curr_aot_file(fd); goto exit_aot_load; diff --git a/target/i386/latx/sbt/segment.c b/target/i386/latx/sbt/segment.c index 061a7592d16..b8c60bf44d0 100644 --- a/target/i386/latx/sbt/segment.c +++ b/target/i386/latx/sbt/segment.c @@ -17,6 +17,15 @@ static GTree *segment_tree; static GTree *wine_sec_tree; +static int aot_cache_kzt_enabled(void) +{ +#if defined(CONFIG_LATX_KZT) + return !!option_kzt; +#else + return 0; +#endif +} + int segment_get_aot_file_name(const seg_info *seg, char *name, size_t name_size) { @@ -39,13 +48,17 @@ int segment_get_aot_file_name(const seg_info *seg, char *name, profile_suffix = aot_process_profile; } if (seg->aot_file_type & (PE_AOT_FILE | CACHE_AOT_FILE)) { - len = snprintf(name, name_size, "v2-%02x-%s-%" PRIx64 "%s%s", - seg->aot_file_type, path_hash, + len = snprintf(name, name_size, + "v2-%02x-" AOT_CACHE_KEY_VERSION "-%s-kzt%d-%s-%" PRIx64 "%s%s", + seg->aot_file_type, TARGET_NAME, + aot_cache_kzt_enabled(), path_hash, (uint64_t)seg->seg_begin, profile_suffix[0] ? "-" : "", profile_suffix); } else { - len = snprintf(name, name_size, "v2-%02x-%s", - seg->aot_file_type, path_hash); + len = snprintf(name, name_size, + "v2-%02x-" AOT_CACHE_KEY_VERSION "-%s-kzt%d-%s", + seg->aot_file_type, TARGET_NAME, + aot_cache_kzt_enabled(), path_hash); } g_free(path_hash); if (len < 0 || (size_t)len >= name_size) { From fe00c141f910e75733241597e29d60dd4b620dd9 Mon Sep 17 00:00:00 2001 From: zqz Date: Thu, 13 Aug 2026 11:30:32 +0800 Subject: [PATCH 11/13] LATX, KZT: raise INT3 with the guest return RIP KZT ends a translation block at INT3. A raw host BREAK cannot rely on the following host instruction to restore the guest program counter, so an x86 breakpoint can re-enter the same translated BREAK instead of advancing. Save the required guest state, record the next guest RIP explicitly, and invoke the target interrupt helper for normal KZT INT3 instructions. The existing SC bridge marker continues to take its dedicated bridge path. Signed-off-by: zqz --- target/i386/latx/translator/tr-misc.c | 61 ++++++++++++++++++++++++--- 1 file changed, 54 insertions(+), 7 deletions(-) diff --git a/target/i386/latx/translator/tr-misc.c b/target/i386/latx/translator/tr-misc.c index e2e6d086c24..eede117a950 100644 --- a/target/i386/latx/translator/tr-misc.c +++ b/target/i386/latx/translator/tr-misc.c @@ -246,29 +246,76 @@ void do_translate_tbbridge(ADDR func_pc, ADDR wrapper, TranslationBlock *tb) } #endif +#if defined(CONFIG_LATX_KZT) +/* + * KZT ends a TB at INT3. A host BREAK cannot then use the following host + * instruction to restore the guest RIP, so raise the target interrupt with + * the return RIP recorded explicitly. + */ +static bool translate_kzt_int3_signal(IR1_INST *pir1) +{ +#ifdef TARGET_X86_64 + IR2_OPND codemode_value_opnd = ra_alloc_itemp(); + IR2_OPND not_64 = ra_alloc_label(); + + la_ld_d(codemode_value_opnd, env_ir2_opnd, + offsetof(CPUX86State, sys.codemode)); + la_beq(codemode_value_opnd, zero_ir2_opnd, not_64); + ra_free_temp(codemode_value_opnd); + tr_save_x64_8_registers_to_env(0xff, option_save_xmm); + la_label(not_64); +#endif + tr_save_fcsr_to_env(); + tr_save_registers_to_env(0xff, 0xff, 0xff, options_to_save()); + + IR2_OPND intno = ra_alloc_itemp(); + li_guest_addr(intno, EXCP03_INT3); + la_st_w(intno, env_ir2_opnd, lsenv_offset_exception_index(lsenv)); + ra_free_temp(intno); + + IR2_OPND next_pc = ra_alloc_itemp(); + target_ulong call_offset __attribute__((unused)) = + aot_get_call_offset(ir1_addr_next(pir1)); + aot_load_guest_addr(next_pc, ir1_addr_next(pir1), + LOAD_CALL_TARGET, call_offset); + la_store_addrx(next_pc, env_ir2_opnd, + lsenv_offset_exception_next_eip(lsenv)); + ra_free_temp(next_pc); + + IR2_OPND helper_addr_opnd = ra_alloc_dbt_arg2(); + aot_load_host_addr(helper_addr_opnd, (ADDR)helper_raise_int, + LOAD_HELPER_RAISE_INT, 0); + la_jirl(ra_ir2_opnd, helper_addr_opnd, 0); + tr_load_registers_from_env(0x1, 0, 0, options_to_save()); + return true; +} +#endif + bool translate_int_3(IR1_INST *pir1) { #if defined(CONFIG_LATX_KZT) - if (CODEIS64) { + if (option_kzt && CODEIS64) { struct cpu_state_info state_info; CPUState *cpu = env_cpu(lsenv->cpu_state); /* fill in cpu_state_info */ state_info.cflags = cpu->tcg_cflags; cpu_get_tb_cpu_state(cpu->env_ptr, &state_info.current_pc, &state_info.cs_base, &state_info.flags); - if(option_kzt && Peek8(state_info.current_pc + 1, 0) == 'S' && Peek8(state_info.current_pc + 1, 1) == 'C') - { + if (Peek8(state_info.current_pc + 1, 0) == 'S' && + Peek8(state_info.current_pc + 1, 1) == 'C') { TranslationBlock *tb = NULL; mmap_lock(); onebridge_t *bridge= (onebridge_t*)state_info.current_pc; do_translate_brick_tb(bridge, &state_info, cpu, state_info.current_pc, tb); mmap_unlock(); - } else { - la_break(0x5); + return true; } - } else { - la_break(0x5); } + + if (option_kzt) { + return translate_kzt_int3_signal(pir1); + } + la_break(0x5); return true; #else la_break(0x5); From 5d98afb50e2af28a5f3792e35c21bbcacc129d28 Mon Sep 17 00:00:00 2001 From: zqz Date: Thu, 13 Aug 2026 11:30:32 +0800 Subject: [PATCH 12/13] LATX, KZT: resolve pthread cancellation callback correctly The KZT loader observer records pthread_setcanceltype from libc so the callback bridge can dispatch the guest implementation when cancellation state changes. The original symbol scan compared against malloc, which stored the wrong address and left the cancellation callback unresolved. Match pthread_setcanceltype explicitly while retaining the separate malloc/free/realloc collector. Signed-off-by: zqz --- target/i386/latx/context/myalign.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/target/i386/latx/context/myalign.c b/target/i386/latx/context/myalign.c index 174b25b2b57..40fd8c70e45 100644 --- a/target/i386/latx/context/myalign.c +++ b/target/i386/latx/context/myalign.c @@ -2210,7 +2210,7 @@ static int findx86pthread_setcanceltype(elfheader_t* h) sym = h->DynSym+i; if (h->DynSym[i].st_shndx != SHN_UNDEF && sym->st_value) { const char * symname = h->DynStr+sym->st_name; - if (!strcmp(symname, "malloc")) { + if (!strcmp(symname, "pthread_setcanceltype")) { x86pthread_setcanceltype = (void*)sym->st_value+h->delta; printf_log(LOG_DEBUG, "latx x86pthread_setcanceltype=%p type=0x%x from %s\n", x86pthread_setcanceltype, ELF64_ST_TYPE(sym->st_info), h->path); return 0; From cbd65a16960e3f38a474c147e8d2970e2b7b2f72 Mon Sep 17 00:00:00 2001 From: zqz Date: Thu, 13 Aug 2026 11:30:52 +0800 Subject: [PATCH 13/13] linux-user: serialize fd-transform table access Guest threads can register, duplicate, close, and query descriptor transforms concurrently. The old resizable global table was accessed without synchronization, so a reader could race a reallocation or observe an entry while it was being replaced. Move table operations behind a mutex, copy callback pointers while locked before invoking them, and synchronize the lock across fork so both parent and child preserve a valid descriptor-transform state. Signed-off-by: zqz --- linux-user/fd-trans.c | 117 ++++++++++++++++++++++++++++++++++++++++++ linux-user/fd-trans.h | 59 +++------------------ linux-user/main.c | 2 + linux-user/qemu.h | 2 + linux-user/syscall.c | 49 ++++++++++-------- 5 files changed, 156 insertions(+), 73 deletions(-) diff --git a/linux-user/fd-trans.c b/linux-user/fd-trans.c index e9813f86ae1..a437375b619 100644 --- a/linux-user/fd-trans.c +++ b/linux-user/fd-trans.c @@ -297,6 +297,123 @@ enum { TargetFdTrans **target_fd_trans; unsigned int target_fd_max; +/* Guest threads share and can concurrently grow this descriptor table. */ +static pthread_mutex_t target_fd_trans_mutex = PTHREAD_MUTEX_INITIALIZER; + +static TargetFdTrans *fd_trans_lookup_locked(int fd) +{ + if (fd >= 0 && fd < target_fd_max) { + return target_fd_trans[fd]; + } + return NULL; +} + +static void fd_trans_register_locked(int fd, TargetFdTrans *trans) +{ + if (fd >= target_fd_max) { + unsigned int oldmax = target_fd_max; + unsigned int newmax = ((fd >> 6) + 1) << 6; + TargetFdTrans **new_trans; + + new_trans = g_renew(TargetFdTrans *, target_fd_trans, newmax); + memset(new_trans + oldmax, 0, + (newmax - oldmax) * sizeof(*new_trans)); + target_fd_trans = new_trans; + target_fd_max = newmax; + } + target_fd_trans[fd] = trans; +} + +TargetFdDataFunc fd_trans_target_to_host_data(int fd) +{ + TargetFdTrans *trans; + TargetFdDataFunc func = NULL; + + pthread_mutex_lock(&target_fd_trans_mutex); + trans = fd_trans_lookup_locked(fd); + if (trans) { + func = trans->target_to_host_data; + } + pthread_mutex_unlock(&target_fd_trans_mutex); + return func; +} + +TargetFdDataFunc fd_trans_host_to_target_data(int fd) +{ + TargetFdTrans *trans; + TargetFdDataFunc func = NULL; + + pthread_mutex_lock(&target_fd_trans_mutex); + trans = fd_trans_lookup_locked(fd); + if (trans) { + func = trans->host_to_target_data; + } + pthread_mutex_unlock(&target_fd_trans_mutex); + return func; +} + +TargetFdAddrFunc fd_trans_target_to_host_addr(int fd) +{ + TargetFdTrans *trans; + TargetFdAddrFunc func = NULL; + + pthread_mutex_lock(&target_fd_trans_mutex); + trans = fd_trans_lookup_locked(fd); + if (trans) { + func = trans->target_to_host_addr; + } + pthread_mutex_unlock(&target_fd_trans_mutex); + return func; +} + +void fd_trans_register(int fd, TargetFdTrans *trans) +{ + pthread_mutex_lock(&target_fd_trans_mutex); + fd_trans_register_locked(fd, trans); + pthread_mutex_unlock(&target_fd_trans_mutex); +} + +void fd_trans_unregister(int fd) +{ + pthread_mutex_lock(&target_fd_trans_mutex); + if (fd >= 0 && fd < target_fd_max) { + target_fd_trans[fd] = NULL; + } + pthread_mutex_unlock(&target_fd_trans_mutex); +} + +void fd_trans_dup(int oldfd, int newfd) +{ + TargetFdTrans *trans; + + if (oldfd == newfd) { + return; + } + + pthread_mutex_lock(&target_fd_trans_mutex); + trans = fd_trans_lookup_locked(oldfd); + if (newfd >= 0 && newfd < target_fd_max) { + target_fd_trans[newfd] = NULL; + } + if (trans) { + fd_trans_register_locked(newfd, trans); + } + pthread_mutex_unlock(&target_fd_trans_mutex); +} + +void fd_trans_fork_start(void) +{ + pthread_mutex_lock(&target_fd_trans_mutex); +} + +void fd_trans_fork_end(int child) +{ + if (child) { + pthread_mutex_init(&target_fd_trans_mutex, NULL); + } else { + pthread_mutex_unlock(&target_fd_trans_mutex); + } +} static void tswap_nlmsghdr(struct nlmsghdr *nlh) { diff --git a/linux-user/fd-trans.h b/linux-user/fd-trans.h index 0c80c8a7782..13f0a16811b 100644 --- a/linux-user/fd-trans.h +++ b/linux-user/fd-trans.h @@ -28,59 +28,12 @@ extern TargetFdTrans **target_fd_trans; extern unsigned int target_fd_max; -static inline TargetFdDataFunc fd_trans_target_to_host_data(int fd) -{ - if (fd >= 0 && fd < target_fd_max && target_fd_trans[fd]) { - return target_fd_trans[fd]->target_to_host_data; - } - return NULL; -} - -static inline TargetFdDataFunc fd_trans_host_to_target_data(int fd) -{ - if (fd >= 0 && fd < target_fd_max && target_fd_trans[fd]) { - return target_fd_trans[fd]->host_to_target_data; - } - return NULL; -} - -static inline TargetFdAddrFunc fd_trans_target_to_host_addr(int fd) -{ - if (fd >= 0 && fd < target_fd_max && target_fd_trans[fd]) { - return target_fd_trans[fd]->target_to_host_addr; - } - return NULL; -} - -static inline void fd_trans_register(int fd, TargetFdTrans *trans) -{ - unsigned int oldmax; - - if (fd >= target_fd_max) { - oldmax = target_fd_max; - target_fd_max = ((fd >> 6) + 1) << 6; /* by slice of 64 entries */ - target_fd_trans = g_renew(TargetFdTrans *, - target_fd_trans, target_fd_max); - memset((void *)(target_fd_trans + oldmax), 0, - (target_fd_max - oldmax) * sizeof(TargetFdTrans *)); - } - target_fd_trans[fd] = trans; -} - -static inline void fd_trans_unregister(int fd) -{ - if (fd >= 0 && fd < target_fd_max) { - target_fd_trans[fd] = NULL; - } -} - -static inline void fd_trans_dup(int oldfd, int newfd) -{ - fd_trans_unregister(newfd); - if (oldfd < target_fd_max && target_fd_trans[oldfd]) { - fd_trans_register(newfd, target_fd_trans[oldfd]); - } -} +TargetFdDataFunc fd_trans_target_to_host_data(int fd); +TargetFdDataFunc fd_trans_host_to_target_data(int fd); +TargetFdAddrFunc fd_trans_target_to_host_addr(int fd); +void fd_trans_register(int fd, TargetFdTrans *trans); +void fd_trans_unregister(int fd); +void fd_trans_dup(int oldfd, int newfd); extern TargetFdTrans target_packet_trans; #ifdef CONFIG_RTNETLINK diff --git a/linux-user/main.c b/linux-user/main.c index 16171a1813f..e6cb8ca3c2f 100644 --- a/linux-user/main.c +++ b/linux-user/main.c @@ -220,6 +220,7 @@ void fork_start(void) mmap_fork_start(); sigact_fork_start(); path_fork_start(); + fd_trans_fork_start(); cpu_list_lock(); } @@ -228,6 +229,7 @@ void fork_end(int child) mmap_fork_end(child); sigact_fork_end(child); path_fork_end(child); + fd_trans_fork_end(child); if (child) { CPUState *cpu, *next_cpu; /* Child processes created by fork() only have a single thread. diff --git a/linux-user/qemu.h b/linux-user/qemu.h index 4201f5350e5..088d6f0998e 100644 --- a/linux-user/qemu.h +++ b/linux-user/qemu.h @@ -376,6 +376,8 @@ int get_osversion(void); void init_qemu_uname_release(void); void fork_start(void); void fork_end(int child); +void fd_trans_fork_start(void); +void fd_trans_fork_end(int child); /** * probe_guest_base: diff --git a/linux-user/syscall.c b/linux-user/syscall.c index f044d8bceb4..81b8a5a1578 100644 --- a/linux-user/syscall.c +++ b/linux-user/syscall.c @@ -2501,9 +2501,11 @@ static inline abi_long target_to_host_sockaddr(int fd, struct sockaddr *addr, const socklen_t unix_maxlen = sizeof (struct sockaddr_un); sa_family_t sa_family; struct target_sockaddr *target_saddr; + TargetFdAddrFunc trans; - if (fd_trans_target_to_host_addr(fd)) { - return fd_trans_target_to_host_addr(fd)(addr, target_addr, len); + trans = fd_trans_target_to_host_addr(fd); + if (trans) { + return trans(addr, target_addr, len); } target_saddr = lock_user(VERIFY_READ, target_addr, len, 1); @@ -4629,6 +4631,7 @@ static abi_long do_sendrecvmsg_locked(int fd, struct target_msghdr *msgp, abi_ulong count; struct iovec *vec; abi_ulong target_vec; + TargetFdDataFunc trans; if (msgp->msg_name) { msg.msg_namelen = tswap32(msgp->msg_namelen); @@ -4673,13 +4676,13 @@ static abi_long do_sendrecvmsg_locked(int fd, struct target_msghdr *msgp, msg.msg_iov = vec; if (send) { - if (fd_trans_target_to_host_data(fd)) { + trans = fd_trans_target_to_host_data(fd); + if (trans) { void *host_msg; host_msg = g_malloc(msg.msg_iov->iov_len); memcpy(host_msg, msg.msg_iov->iov_base, msg.msg_iov->iov_len); - ret = fd_trans_target_to_host_data(fd)(host_msg, - msg.msg_iov->iov_len); + ret = trans(host_msg, msg.msg_iov->iov_len); if (ret >= 0) { msg.msg_iov->iov_base = host_msg; ret = get_errno(safe_sendmsg(fd, &msg, flags)); @@ -4714,9 +4717,10 @@ static abi_long do_sendrecvmsg_locked(int fd, struct target_msghdr *msgp, if (!is_error(ret)) { len = ret; - if (fd_trans_host_to_target_data(fd)) { - ret = fd_trans_host_to_target_data(fd)(msg.msg_iov->iov_base, - MIN(msg.msg_iov->iov_len, len)); + trans = fd_trans_host_to_target_data(fd); + if (trans) { + ret = trans(msg.msg_iov->iov_base, + MIN(msg.msg_iov->iov_len, len)); } if (!is_error(ret)) { ret = host_to_target_cmsg(msgp, &msg); @@ -4961,6 +4965,7 @@ static abi_long do_sendto(int fd, abi_ulong msg, size_t len, int flags, void *host_msg; void *copy_msg = NULL; abi_long ret; + TargetFdDataFunc trans; if ((int)addrlen < 0) { return -TARGET_EINVAL; @@ -4969,11 +4974,12 @@ static abi_long do_sendto(int fd, abi_ulong msg, size_t len, int flags, host_msg = lock_user(VERIFY_READ, msg, len, 1); if (!host_msg) return -TARGET_EFAULT; - if (fd_trans_target_to_host_data(fd)) { + trans = fd_trans_target_to_host_data(fd); + if (trans) { copy_msg = host_msg; host_msg = g_malloc(len); memcpy(host_msg, copy_msg, len); - ret = fd_trans_target_to_host_data(fd)(host_msg, len); + ret = trans(host_msg, len); if (ret < 0) { goto fail; } @@ -5006,6 +5012,7 @@ static abi_long do_recvfrom(int fd, abi_ulong msg, size_t len, int flags, void *addr; void *host_msg; abi_long ret; + TargetFdDataFunc trans; if (!msg) { host_msg = NULL; @@ -5034,11 +5041,11 @@ static abi_long do_recvfrom(int fd, abi_ulong msg, size_t len, int flags, ret = get_errno(safe_recvfrom(fd, host_msg, len, flags, NULL, 0)); } if (!is_error(ret)) { - if (fd_trans_host_to_target_data(fd)) { - abi_long trans; - trans = fd_trans_host_to_target_data(fd)(host_msg, MIN(ret, len)); - if (is_error(trans)) { - ret = trans; + trans = fd_trans_host_to_target_data(fd); + if (trans) { + abi_long trans_ret = trans(host_msg, MIN(ret, len)); + if (is_error(trans_ret)) { + ret = trans_ret; goto fail; } } @@ -14400,6 +14407,7 @@ static abi_long do_syscall1(void *cpu_env, int num, abi_long arg1, struct statfs stfs; #endif void *p; + TargetFdDataFunc fd_data_trans; switch(num) { case TARGET_NR_ioperm: @@ -14455,9 +14463,9 @@ static abi_long do_syscall1(void *cpu_env, int num, abi_long arg1, if (!(p = lock_user(VERIFY_WRITE, arg2, arg3, 0))) return -TARGET_EFAULT; ret = get_errno(safe_read(arg1, p, arg3)); - if (ret >= 0 && - fd_trans_host_to_target_data(arg1)) { - ret = fd_trans_host_to_target_data(arg1)(p, ret); + fd_data_trans = fd_trans_host_to_target_data(arg1); + if (ret >= 0 && fd_data_trans) { + ret = fd_data_trans(p, ret); } unlock_user(p, arg2, ret); } @@ -14468,10 +14476,11 @@ static abi_long do_syscall1(void *cpu_env, int num, abi_long arg1, } if (!(p = lock_user(VERIFY_READ, arg2, arg3, 1))) return -TARGET_EFAULT; - if (fd_trans_target_to_host_data(arg1)) { + fd_data_trans = fd_trans_target_to_host_data(arg1); + if (fd_data_trans) { void *copy = g_malloc(arg3); memcpy(copy, p, arg3); - ret = fd_trans_target_to_host_data(arg1)(copy, arg3); + ret = fd_data_trans(copy, arg3); if (ret >= 0) { ret = get_errno(safe_write(arg1, copy, ret)); }