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(); 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/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/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, diff --git a/linux-user/main.c b/linux-user/main.c index 47281e85906..e6cb8ca3c2f 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 @@ -217,6 +220,7 @@ void fork_start(void) mmap_fork_start(); sigact_fork_start(); path_fork_start(); + fd_trans_fork_start(); cpu_list_lock(); } @@ -225,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. @@ -1287,6 +1292,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 +1456,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 +1559,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/linux-user/qemu.h b/linux-user/qemu.h index cba8f748b37..088d6f0998e 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. */ @@ -374,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 d65e4930f64..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; } } @@ -11024,6 +11031,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 +11089,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 +11106,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 +11159,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 +11175,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), @@ -12830,6 +12867,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 +13019,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 @@ -14294,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: @@ -14349,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); } @@ -14362,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)); } @@ -15258,7 +15373,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 @@ -18478,7 +18593,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)) { @@ -19319,9 +19435,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; @@ -19357,9 +19473,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; @@ -19402,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_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; @@ -19441,9 +19557,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; @@ -19649,22 +19765,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) 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. 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; 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) { 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; 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; } 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); 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"