Skip to content

feat: add InjectorPP::new_global() for cross-thread fake visibility#125

Merged
mazong1123 merged 3 commits intomainfrom
feature/global-mode
Mar 27, 2026
Merged

feat: add InjectorPP::new_global() for cross-thread fake visibility#125
mazong1123 merged 3 commits intomainfrom
feature/global-mode

Conversation

@mazong1123
Copy link
Copy Markdown
Collaborator

Summary

Adds a new constructor InjectorPP::new_global() that provides global fake visibility — fakes registered through this constructor are visible to all threads, not just the creating thread.

This complements the default InjectorPP::new() which uses thread-local dispatch for parallel test safety.

Motivation

When faked functions are called from background threads (e.g., timer callbacks, thread pool workers), thread-local fakes are not visible because the background thread has no TLS entry. new_global() solves this by using direct code patching (JMP instruction overwrite), making fakes visible process-wide.

API

// Thread-local mode (default, parallel-safe)
let mut injector = InjectorPP::new();

// Global mode (fakes visible to all threads, serialized)
let mut injector = InjectorPP::new_global();

Both constructors expose the same when_called() / will_execute() / will_return_boolean() API — the only difference is visibility scope.

Design

  • new(): Thread-local dispatch via TLS HashMap. Multiple instances can run in parallel. Fakes only visible on the creating thread.
  • new_global(): Direct JMP patching via PatchGuard. Acquires an exclusive write lock, so global-mode tests run serialized. Fakes visible to all threads.
  • RwLock coordination: new() acquires a read lock (allows parallel TLS tests), new_global() acquires a write lock (waits for all other instances to drop, then holds exclusive access).

Changes

  • src/interface/injector.rs: Added new_global(), use_global field, RwGuard enum with GLOBAL_FAKE_LOCK: RwLock
  • src/injector_core/internal.rs: Made PatchGuard helpers (will_execute_guard, will_return_boolean_guard) available on all architectures
  • src/injector_core/patch_amd64.rs: Added IAT thunk resolution in patch_and_guard() to match TLS dispatcher behavior
  • src/injector_core/thread_local_registry.rs: Removed unused global replacement code
  • tests/global.rs: 10 new integration tests covering cross-thread visibility, call counting, restore-on-drop, multi-thread, unchecked mode, and thread-local isolation

mazong1123 and others added 3 commits March 26, 2026 21:09
Add a new constructor InjectorPP::new_global() that uses direct code
patching (JMP instruction overwrite) instead of thread-local dispatch.
This makes fakes visible to all threads, which is needed when faked
functions are called from background threads (e.g., timer callbacks).

Key changes:
- Add
ew_global() constructor using PatchGuard (direct patching)
-
ew() continues to use thread-local dispatch (default, parallel-safe)
- RwLock coordination: new() takes read lock (parallel), new_global()
  takes write lock (exclusive) to prevent interference
- Resolve IAT thunks in PatchGuard path to match TLS dispatcher behavior
- Make PatchGuard helpers available on all architectures in internal.rs

The two modes:
- new() [default]: Thread-local, parallel-safe, fakes only on creator thread
- new_global(): Global patching, serialized, fakes visible to all threads

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… gate

- Replace || func() closures with func references in test spawns (clippy)
- Gate thread-local isolation test with #[cfg(x86_64, aarch64)] since
  arm uses global patching for new() and has no TLS dispatch

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
On ARM, PatchGuard uses 12-byte patches. Tiny helper functions (e.g.,
'fn f() -> i32 { 42 }') can be as small as 4-6 bytes in Thumb mode.
When two adjacent functions are patched simultaneously in the same
injector, the 12-byte patches overlap, corrupting the first patch's
target address and causing a SIGSEGV.

Fix: wrap return values in core::hint::black_box() to ensure each
function compiles to at least 16 bytes on all architectures.

Also re-enable thread-local isolation test on ARM (target_arch = arm
has TLS dispatch support).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@mazong1123 mazong1123 merged commit b60ea30 into main Mar 27, 2026
17 checks passed
@mazong1123 mazong1123 deleted the feature/global-mode branch March 27, 2026 06:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant