Add process discovery helpers and Magic facade accessors

Introduces ApplicationFinder (by name/title/handle), Magic.Open overloads, and Magic.Threads/Regions/QueryRegion accessors. Closes section 3 of add-thread-region-finder and updates tasks/comparison doc.
This commit is contained in:
kbe
2026-07-22 16:04:53 +02:00
parent 8f988768fe
commit 3e294dc846
12 changed files with 624 additions and 2 deletions
@@ -0,0 +1,25 @@
## ADDED Requirements
### Requirement: Process discovery
WhiteMagic SHALL attach to a target process discovered by process name, window title, or window handle, and SHALL enumerate candidate processes. An ambiguous match MUST fail deterministically rather than attaching to an arbitrary candidate.
#### Scenario: open by process name
- **WHEN** a target is opened by a unique process name
- **THEN** it MUST attach to that process
#### Scenario: open by window title
- **WHEN** a target is opened by a window title
- **THEN** it MUST attach to the process owning the window with that title
#### Scenario: open by window handle
- **WHEN** a target is opened by a window handle
- **THEN** it MUST attach to the process that owns that window
#### Scenario: ambiguous match is rejected
- **WHEN** more than one process matches the given name or title
- **THEN** the open MUST fail and surface the set of candidate processes rather than picking one
#### Scenario: enumerate candidates
- **WHEN** candidate processes are enumerated
- **THEN** the result MUST list the processes eligible to be opened
@@ -0,0 +1,41 @@
## ADDED Requirements
### Requirement: Query the region containing an address
WhiteMagic SHALL return the mapped memory region that contains a given address, including its base, size, protection, state, and type.
#### Scenario: query a committed address
- **WHEN** the region containing a known committed address is queried
- **THEN** it MUST return a region whose base and size bracket that address and whose protection reflects the page's actual protection
#### Scenario: region membership test
- **WHEN** a region is asked whether it contains an address
- **THEN** it MUST return true only for addresses within `[base, base + size)`
### Requirement: Enumerate mapped regions
WhiteMagic SHALL enumerate the mapped memory regions of the target from the lowest address upward, lazily.
#### Scenario: enumeration walks the address space
- **WHEN** the target's regions are enumerated
- **THEN** the sequence MUST yield contiguous, non-overlapping regions ascending by base address until the end of the queryable address space
#### Scenario: early stop
- **WHEN** a caller stops consuming the enumeration after the first match
- **THEN** enumeration MUST NOT query the entire address space
### Requirement: Scoped protection change
WhiteMagic SHALL change the protection of a region and restore the original protection when the returned scope is disposed.
#### Scenario: protection is applied within the scope
- **WHEN** a protection-change scope is created for a region with a new protection
- **THEN** the region's protection MUST be the requested value for the duration of the scope
#### Scenario: protection is restored on dispose
- **WHEN** the protection-change scope is disposed
- **THEN** the region's protection MUST be restored to the value it had before the scope was created
#### Scenario: restore on exception
- **WHEN** the guarded body throws before the scope is disposed
- **THEN** the original protection MUST still be restored as the scope unwinds
@@ -0,0 +1,57 @@
## ADDED Requirements
### Requirement: Enumerate target threads
WhiteMagic SHALL enumerate the threads belonging to the target process and expose each as a controllable thread handle.
#### Scenario: enumerate returns the target's threads
- **WHEN** the threads of an open target are enumerated
- **THEN** the result MUST contain a handle for each thread owned by the target process and none owned by other processes
#### Scenario: resolve the main thread
- **WHEN** the main thread is requested
- **THEN** it MUST return the earliest-created thread of the target process
#### Scenario: get a thread by id
- **WHEN** a thread is requested by its thread id
- **THEN** it MUST return a handle bound to that thread, or fail deterministically if the id is not a thread of the target
### Requirement: Suspend and resume a thread
WhiteMagic SHALL suspend and resume an individual target thread and report the prior suspend count.
#### Scenario: suspend increments the suspend count
- **WHEN** a running thread is suspended
- **THEN** the thread MUST stop executing and the returned prior suspend count MUST reflect its state before the call
#### Scenario: resume restores execution
- **WHEN** a previously suspended thread is resumed to a zero suspend count
- **THEN** the thread MUST resume executing
### Requirement: Read and write thread context
WhiteMagic SHALL read and write a target thread's register context, selecting the context layout that matches the target's bitness.
#### Scenario: round-trip a register value
- **WHEN** a thread's context is read, a register is modified, and the context is written back
- **THEN** a subsequent read MUST reflect the modified register value
#### Scenario: bitness-correct context
- **WHEN** the target is a 32-bit (WOW64) process
- **THEN** the WOW64 context layout MUST be used, and for a 64-bit target the native layout MUST be used
### Requirement: Scoped thread freeze
WhiteMagic SHALL provide a scoped freeze that suspends a selected set of target threads and resumes exactly those threads when the scope is disposed, including when the guarded body throws.
#### Scenario: freeze suspends selected threads
- **WHEN** a freeze scope is created over a set of threads
- **THEN** each of those threads MUST be suspended for the duration of the scope
#### Scenario: dispose resumes only the frozen threads
- **WHEN** the freeze scope is disposed
- **THEN** exactly the threads it suspended MUST be resumed, and threads suspended by other callers MUST be left unchanged
#### Scenario: exception in the body still resumes
- **WHEN** the guarded body throws before the scope is disposed
- **THEN** the frozen threads MUST still be resumed as the scope unwinds