Skip to content

build: trace the symbol order file on windows x64 and arm64 - #37745

Merged
Jarred-Sumner merged 1 commit into
mainfrom
farm/b48ea817/windows-trace-order
Aug 19, 2026
Merged

build: trace the symbol order file on windows x64 and arm64#37745
Jarred-Sumner merged 1 commit into
mainfrom
farm/b48ea817/windows-trace-order

Conversation

@robobun

@robobun robobun commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator

Problem

Fix

  • Windows release links take /order:@linker.order (plus /ignore:4037 for traced names that no longer exist), and both windows targets get a trace-order step on their existing test images. The generated pipeline differs by exactly those two steps.
  • A new tracer runs the binary as its debuggee: with the process stopped at creation it writes a breakpoint over every function start; each hit restores the instruction, records the address and resumes there. Every function traps once and then runs unmodified, and the trace file format is the unix tracer's.
  • Names come from two maps the release link now writes into the profile zip: /map for names, /lldmap for where each input chunk landed. Only names at chunk starts count as functions, because the MSVC CRT leaves labels (Table, $LN123) on data inside .text and a breakpoint there killed the debuggee. Chunk starts are exactly what /order can move, so nothing orderable is lost.
  • Verification: the linker-order test covers the flag and map wiring, both map parsers against fixtures, and on windows the tracer itself against a C fixture (exact entry order, exit code, argv, ConPTY stdio). The generator was also run by hand on Server 2019 x64 and Windows 11 arm64 boxes, all eight workloads under the real tracer, and this PR's own build runs all four trace-order steps on the real binaries.

Background

  • Symbol order file: one function name per line; the linker lays those out first in .text so startup-hot code shares pages. Generated in CI, never committed. lld takes --symbol-ordering-file, Apple ld -order_file, lld-link /order:@file.
  • trace-order step: a soft-fail, non-PR CI step that downloads the build lane's unstripped bun-profile, runs a few workloads under a tracer that records first-entry order, and uploads the .order file the next build links with.
  • /order can only move a function that is in its own COMDAT section. Bun's C++ and the WebKit prebuilt use /Gy, rustc emits function sections, and LTO output is per-function, so all of bun's code already qualifies.
  • ELF and Mach-O binaries carry a symbol table that nm reads. A PE keeps its names in the PDB, so on Windows the linker's map files stand in as the address-to-name source.
  • A Windows debugger gets its debuggee's creation event before any of it has run, may write its memory, and receives every breakpoint exception, so nothing has to be loaded into the traced process.

no test proof · iteration 0 · Platform-specific test-only change; deferring to CI.

Original description

What

