Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions accel/tcg/tb-flush.c
Original file line number Diff line number Diff line change
Expand Up @@ -108,10 +108,15 @@ void do_tb_flush(CPUState *cpu, run_on_cpu_data tb_flush_count)
qemu_plugin_flush_cb();
}
#if defined(CONFIG_LATX_KZT)
CPU_FOREACH(cpu) {
/* The installer also checks the effective library-group mask. */
if (cpu && option_kzt) {
kzt_install_runtime_callbacks(cpu, &info1);
if (!latx_kzt_guest_tls_enabled() || did_flush) {
CPU_FOREACH(cpu) {
/* The installer checks the effective library-group mask. */
if (cpu && option_kzt) {
kzt_install_runtime_callbacks(cpu, &info1);
if (latx_kzt_guest_tls_enabled()) {
break;
}
}
}
}
#endif
Expand Down
146 changes: 146 additions & 0 deletions docs/devel/kzt-guest-tls.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
# Optional Guest TLS for KZT native-thread callbacks

KZT can receive Guest callbacks on pthreads created by a native library.
Such a thread has not executed the Guest pthread creation path. The optional
Guest TLS runtime provides a Guest CPU, stack, TCB and DTV for that thread.

## Activation

The feature is disabled by default. Select it at process startup:

```sh
LATX_KZT=1 LATX_KZT_GUEST_TLS=1 latx-x86_64 program
```

The equivalent command-line option is `-latx-kzt-guest-tls 1`.
Only `0` and `1` are accepted. Enabling this option without an effective
KZT library group does not activate the feature. Configuration is fixed
before Guest execution and must not be changed after threads are attached.

With the option disabled, no Host-thread template is created, automatic
attachment is disabled, and the added loader transaction and TLS refresh
locks are bypassed. Existing library registration remains available.
Guest pthreads continue to own their Guest libc TLS.

## Ownership and lifetime

An immutable template is captured at the Guest program entry point. An
unattached Host thread copies this template, creates its own Guest stack
and constructs Guest TLS from validated live loader information. It never
copies a concurrently executing parent's CPU state or entire pthread
descriptor.

The loader observer reads Guest r_debug/link_map and ELF metadata. Static
TLS placement, dynamic module IDs, object identity and DTV generation must
agree before Guest code executes. An unchanged loader epoch allows a
callback to reuse its TLS without another link_map walk.

Callback execution protects the attached thread's DTV from concurrent
replacement. Loader changes and fork coordinate with this protection.
Internal initialization calls use an explicit no-refresh entry point to
avoid recursively entering the initializer.

Guest pthread keys and values stay in Guest libc. Optional key wrappers
record destructors while forwarding key operations to Guest libc. Attached
thread exit runs recorded TSD and C++ TLS destructors, releases retained
DSOs and handles non-PI robust mutex owner death before releasing TLS.
The standard Guest pthread exit path retains its own destructor ownership.

If Guest code running on an attached thread creates a Guest pthread, CPU
cloning clears the managed stack, TLS allocation, parent snapshot and
destructor-state pointers. Guest libc supplies the child's own TLS through
the normal clone path. The child must not inherit the attached parent's
DTV ownership or execution lock.

## Scope

This feature does not enable bidirectional errno or locale synchronization.
The separate libc boundary facility requires explicit activation.
Constructing a usable Guest libc thread state still initializes that
thread's Guest locale/ctype data.

The implementation targets x86-64 glibc Guest TLS. Other libc layouts,
additional loader namespaces, PI robust futexes and arbitrary non-local
exits across a native callback require separate validation.

This does not implement all private glibc pthread initialization. In
particular, the extended ABI1 resolver probe currently observes shared
`__res_state()` backing storage on attached threads. Resolver APIs are
outside the supported attached-thread profile until that bootstrap is
implemented and validated.

The Guest loader must expose the TLS allocation helpers and a loaded
`dlinfo` provider for module IDs that cannot be established from relocation
evidence. On systems with a separate Guest libdl, the caller must load/link
that library before attachment. Missing helpers reject attachment; the
runtime does not infer private link_map offsets or guess module IDs.

## Tests

`test-kzt-guest-tls-policy` exercises the opt-in policy.
`test-kzt-public-loader-observer` exercises live ELF validation.
`test-kzt-guest-tls-epoch` exercises snapshot reuse.
`test-kzt-guest-tls-opt-in` uses a native probe library to check existing
thread callbacks with the option unset/zero and isolated Host-thread TLS
with the option enabled, including Guest TSD destruction.

With a debug-symbol LATX build, set `LATX_KZT_BOOTSTRAP_GDB=gdb` when
running `test-kzt-guest-tls-opt-in.sh` to additionally check the actual
Guest TLS allocation helper entry. The diagnostic checks that every
observed attached TCB already contains its real thread ID before the
loader helper executes, and fails if that entry is never reached.
The second diagnostic injects one transient busy loader snapshot before
Guest TLS allocation and verifies that attachment retries and executes the
callback. Attachment retries release the failed attempt's resources and
locks before waiting; other initialization failures are not retried.

These test binaries are not part of the default product build.
Target tests require a LoongArch host and matching Guest compilation tools.
SKIP is not a successful runtime result.


## Inventory and fork lifetime

The KZT inventory version is a private observation counter. It is never used
as the Guest DTV generation. A separate loader generation is seeded from a DTV
initialized by the Guest loader. The `r_brk` observer records every Guest-loader
transaction whose TLS inventory changes and advances this generation once per
transaction, so several load and unload operations cannot collapse into one
attached-thread observation.

The early `r_brk` callback may run before glibc publishes the new loader
generation. It prepares new TLS entries for constructors but leaves the
attached inventory dirty. A callback reached while the serialized Guest loader
operation is active uses the same preparation-only path. The regular
post-`dlopen` refresh publishes the separately tracked loader generation without
reinitializing live TLS pointers or values.

KZT-only external registrations are tracked in the inventory but do not advance
the Guest loader's TLS generation. They therefore propagate their prepared DTV
entries without advancing the loader generation.

Ordinary attached callbacks may suspend their execution guard at selected
blocking I/O and non-PI futex wait syscalls. Their CallbackScope retains the
CPU/TLS allocation and keeps native cancellation disabled. Internal helper
calls and a thread already preparing fork do not take this suspension path.
Syscalls changing mappings, TLS or thread identity retain their existing
serialization.

A Guest libc fork hook takes the fork writer lock before libc takes its
internal locks. Its Guest address survives a full TB flush, but the saved
translated instructions do not: both the loader and fork hooks are rebuilt.
A new executable clears the cached addresses. The libc-scoped lookup avoids
ambiguity when libpthread also exports fork.

Fork completion releases an outstanding early writer even when seccomp or
argument validation bypasses do_fork. A real internal syscall restart keeps
the same fork scope; a seccomp errno numerically equal to a restart code does
not. Cleanup is idempotent when do_fork already finished it.

The test-kzt-tls-fork-* integration cases cover retained GD TLS values after
coalesced loader activity, observed blocking read/futex waits, denied fork,
restart-valued errno, denied fork from attached callers, and fork hooks after an explicit full
TB flush with Guest libc lock contention. The flush case uses gdb to observe
hook execution; the other tests require no debugger. Set
LATX_KZT_FORK_GUEST_ARTIFACT_DIR for verified prebuilt Guest fixtures and
LATX_X11_INCLUDE when the Xlib development headers are outside /usr/include.
48 changes: 48 additions & 0 deletions docs/devel/kzt-libc-boundaries.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# KZT libc state at explicit native-call boundaries

Guest TLS storage and cross-libc state propagation are separate facilities.
This layer transfers errno/h_errno values and locale category names across
an explicitly selected native-call boundary. It does not transfer locale_t
objects, ctype pointers or allocator ownership between libc instances.

Each Guest context owns its own non-global locale projection objects; they
are not process-wide cache handles. A current locale obtained with
`uselocale(0)` is borrowed from the boundary runtime and remains valid only
until that context is destroyed. Consumers must call `duplocale()` to obtain
an owned copy before modifying it with `newlocale(..., base)` or releasing
it with `freelocale()`. Borrowed projection handles must not be shared with
other threads. Context teardown deselects its active projections before
freeing them in their owning libc. Ordinary consumer-created locale objects
retain their original ownership.

The caller provides the entry state. On return, the callee provides the
state to propagate back. Guest helper calls used by initialization must
not themselves initiate another semantic boundary.

The broker starts inactive. A consumer first initializes semantic state
for the current Guest context and explicitly prepares the process locale
before invoking native code that requires this protocol. Guest TLS must
already be enabled. Merely enabling KZT or optional Guest TLS does not
activate libc state propagation.

Native consumers explicitly call `kzt_libc_semantic_enter_current()`
and `kzt_libc_semantic_leave_current()` around a Guest-to-Host call.
Host-to-Guest calls that participate use
`latx_run_guest_callback_with_libc()`. The ordinary
`latx_run_guest_callback()`, `RunFunctionWithState()` and formatted
callback entries continue to provide TLS without projecting libc state,
even when another consumer has activated the process broker.

Ordinary Guest pthread key/value operations and TLS destructor ownership
belong to the Guest thread runtime, not this broker.

Locale names must be available in both libc installations. Failure to
reconstruct a required locale is an error, not permission to substitute
a pointer or silently select another locale. Resolver state, cancellation
and other private libc caches are not covered by this protocol.

The extended resolver-isolation probe currently fails on the ABI1 profile:
attached threads can receive the same Guest `__res_state()` object.
Neither TLS opt-in nor this broker makes resolver APIs safe on attached
threads. This is a known limitation, retained in local extended validation,
not a successful test or a bidirectional resolver implementation.
54 changes: 50 additions & 4 deletions linux-user/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ int mydebug = 1;
#include "wrapper.h"
#if defined(CONFIG_LATX_KZT)
#include "kzt-groups.h"
#include "kzt-guest-tls.h"
#include "kzt-libc-semantic.h"
#include "wrappertbbridge.h"
box64context_t* my_context = NULL;
elfheader_t* elf_header = NULL;
Expand Down Expand Up @@ -314,10 +316,9 @@ void init_task_state(TaskState *ts)
#endif
}

CPUArchState *cpu_copy(CPUArchState *env)
static CPUArchState *cpu_copy_into(CPUArchState *env, CPUState *new_cpu)
{
CPUState *cpu = env_cpu(env);
CPUState *new_cpu = cpu_create(cpu_type);
CPUArchState *new_env = new_cpu->env_ptr;
CPUBreakpoint *bp;
CPUWatchpoint *wp;
Expand All @@ -327,6 +328,14 @@ CPUArchState *cpu_copy(CPUArchState *env)

new_cpu->tcg_cflags = cpu->tcg_cflags;
memcpy(new_env, env, sizeof(CPUArchState));
#ifdef CONFIG_LATX_KZT
/* Managed resources belong to one CPU and must not be inherited. */
new_env->kzt_guest_stack_base = 0;
new_env->kzt_guest_tls_allocation = NULL;
new_env->kzt_guest_tls_parent_snapshot = NULL;
new_env->kzt_guest_thread_state = NULL;
new_env->kzt_libc_semantic_state = NULL;
#endif

/*
* NOTE: Current QEMU only has one and only one gdt_table ptr.
Expand Down Expand Up @@ -360,6 +369,11 @@ CPUArchState *cpu_copy(CPUArchState *env)
return new_env;
}

CPUArchState *cpu_copy(CPUArchState *env)
{
return cpu_copy_into(env, cpu_create(cpu_type));
}

#if defined(CONFIG_LATX_DEBUG) || defined(CONFIG_DEBUG_TCG)
#ifdef CONFIG_LATX
#include "latx-options.h"
Expand Down Expand Up @@ -426,6 +440,7 @@ static void handle_arg_latx_disassemble_trace_cmp(const char *arg)
}

#endif
#endif /* CONFIG_LATX_DEBUG || CONFIG_DEBUG_TCG */

static void handle_arg_imm_skip_pc(const char *arg) {
imm_skip_pc = strtol(arg, NULL, 16);
Expand Down Expand Up @@ -602,7 +617,6 @@ static void handle_arg_plugin(const char *arg)
qemu_plugin_opt_parse(arg, &plugins);
}
#endif
#endif

static void handle_arg_help(const char *arg)
{
Expand All @@ -625,6 +639,12 @@ static void handle_arg_runtime_info(const char *arg)

static void handle_arg_ld_prefix(const char *arg)
{
g_autofree char *setting = g_strdup_printf(
"LAT_LD_PREFIX=%s", arg);

if (!setting || envlist_setenv(envlist, setting) != 0) {
usage(EXIT_FAILURE);
}
interp_prefix = strdup(arg);
latx_runtime_prefix_selected();
}
Expand Down Expand Up @@ -686,6 +706,26 @@ static void handle_arg_latx_kzt(const char *arg)
option_kzt = value;
}

static void handle_arg_latx_kzt_guest_tls(const char *arg)
{
g_clear_pointer(&option_kzt_guest_tls_error, g_free);
if (!strcmp(arg, "0") || !strcmp(arg, "1")) {
#ifndef TARGET_X86_64
if (arg[0] == '1') {
option_kzt_guest_tls = 0;
option_kzt_guest_tls_error =
g_strdup("LATX_KZT_GUEST_TLS requires an x86-64 Guest");
return;
}
#endif
option_kzt_guest_tls = arg[0] == '1';
return;
}
option_kzt_guest_tls = 0;
option_kzt_guest_tls_error = g_strdup_printf(
"LATX_KZT_GUEST_TLS must be exactly 0 or 1 (got '%s')", arg);
}

static void handle_arg_latx_kzt_libs(const char *arg)
{
g_free(option_kzt_libs);
Expand Down Expand Up @@ -969,6 +1009,9 @@ static const struct qemu_argument arg_table[] = {
#if defined(CONFIG_LATX_KZT)
{"latx-kzt", "LATX_KZT", true, handle_arg_latx_kzt,
"", "enable kuzhitong"},
{"latx-kzt-guest-tls", "LATX_KZT_GUEST_TLS", true,
handle_arg_latx_kzt_guest_tls, "0|1",
"Enable Guest TLS for native-thread callbacks (default: 0)"},
{"latx-kzt-libs", "LATX_KZT_LIBS", true, handle_arg_latx_kzt_libs,
"group,...", "select KZT library groups"},
{"latx-kzt-log", "LATX_KZT_LOG", true, handle_arg_latx_kzt_log,
Expand Down Expand Up @@ -1058,6 +1101,7 @@ static const struct qemu_argument arg_table[] = {
true, handle_arg_latx_disassemble_trace_cmp,
"", "LATX Compare different disassemble."},
#endif
#endif /* CONFIG_LATX_DEBUG || CONFIG_DEBUG_TCG */
{"g", "LAT_GDB", true, handle_arg_gdb,
"port", "wait gdb connection to 'port'"},
{"s", "LAT_STACK_SIZE", true, handle_arg_stack_size,
Expand Down Expand Up @@ -1100,7 +1144,6 @@ static const struct qemu_argument arg_table[] = {
#ifdef CONFIG_PLUGIN
{"plugin", "LAT_PLUGIN", true, handle_arg_plugin,
"", "[file=]<file>[,arg=<string>]"},
#endif
#endif
{"h", NULL, false, handle_arg_help,
"", "print this help"},
Expand Down Expand Up @@ -1624,6 +1667,9 @@ int main(int argc, char **argv, char **envp)
latx_handle_args(exec_path);
#endif
thread_cpu = cpu;
#ifdef CONFIG_LATX
latx_register_host_thread_template(env);
#endif

/*
* Reserving too much vm space via mmap can run into problems
Expand Down
7 changes: 7 additions & 0 deletions linux-user/qemu.h
Original file line number Diff line number Diff line change
Expand Up @@ -372,8 +372,15 @@ abi_long do_syscall_with_seccomp(void *cpu_env, int num, int seccomp_num,
abi_long arg5, abi_long arg6, abi_long arg7,
abi_long arg8);
extern __thread CPUState *thread_cpu;
#ifdef CONFIG_LATX
void latx_register_host_thread_template(CPUArchState *env);
int latx_finalize_host_thread_template(CPUArchState *env);
int latx_attach_current_host_thread(void);
#endif
void cpu_loop(CPUArchState *env);
const char *target_strerror(int err);
int host_to_target_errno(int err);
int target_to_host_errno(int err);
int get_osversion(void);
void init_qemu_uname_release(void);
void fork_start(void);
Expand Down
Loading