Skip to content

feat(ios-profiler): attributable memory leaks via malloc_stack_logging#351

Open
latekvo wants to merge 4 commits into
mainfrom
profiler-attributable-leaks
Open

feat(ios-profiler): attributable memory leaks via malloc_stack_logging#351
latekvo wants to merge 4 commits into
mainfrom
profiler-attributable-leaks

Conversation

@latekvo

@latekvo latekvo commented Jun 17, 2026

Copy link
Copy Markdown
Member

What

Opt-in malloc_stack_logging flag on native-profiler-start that makes iOS memory leaks attributable — they come back with a real responsible frame + library instead of <Call stack limit reached>.

Why

native-profiler-start attaches to the already-running app. iOS records malloc allocation backtraces only when MallocStackLogging is set at process launch, so an attached app has none: Instruments' Leaks scanner finds the leaked blocks but can't attribute them and emits the placeholder <Call stack limit reached> (that string is Instruments', not ours).

How

When malloc_stack_logging: true, the profiler cold-launches the app under xctrace instead of attaching:

xctrace record --template <Argent.tracetemplate> --device <udid> \
  --env MallocStackLogging=1 --output <…> --no-prompt \
  --launch -- <App.app>
  • .app path resolved via simctl get_app_container; the running instance is terminated first for a clean cold start.
  • The existing Leaks export / parser / render path is reused unchanged — no parser changes.
  • Default behaviour is untouched: attach to the running app (no relaunch, no overhead). The flag is opt-in because it restarts the app (loses state) and adds allocator overhead — for leak-attribution passes, not CPU/hang work.
  • The report relabels unattributable leaks with a hint to re-run with malloc_stack_logging: true, instead of surfacing the raw placeholder.

Evidence (real iPhone 16 sim, same scroll workload)

Before (attach) — every leak row:

responsible-frame="<Call stack limit reached>" responsible-library=""

After (--env MallocStackLogging=1 --launch) — same Leaks export:

leaked-object responsible-frame library
Malloc 1008 B ×3 itanium_demangle::OutputBuffer::grow(...) libc++abi
Malloc 48 B ×1 hermes::vm::JSTypedArrayBase::createBuffer(...) hermes

Tests

  • test/ios-instruments/malloc-stack-logging.test.ts — asserts launch + --env MallocStackLogging=1 + --launch -- <app> argv vs default --attach (and terminate-first / get_app_container).
  • test/ios-instruments/leak-attribution-render.test.ts — unattributable vs attributed leak rendering.
  • Full test/ios-instruments/ suite green (7 files / 39 tests); tsc --noEmit clean on tool-server.

Notes

Draft. Not yet exercised through a live tool-server end-to-end (running a modified tool-server would clobber the local ~/.argent/tool-server.json); the mechanism, the assembled argv, and the export→parse→render pipeline are each verified independently. Docs updated: IOS_PROFILER_REFERENCE.md and the argent-native-profiler skill.

latekvo added 2 commits June 17, 2026 12:43
native-profiler-start gains an opt-in `malloc_stack_logging` flag. When set, it
cold-launches the target app under xctrace with `--env MallocStackLogging=1`
instead of attaching, so Instruments records allocation backtraces and leaks
carry a real responsible frame + library. Without it leaks are detected but
unattributable — Instruments reports "<Call stack limit reached>".

Default behaviour is unchanged: attach to the running app, no relaunch, no
overhead. The report now relabels unattributable leaks with a hint to re-run
with malloc_stack_logging rather than surfacing the raw placeholder.

- split detectRunningApp into reusable AppInfo helpers
- resolve the .app bundle path via `simctl get_app_container` for --launch
- terminate the running instance first for a clean cold start
- tests: launch+env vs attach argv, and the unattributable-leak render
Reference + native-profiler skill now explain the attach-vs-cold-launch trade-off and how to get attributable leaks.
@latekvo latekvo force-pushed the profiler-attributable-leaks branch from e341e7d to 58a8d62 Compare June 17, 2026 10:47
latekvo added 2 commits June 18, 2026 18:11
…ingUserApps

enumerateRunningUserApps inlined the same simctl listapps | plutil | JSON.parse
block that the new getInstalledApps helper already provides. Route it through
the helper so the two can't drift.
…-leaks

# Conflicts:
#	packages/tool-server/src/utils/ios-profiler/render.ts
@latekvo latekvo marked this pull request as ready for review June 18, 2026 16:16
@latekvo latekvo requested review from filip131311, hubgan and stachbial and removed request for filip131311 and stachbial June 18, 2026 16:30
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