darwin-aarch64 and linux-x64 have a -trace-order step that traces the freshly built bun-profile on the native test fleet and publishes the symbol order file the next build links with. Windows had none of this: usesOrderFile() was false there, and the tracer is an LD_PRELOAD library. This adds it for windows-x64 and windows-aarch64.

  • Link (scripts/build/flags.ts): windows release links get /order:@<buildDir>/linker.order plus /ignore:4037 (the --no-warn-symbol-ordering counterpart), and linker.order becomes a link dependency so an inherited file relinks. Every function is its own COMDAT already (/Gy for bun's C++ and the WebKit prebuilt, function sections for rustc, per-function LTO output), which is what /order needs; checked against lld-link 21 that it orders statics and ThinLTO-internalized functions too.
  • Tracer (scripts/orderfile/functrace-windows.c): a debugger rather than an injected library. It starts the binary as its debuggee, writes a breakpoint (x64 INT3, arm64 BRK #0xF000) over every function start while the process is stopped at its creation event, and on each breakpoint exception restores the instruction, records the address and resumes the thread there. Everything else (the loader's initial breakpoint, exceptions the program handles itself) is passed through. With BUN_FUNCTRACE_TTY it starts the debuggee on a ConPTY and types our stdin into it, standing in for ptyrun.c. Same trace file format as functrace.c. Built on the test VM by generate.ts with clang-cl (on the images' machine PATH) or cl.
  • Symbols (scripts/orderfile/windows-symbols.ts): a PE has no symbol table, so the windows release link writes two maps that ship in the profile zip next to bun-profile.pdb: lld-link's MSVC-style /map (bun-profile.map, every symbol under the exact name /order takes) and /lldmap (bun-profile.linker-map, where every input chunk was placed). Only names at chunk starts are treated as functions. This turned out to be necessary, not cosmetic: the MSVC CRT defines symbols on data it keeps in .text. The arm64 memset keeps the byte table for its computed branch there under a symbol named Table, and MSVC-compiled code leaves a $LN123 symbol on every slot of the jump tables it emits after a function. A breakpoint written over those corrupts the table and the debuggee died in the first small memset / in printf. Chunk starts are also exactly the set /order can move, so the filter loses nothing orderable. Cost: roughly 10 to 15 MB more per compressed windows profile zip (the linux map compresses about 10x).
  • CI (.buildkite/ci.mjs): windows-x64 and windows-aarch64 join traceOrderTargets, tracing on the existing Server 2019 x64 and Windows 11 arm64 test images. The step runs under cmd.exe through vs-shell.ps1, like the test runner. Generating the pipeline as main before and after this change differs by exactly those two steps.
  • linkerMapPath moves from rust.ts to flags.ts next to the new symbolMapPath/linkerMapOutputs; bun.ts declares the maps to ninja and ci.ts packs them from that one list. verifyOrderFileApplied() now reads symbols through the same readTextSymbols() the generator uses, so it works on the windows build lanes too.

Verification

test/js/bun/perf/linker-order.test.ts (26 pass / 4 platform skips on each of linux x64, windows x64 and windows arm64):

  • flag, link-dependency and map-output wiring for both windows targets, and that the maps are written exactly where linkerMapOutputs() declares them;
  • parsers for both maps against fixtures shaped like real output (long names, folded functions, absolute symbols, demangled lld-map names, Table/$LN labels being dropped);
  • the tracer itself, windows only: functrace-fixture.c (now also compiled with clang-cl; it links the static CRT, so it crashes on arm64 without the chunk filter) must record f0..f31 in order, main, and after as distinct first entries with a child process spawned in between; the debuggee's exit code and argv come through and a starts file for another binary is refused; and a C probe reports console stdio 80 columns wide and the typed line under BUN_FUNCTRACE_TTY, pipe stdio otherwise. The unix tracer test now makes the same exact-order assertions.

Also run by hand on the two windows boxes (Server 2019 x64, Windows 11 24H2 arm64, both bootstrapped like the CI images): the whole of generate.ts against the installed bun.exe with maps faked down to its entry point, so all eight workloads run under the real tracer, ConPTY one included (cli-fixture.js reports tty=true 80x24 and exits on quit); and a 20k-function program to size the cost, about 65 to 70 us per breakpoint hit on both, so a full trace of bun is a few seconds. The unix path was re-run against a linux release build (13973 functions).

The commit message carries the opt-in tag ci.mjs honours outside main, so this PR's own build runs all four trace-order steps; the two windows ones are the end-to-end check on the real binaries and maps. The tag is deliberately not in this title or body.

Related: #37711 (linux-aarch64 step), #35328.

@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: a72d805c-070d-413f-969e-1bcb01a0f68b

📥 Commits

Reviewing files that changed from the base of the PR and between ddc9b3e and e3e0f32.

📒 Files selected for processing (11)
  • .buildkite/ci.mjs
  • scripts/build/bun.ts
  • scripts/build/ci.ts
  • scripts/build/compile.ts
  • scripts/build/flags.ts
  • scripts/build/rust.ts
  • scripts/orderfile/functrace-windows.c
  • scripts/orderfile/generate.ts
  • scripts/orderfile/windows-symbols.ts
  • test/js/bun/perf/functrace-fixture.c
  • test/js/bun/perf/linker-order.test.ts

Included review availability: Your plan provides up to 5 included reviews per hour; 2 remain after this review.


Walkthrough

Changes

Windows x64 and arm64 now support linker maps, /order files, symbol extraction, function-entry tracing, and CI execution. Build packaging includes platform-specific map outputs. Tests cover map parsing, tracing, console behavior, stdin, and exit-code propagation.

Windows linker-order support

Layer / File(s) Summary
Linker outputs and order flags
scripts/build/*
Build and packaging code now handles multiple linker-map outputs, Windows map files, and Windows release order files.
Windows symbols and order generation
scripts/orderfile/generate.ts, scripts/orderfile/windows-symbols.ts
Windows map files are parsed into symbol data. Order-file generation selects Windows tracing and cross-platform symbol loading.
Windows debugger tracer
scripts/orderfile/functrace-windows.c
A Windows x64/arm64 debugger tracer plants breakpoints, records function entries, manages console I/O, and returns the debuggee exit code.
Cross-platform tests and CI wiring
test/js/bun/perf/*, .buildkite/ci.mjs
Tests and Buildkite workflows cover Windows builds, tracing, map validation, console handling, stdin, and x64/arm64 targets.

Suggested reviewers: jarred-sumner

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly summarizes the main change: adding symbol-order tracing for Windows x64 and arm64.
Description check ✅ Passed The description explains the problem, implementation, verification, affected targets, and test results in sufficient detail.

Comment @coderabbitai help to get the list of available commands.

@robobun
robobun force-pushed the farm/b48ea817/windows-trace-order branch from e332286 to 13025d1 Compare August 12, 2026 04:54
@robobun

robobun commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator Author
Updated 9:49 PM PT - Aug 18th, 2026

@robobun, your commit e3e0f32 has 1 failures in Build #101034 (All Failures):


🧪   To try this PR locally:

bunx bun-pr 37745

That installs a local version of the PR into your bun-37745 executable, so you can run:

bun-37745 --bun

@robobun

robobun commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator Author

Status: ready for review.

Verified locally on a Server 2019 x64 box and a Windows 11 arm64 box (both bootstrapped like the CI images): test/js/bun/perf/linker-order.test.ts passes on both and on linux, and generate.ts runs all eight workloads under the tracer against the installed bun.exe on both.

End to end in CI: this branch's build (https://buildkite.com/bun/bun/builds/92842) ran windows-x64-trace-order and windows-aarch64-trace-order against the real bun-profile.exe and maps; both passed and published their .order files (10985 and 13233 functions), and the Windows test lanes ran the new tracer tests (26 pass on each). Details in the comment below.

CI state: rebased onto main (ddc9b3e) on Aug 19 at Jarred's request, see the last comment below. The rebased build is https://buildkite.com/bun/bun/builds/101034. 181 of 182 jobs passed, including all four trace-order steps, both windows build lanes and both windows test lanes. The one red job is the macOS 26 arm64 test shard: test/js/node/tls/node-tls-server.test.ts ("reports the reset that arrives while the socket is paused as ECONNRESET") times out on all four attempts. That test comes from #39600 and fails the same way on main's own build of ddc9b3e (https://buildkite.com/bun/bun/builds/101032). This PR changes build scripts only and does not change the macOS link. I reported the main failure separately. The other annotations on 101034 are retries that passed.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I reviewed this PR and didn't find any bugs. Because it adds a new ~540-line Windows debugger-based tracer (functrace-windows.c), changes how Windows release binaries are linked (/order, /map, /lldmap), and rewires CI pipeline steps and profile-zip packaging, a human look would still be worthwhile — especially with the end-to-end check depending on this branch's own Buildkite run.

What was reviewed:

  • usesOrderFile/linkDepends/linkerMapOutputs gating and that the new flags line up with the declared ninja outputs and packaged files.
  • functrace-windows.c: PE header bounds checks, the per-arch breakpoint encoding/PC handling, argv quoting against CommandLineToArgvW rules, ConPTY stdio handoff, and the debug-event loop's exception passthrough.
  • windows-symbols.ts parsers against the fixture shapes and the chunk-start filter dropping Table/$LN labels; readTextSymbols fallback ordering and its use in verifyOrderFileApplied.
  • .buildkite/ci.mjs cmd.exe command shape (|| exit /b 1, vs-shell.ps1) matches the existing verify-baseline/test-runner conventions.
Extended reasoning...

Overview

This PR extends the symbol-order-file infrastructure to Windows x64 and arm64. It adds a new debugger-based function-entry tracer written in C (scripts/orderfile/functrace-windows.c, ~540 lines) that plants INT3/BRK breakpoints via WriteProcessMemory and drives a ConPTY for the tty workload; a linker-map parser (scripts/orderfile/windows-symbols.ts) that stands in for nm on PE binaries; new lld-link flags (/order:@, /ignore:4037, /map:, /lldmap:) in scripts/build/flags.ts; refactored linkerMapOutputs()/linkDepends() consumed by bun.ts/compile.ts/ci.ts; two new traceOrderTargets in .buildkite/ci.mjs with a Windows-specific cmd.exe command block; and substantial test additions in test/js/bun/perf/linker-order.test.ts. The refactor also moves linkerMapPath from rust.ts to flags.ts and routes verifyOrderFileApplied() through the shared readTextSymbols().

Security risks

None user-facing. The tracer is a build-time tool that runs only in CI/local dev against the freshly built bun-profile.exe; it doesn't ship in the release binary. The PE-header parsing in map_image() bounds-checks e_lfanew and the section table before dereferencing, and the starts-file reader validates magic/version/count against the file size. The argv quoting in append_argument() follows the CommandLineToArgvW backslash-doubling rule. No new attack surface on the shipped runtime.

Level of scrutiny

High. This is ~1400 lines of net-new code across 11 files, including low-level Win32 debugging APIs (WaitForDebugEvent, Get/SetThreadContext, VirtualProtectEx, FlushInstructionCache), per-architecture breakpoint encoding, ConPTY lifecycle management with a quiescence heuristic, and changes to release-link flags and CI artifact packaging. The /order flag now affects every Windows release binary's .text layout. The trace-order step is soft-fail so a broken tracer can't fail main, but the linker-flag and packaging changes are on the critical path. The author notes the definitive end-to-end check is this PR's own Buildkite run (via the [generate symbol order] commit tag), which is still in progress.

Other factors

The test coverage is thorough for the pure-function pieces (flag wiring, map parsers, chunk-start filtering) and the tracer has three concurrent Windows-only integration tests, but the real-binary path (bun-profile.exe with its actual maps under all eight workloads) is only exercised by the CI trace-order step itself. The refactor of linkDepends() and linkerMapOutputs() is behavior-preserving for existing platforms per the test matrix, and the darwin .linker-map now goes through linkerMapPath() instead of an inline string (checked equivalent). The writesLinkerMap() gate for darwin no longer excludes asan (previously cfg.unix && !cfg.asan in packageAndUpload; now cfg.darwin unconditionally under release), which is a minor behavior change but darwin+asan isn't a shipped configuration. Given the scope, cross-arch subtlety, and pending CI verification, deferring to a human reviewer.

@robobun

robobun commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator Author

The trace-order steps in this branch's build (https://buildkite.com/bun/bun/builds/92842) passed on both Windows targets, against the real bun-profile.exe and the maps the build lanes shipped:

step functions bun -e alone tty workload generate.ts wall time artifact
windows-x64-trace-order (Server 2019) 10985 4528 +133 ~30s bun-windows-x64.order, 1.6 MiB
windows-aarch64-trace-order (Windows 11) 13233 5163 +216 ~21s bun-windows-aarch64.order, 1.9 MiB
linux-x64-trace-order, for comparison 10424 4655 +651 1.1 MiB

All eight workloads ran on both (the arm64 count is higher because that build is not LTO). Most of the wall time is compiling the tracer and reading the two maps; the tracing itself is a few seconds. The two maps grew each Windows profile zip by 7 MiB (x64 200 -> 207 MiB, arm64 167 -> 174 MiB, against main build 92794). Both windows-*-build-bun lanes linked with the new /order, /map and /lldmap flags.

On the review's note about darwin: yes, packageAndUpload now lists the map for any darwin release config, including asan, where before it skipped it. The darwin -Wl,-map flag has always been gated on release alone, so the file exists in that case and the list now matches what the link writes; CI has no darwin asan lane either way.

@robobun
robobun force-pushed the farm/b48ea817/windows-trace-order branch from 13025d1 to 8711726 Compare August 16, 2026 18:58
@robobun

robobun commented Aug 16, 2026

Copy link
Copy Markdown
Collaborator Author

Rebased onto current main (8711726). The only conflict was the import line at the top of scripts/build/rust.ts: main had added Abi/Arch/OS type imports there while this branch drops bunExeName (the one function that used it, linkerMapPath, moved to flags.ts); resolved by keeping the type imports and dropping bunExeName. No other changes. The generated pipeline still differs from main's by exactly the two Windows trace-order steps, and the linker-order tests pass on the rebased tree.

New build, again with all four trace-order steps: https://buildkite.com/bun/bun/builds/99438

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I reviewed this PR and the automated bug hunt found no issues. Given the scope — a new 540-line Windows debugger-based tracer, a two-map symbol resolution scheme, and build-lane wiring across half a dozen files — a human look at the design is still worthwhile.

What was reviewed:

  • The /order, /map, /lldmap flag wiring and linkDepends/linkerMapOutputs gating against the existing linux/darwin paths.
  • functrace-windows.c: PE header bounds checks, breakpoint arm/restore on both arches, the ConPTY std-handle dance, and append_argument's CommandLineToArgvW quoting.
  • windows-symbols.ts parsers against the fixture shapes (chunk-start filter dropping Table/$LN labels; image-base handling).
  • packageAndUpload's new linkerMapOutputs() list vs the darwin-asan case the author already noted — the -Wl,-map flag is release-gated so the file exists.
Extended reasoning...

Overview

This PR extends Bun's symbol-order-file infrastructure to Windows x64 and arm64. It adds /order:@linker.order to the Windows release link, a new debugger-based function-entry tracer (scripts/orderfile/functrace-windows.c, ~540 lines) that plants INT3/BRK breakpoints via WriteProcessMemory and drives the debuggee through the Win32 debug loop, a two-map symbol resolver (scripts/orderfile/windows-symbols.ts) that reads lld-link's /map and /lldmap outputs to recover function names and filter out non-function labels, and CI wiring (traceOrderTargets, getTraceOrderStep) to run the tracer on the Windows test fleet. It also refactors linkerMapPath/linkerMapOutputs from rust.ts into flags.ts and centralizes what bun.ts, compile.ts, and ci.ts each declare/pack. Test coverage in linker-order.test.ts is thorough (flag wiring, both map parsers against realistic fixtures, and on Windows the tracer end-to-end against a compiled C fixture including ConPTY stdio).

Security risks

None material. The tracer runs only in a soft-fail CI step against Bun's own freshly-built binary; it does not ship in the release. Inputs (the starts file, the two maps) are produced by the same build. The C code does bounds-check the PE headers before reading the section table and validates the starts file's magic/count against its size. append_argument implements the standard MSVCRT argv quoting rules correctly.

Level of scrutiny

Medium-high. This is build/CI tooling, not runtime code — the trace-order step is soft-fail and non-PR-only, and /order on an empty seeded file is a no-op — so a defect degrades to "Windows ships unordered" plus a Buildkite annotation, not a broken release. But it is a substantial new subsystem: a hand-written Win32 debugger that patches another process's code pages, a novel chunk-start heuristic for distinguishing functions from CRT data-in-.text labels, and cross-cutting refactors of the linker-map plumbing. The design choices (debugger vs injection, two maps vs PDB, chunk-start filter) are well-reasoned in the description and comments, but they are choices a maintainer should sign off on.

Other factors

The PR has strong evidence of working end-to-end: the author ran the full pipeline on this branch with the [generate symbol order] opt-in, both Windows trace-order steps passed and produced 11k–13k function order files, and linker-order.test.ts passed on all three test platforms. The one behavior change I checked outside Windows — packageAndUpload now lists the darwin map via linkerMapOutputs() for any release config including asan, where before it was gated on !asan — is benign because the -Wl,-map flag has always been release-only (so the file exists) and makeZip skips missing files anyway; the author already addressed this in a PR comment. No prior human review comments to track.

@Jarred-Sumner

Copy link
Copy Markdown
Collaborator

@robobun rebase

@robobun

robobun commented Aug 19, 2026

Copy link
Copy Markdown
Collaborator Author

Rebased onto current main (ddc9b3e) and pushed as e3e0f32. The PR is mergeable again.

One conflict, in scripts/build/bun.ts at the link call. Main added ...depChecks to the link's implicitInputs (the forbidUndefined stamps from #39472). This branch replaces the linkerMapOutput: line below it with linkerMapOutputs: linkerMapOutputs(cfg). The resolution keeps both. No other changes.

Checks on the rebased tree:

  • tsc -p scripts/build/tsconfig.json reports the same 11 pre-existing errors as main, none new.
  • bun scripts/build.ts --configure-only generates build.ninja.
  • test/js/bun/perf/linker-order.test.ts: 26 pass, 4 skip on linux (the skipped ones are the windows tracer tests, which the windows test lanes run).

Main did not touch .buildkite/, scripts/orderfile/ or test/js/bun/perf/ since the previous base, so the generated pipeline still differs from main's by the two windows trace-order steps only.

New build, again with all four trace-order steps: https://buildkite.com/bun/bun/builds/101034. All four passed on the rebased tree and published their order files: bun-windows-x64.order (10865 functions), bun-windows-aarch64.order (13126), bun-linux-x64.order (10336), bun-darwin-aarch64.order (9709). The two windows build lanes linked with the new flags and shipped both maps.

… symbol order]

Link the windows release binaries with lld-link's /order:@linker.order (plus
/ignore:4037, the counterpart of --no-warn-symbol-ordering), and add a
trace-order step for windows-x64 and windows-aarch64 that traces the
cross-built bun-profile.exe on the matching test fleet and publishes the
.order file the next build inherits, as the darwin-aarch64 and linux-x64
steps already do.

The tracer (scripts/orderfile/functrace-windows.c) is a small debugger
rather than an injected library: it starts the binary as its debuggee,
writes a breakpoint (INT3 on x64, BRK on arm64) over every function start
while the process is still stopped at its creation event, and on each
breakpoint exception restores the instruction, records the address and
resumes the thread there. With BUN_FUNCTRACE_TTY it starts the debuggee on
a pseudo console, standing in for ptyrun.c. It is built on the test VM with
clang-cl (or cl).

A PE has no symbol table, so the windows release link now writes two maps
that ship in the profile zip (scripts/orderfile/windows-symbols.ts): the
MSVC-style /map for the names /order takes, and lld's own /lldmap for which
of those names start an input chunk. The second is needed because the MSVC
CRT defines symbols on data it keeps in .text (arm64 memset's branch table,
the $LN labels on MSVC-emitted jump tables); a breakpoint there corrupts
the table. Only chunk starts are functions, which is also exactly what
/order can move.

linkerMapPath moves from rust.ts to flags.ts next to the new symbolMapPath
and linkerMapOutputs, which bun.ts uses to declare the maps to ninja and
ci.ts to pack them; verifyOrderFileApplied reads symbols through the same
readTextSymbols the generator uses.

The tag in the subject makes this branch's own CI build run the trace-order
steps (ci.mjs only adds them on main otherwise); Buildkite's message for a
PR build is the commit subject alone.
@robobun
robobun force-pushed the farm/b48ea817/windows-trace-order branch from 8711726 to e3e0f32 Compare August 19, 2026 04:22

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I reviewed this PR and didn't find any bugs. Given the scope — a new 541-line Windows debugger-tracer in C, linker-map parsing, and release-link flag changes across the build system — a human look is still warranted.

Checked: the /order//map//lldmap flag gating matches linkerMapOutputs() so ninja and the profile zip agree; linkDepends() refactor preserves the existing linux/darwin/freebsd outputs; packageAndUpload's switch to linkerMapOutputs() now includes the darwin-asan map, which the darwin -Wl,-map flag already writes (gated on release alone). The tracer's breakpoint-restore path handles the seen-twice race and passes non-breakpoint exceptions through with DBG_EXCEPTION_NOT_HANDLED.

Extended reasoning...

Overview

This PR extends the symbol-ordering-file infrastructure (previously linux + darwin-aarch64 only) to Windows x64 and arm64. It adds: a new native Windows debugger-based function tracer (scripts/orderfile/functrace-windows.c, 541 lines) that plants INT3/BRK breakpoints over every function start via WriteProcessMemory and records first-entry order; a linker-map parser (scripts/orderfile/windows-symbols.ts) that reads lld-link's /map and /lldmap outputs to recover function names (a PE has no symbol table); /order:@, /ignore:4037, /map:, and /lldmap: flags in scripts/build/flags.ts for the Windows release link; two new soft-fail trace-order CI steps in .buildkite/ci.mjs; and refactors of linkerMapPath/linkerMapOutputs/linkDepends to centralize the map-output declarations that bun.ts, compile.ts, and ci.ts all consume. The unix generate.ts path is refactored into a Tracer interface with per-platform buildUnixTracer/buildWindowsTracer. Tests in linker-order.test.ts grow substantially (flag wiring, both map parsers against fixtures, and on Windows the tracer against a compiled C fixture including ConPTY stdio).

Security risks

None user-facing. The tracer is a build-time CI tool that runs only in the soft-fail trace-order step against Bun's own bun-profile.exe; it never ships and never processes untrusted input. The PE header parsing in map_image() bounds-checks e_lfanew and the section table against the 4 KB read. The starts-file reader validates magic/version/count against file size before allocating. append_argument() implements the standard MSVC argv quoting rules. No changes touch runtime code paths.

Level of scrutiny

High — this warrants human review despite no bugs found. The new C tracer is non-trivial systems code (cross-arch breakpoint encoding, VirtualProtectEx/WriteProcessMemory/FlushInstructionCache, thread-context manipulation, ConPTY lifecycle with the Server 2019 output-drain quirk). The build-system changes alter release-link flags on two shipping targets and change what goes into the profile zip. The writesLinkerMap()/linkerMapOutputs() centralization touches darwin/linux gating that was previously duplicated across three files, and one behavioral edge (darwin+asan now lists the map where before it was skipped) was already noted and justified in the PR thread. These are the kind of cross-cutting infrastructure decisions a maintainer should sign off on.

Other factors

The PR is thoroughly self-documented and comes with end-to-end CI evidence (build 92842 ran both new Windows trace-order steps successfully, publishing 10985/13233-function order files). Test coverage is strong: fixture-based parser tests run everywhere, and the Windows tracer tests compile and run the real functrace-windows.c against a C fixture that would crash on arm64 without the chunk-start filter. Jarred has already engaged (rebase request), so human review is in progress. The linkDepends() refactor was traced to confirm freebsd, linux, darwin, and windows outputs are unchanged except for the intended addition of orderFilePath on windows. The verifyOrderFileApplied() change to use readTextSymbols() was checked to preserve the linux/darwin nm path while adding the windows map path.

@Jarred-Sumner
Jarred-Sumner merged commit 1f4eaa0 into main Aug 19, 2026
9 of 10 checks passed
@Jarred-Sumner
Jarred-Sumner deleted the farm/b48ea817/windows-trace-order branch August 19, 2026 07:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants