Skip to content

Fix callback chaining for _initterm function table walking#277

Draft
williballenthin wants to merge 5 commits intomasterfrom
worktree-fix-initterm-107
Draft

Fix callback chaining for _initterm function table walking#277
williballenthin wants to merge 5 commits intomasterfrom
worktree-fix-initterm-107

Conversation

@williballenthin
Copy link
Collaborator

Summary

  • Fixed callback chaining in winemu.py: The API_CALLBACK_HANDLER_ADDR handler previously popped the first callback and returned to the original caller, silently dropping remaining queued callbacks. It now properly chains: when one callback completes, it sets up the next callback's stack frame and redirects execution there, only returning to the original caller after the last callback finishes.
  • Implemented _initterm/_initterm_e table parsing: Both functions now read the function pointer table between pfbegin and pfend and report the non-NULL entries in the API event args. The functions still return immediately (CRT init functions generally rely on environment state the emulator doesn't provide), but the table contents are now visible to analysts.
  • Added tests validating table parsing and non-regression of existing emulation behavior.

Closes #107

Test plan

  • test_initterm_reports_function_table — verifies function pointers appear in API event args
  • test_initterm_does_not_crash_emulation — verifies main() still executes after _initterm
  • Existing test suite passes (46 passed, 2 pre-existing failures from missing capa-testfiles)

🤖 Generated with Claude Code

williballenthin and others added 4 commits March 9, 2026 11:34
Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
The callback handler in winemu.py only executed the first queued
callback before returning to the original caller, silently dropping
any remaining callbacks. This made setup_callback() unusable for APIs
that need to invoke multiple function pointers in sequence (like
_initterm). The handler now properly chains callbacks: when one
completes, it sets up the next instead of returning early.

_initterm and _initterm_e now parse the function pointer table and
report the entries in the API event args for analyst visibility.

Closes #107
@google-cla
Copy link

google-cla bot commented Mar 9, 2026

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@williballenthin williballenthin force-pushed the worktree-fix-initterm-107 branch from 9b33979 to 0c35735 Compare March 9, 2026 15:10
Comment on lines 1626 to 1640
elif address == winemu.API_CALLBACK_HANDLER_ADDR:
run = self.get_current_run()
if run.api_callbacks:
pc, orig_func, args = run.api_callbacks.pop(0)
self.do_call_return(len(args), pc)
ret, orig_func, args = run.api_callbacks.pop(0)
if run.api_callbacks:
next_ret, next_func, next_args = run.api_callbacks[0]
if next_ret is None:
run.api_callbacks[0] = (ret, next_func, next_args)
sp = self.get_stack_ptr()
self.set_func_args(sp, winemu.API_CALLBACK_HANDLER_ADDR, *next_args)
self.set_pc(next_func)
else:
self.do_call_return(len(args), ret)
self._unset_emu_hooks()
return True
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this nesting doesn't seem correct. why is if run.api_callbacks tested twice?

Comment on lines 1622 to 1623
if address == winemu.SEH_RETURN_ADDR:
self.continue_seh()
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i wonder if maybe we should pull this whole block into its own self._handle_invalid_mem_exec so we can document its behavior a bit better. this function is getting a bit long.

return rv
funcs = self._read_func_table(pfbegin, pfend)
if funcs:
argv.append(", ".join(f"0x{f:x}" for f in funcs))
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is this doing?? its extending argv with a string? at least need some inline documentation cause i don't get it

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i don't think these tests add enough value, lets remove the file.

@williballenthin williballenthin marked this pull request as draft March 10, 2026 08:49
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.

_initterm function table

2 participants