Modules and threads

Beyond memory regions, PyMemoryEditor exposes two more inspection APIs:

  • get_modules() — every executable and shared library loaded into the process.

  • get_threads() — every thread currently running inside the process.

Both return immutable dataclass instances and are cross-platform.

Loaded modules

A module is a file mapped into the process — the main executable plus every shared library it loaded (.exe/.dll on Windows, the binary and .so files on Linux, the Mach-O image and .dylib files on macOS).

with OpenProcess(process_name="game.exe") as process:
    for module in process.get_modules():
        print(
            module.name,
            hex(module.base_address),
            module.size,
            module.path,
        )

The ModuleInfo dataclass

class ModuleInfo
name: str

File name of the module — e.g. "game.exe", "libc.so.6".

path: str

Full path of the backing file on disk. On Windows it falls back to name when only the name is available; on macOS name derives from path, so an unresolvable image yields both as empty strings.

base_address: int

Address where the module is loaded for this run. Combine it with a static offset (base_address + offset) to reach a known location despite ASLR — the natural feed into resolve_pointer_chain().

size: int

Module size in bytes. 0 when the backend cannot determine it. Platform-specific: full image on Windows/Linux; __TEXT segment size on macOS.

raw: Any

Underlying platform handle for advanced follow-up calls.

Finding a module by name

def find_module(process, name):
    for module in process.get_modules():
        if module.name == name:
            return module
    return None

with OpenProcess(pid=1234) as process:
    main_module = find_module(process, "game.exe")
    if main_module:
        # Use module.base_address + static_offset as the base of a pointer chain.
        hp_addr = process.resolve_pointer_chain(
            main_module.base_address + 0x10F4F4, [0x0, 0x158],
        )

Threads

get_threads() yields a ThreadInfo for every thread running inside the target. It’s useful for introspection (“how many workers does it spawn?”, “is the main thread still alive?”).

with OpenProcess(process_name="game.exe") as process:
    for thread in process.get_threads():
        print(thread.tid, thread.state, thread.priority)

    print("Main thread:", process.main_thread.tid)

The ThreadInfo dataclass

class ThreadInfo
tid: int

Thread identifier. The meaning is platform-specific:

  • Linux — POSIX TID; same namespace as PID.

  • Windows — kernel-assigned DWORD thread id.

  • macOS — Mach thread port name.

start_address: int | None

Reserved for the thread entry point — currently always ``None`` on every platform (no backend fetches it).

state: str | None

Short human-readable state (e.g. "R"/"S" on Linux). None on platforms that don’t surface it.

priority: int | None

Scheduling priority as reported by the OS. Scale is platform-specific; None when not exposed.

raw: Any

Underlying platform handle (THREADENTRY32 on Windows, the TID path on Linux, a Mach port int on macOS). Useful for advanced follow-up calls.

The main_thread shortcut

The process.main_thread property is a convenience for the conventional “main thread” — by convention, the thread with the smallest tid:

print(process.main_thread.tid)

It returns None if the target has no listable threads (rare; typically means the process just exited).

Cross-platform tid semantics

Don’t mix tids with pids, and don’t compare tids across operating systems. On Linux a TID and a PID share a namespace; on Windows and macOS they don’t.

See also

  • Memory regions — list the process’s address space.

  • Pointers — use module.base_address + offset as a static base.