Skip to content

doc: rewrite the docs and code comments in simplified English - #498

Merged
williballenthin merged 63 commits into
masterfrom
doc-readme-architecture
Nov 4, 2026
Merged

williballenthin merged 63 commits into
masterfrom
doc-readme-architecture

Conversation

@williballenthin

@williballenthin williballenthin commented Nov 4, 2026 •

Copy link
Copy Markdown
Collaborator

This PR rewrites the README, the pages in doc/ and the comments and docstrings of the core modules in ASD-STE100 style. The sentences put the actor first and use active verbs. I removed vague verbs, unclear "it" and "this" references, notes about earlier changes, and lists of details that go out of date. The PR also corrects about 25 statements in doc/ that did not agree with the code. Examples are the stack_size default, the meaning of $ROOT$, the GDB vKill behavior and the report keys that can be null or absent.

The code has the same behavior. In each changed .py file, an AST comparison against master shows changes only in docstrings, comments and description=/help= strings. The one other change is two module constants in config.py, so that three Field definitions share one description each. The CLI --help text, the config field descriptions and the report field descriptions are user-visible. I regenerated speakeasy/resources/report.schema.json, and only its descriptions changed. ruff and mypy pass.

The review also found code defects. This PR does not fix them. #499 tracks them, with one sub-issue for each defect.

🤖 Generated with Claude Code

No public API changes.

Replace the README documentation map with a link to doc/index.md,
which already lists all pages; the two lists had diverged.

In architecture.md, remove the config defaults, the WOW64 API list,
the relocation types and the _handle_api_* method list. The config,
the handlers and the docstrings already have these details. Remove
the edge cases of dropped DLLs in child processes. State once that a
hook records a native exit and does not run handlers.

In both files, reword sentences so that the subject is the part that
does the action.
Reword the doc pages so that the subject of each sentence does the
action, name the referent of each "it" and "this", and replace vague
verbs. Remove copied counts, defaults and lists that go stale, and
convert short bullet lists and one-line sections to prose.

Fix claims that do not match the code:
- reporting: the report leaves out unknown keys instead of null;
  dropped_files and memory are user-mode only; Speakeasy never emits
  module_load.
- configuration: the stack_size default, the meaning of $ROOT$, the
  winsock default response, and --volume in the precedence order.
- cli-reference: the flag naming rule for repeated words and hex input
  for integer flags.
- gdb: the server reports module unloads; add vKill.
- cli-execution-controls: the collectors behind the --no-* flags are
  off by default; a child process is skipped on any load error.
- memory: the format of memory tags.
- limitations and api-handlers: unsupported API conditions and the
  handler aliases.
- speakeasy2-walkthrough: the memory layout is in every report;
  automatic volumes apply only to load_module with a path.

Rename the walkthrough to "What's new in Speakeasy 2.0".
…comments

The docstring of dispatch_fault said that it returns False when no handler
takes the exception. It returns False only when the dispatch fails before it
calls a guest handler. A later failure ends the run.
The docstring of get_import_slots said that the function skips a
descriptor that it cannot read. The function stops at such a
descriptor. It skips only a descriptor without a valid DLL name.
The class docstring said that peb_addr is the address of the PEB. It is
the address of the PEB pointer in the TEB, and only Win32Emulator sets it.
The docstring of create_reg_path said that it creates a key in the registry. It only writes the path string to guest memory.
Fix false comments. get_pack said that the default pack is the pointer
size, but the code returns twice the pointer size. get_bytes said that
it respects endianness, but it copies the host memory. The __init__
comment said that a __dict__ write prevents a __getattribute__ loop, but
__setattr__ raises AttributeError before __struct__ exists.
The module docstring said that dispatch_fault is a generator handler. It
returns a bool.
A comment said that file_open deep-copies a file. The copy shares the data and has its own file pointer. A comment said that file_config is the "files" key of the config. It is the filesystem section.
The module docstring said that dispatch_fault is a generator handler. It
returns a bool.
The docstring of _prepare_next_run said that it runs the next run. It
only prepares the next run; the run loop starts Unicorn.
The docstring of get_key_paths said that it gives the open keys. It gives all known keys, open or not. A comment said that get_key_handle creates a key only on request. It also creates a key that is the parent of a known key.
The docstring of SessionManager said that it isolates sessions. The emulator has one session and no isolation.
The module docstring said that the emulator calls this module only from
four places. The emulator and the SEH code also call start, fail, alloca
and caller_context. The comment on HandlerState.context said CONTEXT64,
but on x86 the field holds the address of an x86 CONTEXT.
The docstring said that the class removes a load when a DllMain returns
FALSE. It does this only in LoadLibrary. A failed initializer at start
keeps its module mapped with the state INIT_ERROR.
The docstrings said that a handle table belongs to a process. The managers for sockets, windows and crypto objects also have tables.
The docstring of set_hooks said that it reserves memory. It adds the
hooks of the hook lists to Unicorn.
Fix false docstrings. A native hook calls its callback through a wrapper
such as _wrap_code_cb, and a hook that is not native goes through a
dispatch hook of the emulator; the docstring said the reverse. A dynamic
code hook can fire more than once for a region: it also fires when code
in the region calls an API.
The docstring said that the hook fires for generated or copied code. The
hook fires for code in memory that VirtualAlloc or a similar API
allocated.
Fix a false comment. The register list said that Unicorn does not give
the x87 segment selectors or the operand offset, but Unicorn 2.1 has
UC_X86_REG_FCS, UC_X86_REG_FDS and UC_X86_REG_FDP. The comment now
tells what the server does: it gives 0 for these registers.
The stack_size description said that 0 uses SizeOfStackReserve or 0x12000. For a PE, the loader uses max(SizeOfStackReserve, 0x12000). For shellcode, run_shellcode uses its stack_commit argument (default 0x4000). The max_instructions description did not say that 0 or less disables the limit.
The --dropped-files-path help said that the path stores the files. The CLI writes one zip archive with a speakeasy_manifest.json to the path. The --emulate-children help named only CreateProcess, and did not say that it applies only to PE input.
The docstring of _handle_invalid_write said that the hook runs for a write
to a non-writable address. It runs for a write to unmapped memory. A
comment said that the hook ignores patches to APIs, but the reserved
range has the return addresses of the emulator, not the code of APIs.
The get_run_time docstring said that the method gives the time of one run. The method gives the time since the last call of set_start_time. The record_api_event docstring said that the event has the return address. The event has the pc of the call.
The add_hook docstring said that all hooks of a type run and that only IN
hooks are an exception. Invalid memory hooks stop on False, invalid
instruction hooks stop on True, and an API without a handler runs only
the last hook. The do_call_return comment said that the engine removes
the cdecl arguments. The caller removes them.
The comment on the export runs said that the emulator stores them and
queues them only before the unload routine. add_run adds each export run
to the queue at once, after the DllMain run.
The TYPE_SIZES comment said that ParamSig.size gives the size of ps:NAME.
ps is pointer-sized in TYPE_SIZES. ParamSig.size computes only st and arr.
The comment on the export runs said that the emulator queues them just
before the unload routine. next_driver_func queues them when the run
queue is empty after DriverEntry, before the first IRP run.
…nd the objects

The comment of get_module_data_from_emu_file said that it creates a module. It only returns the contents of the file.
The ToggleableHook docstring said that the hook can be toggled. The engine
does not read its enabled flag; the Hook objects in common check their own
flag. The class keeps the ctypes callback alive.
The event descriptions said how to use each event, and some were false. ProcessCreateEvent.pid said that the target may differ from the new process. MemProtectEvent said that more than one API records it; only VirtualProtectEx records it. FileReadEvent.size said 'requested/read'; the profiler adds the number of bytes read. Regenerate the report schema.
The descriptions said how to use each field, and some were false. ErrorInfo.count said that the value is the API call count. The value is the limit: max_api_calls for max_api_count, and the instruction limit for max_instructions. Report.emulation_total_runtime and Report.timestamp said that the clock starts at the start of the emulation. The clock starts again each time the emulator starts the run queue. Regenerate the report schema.
@williballenthin
williballenthin merged commit cf3a7b8 into master Nov 4, 2026
12 of 13 checks passed
@williballenthin
williballenthin deleted the doc-readme-architecture branch November 4, 2026 11:42
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