Skip to content
Open
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
1be19a6
windows: statically merge .node addons into --compile output
robobun May 1, 2026
8953eb4
[autofix.ci] apply automated fixes
autofix-ci[bot] May 1, 2026
6630c7b
pe: keep .pdata addon-relative and pass rva_base as RtlAddFunctionTab…
robobun May 1, 2026
fa0aef5
pe: harden addLinkedAddon against hostile addon images
robobun May 1, 2026
7c8905a
[autofix.ci] apply automated fixes
autofix-ci[bot] May 1, 2026
2b80130
LinkedNodeModule: serialise bind with a mutex and make it single-shot
robobun May 1, 2026
a1392a7
pe: validate AddressOfEntryPoint and .pdata bounds before host mutation
robobun May 1, 2026
ffc879d
BunProcess: hoist better_sqlite3 block above embedded-file handling
robobun May 1, 2026
67a2737
LinkedNodeModule: support implicit TLS in merged addons
robobun May 1, 2026
e8b0335
[autofix.ci] apply automated fixes
autofix-ci[bot] May 1, 2026
3c1388d
address review: stale state after failed bind, handle_token escape, m…
robobun May 1, 2026
7c613c5
drop in-process implicit-TLS: skip-merge addons with IMAGE_TLS_DIRECTORY
robobun May 1, 2026
6ae1b3a
LinkedNodeModule: bounds-check iat_rva in bindImports
robobun May 1, 2026
2b9ab65
test(compile-windows-linked-addon): match hashed asset name in .bunL key
robobun May 1, 2026
2a6449f
LinkedNodeModule: use path_buffer_pool for the lookup normalisation b…
robobun May 1, 2026
20d401a
pe: merge addons with an empty-template TLS directory; reject wrong m…
robobun May 1, 2026
9a111cb
pe: mirror addBunSection's 96-section cap in the reservation gates
robobun May 1, 2026
14ca581
BunProcess/napi: don't attach NapiModuleMeta for linked addons on the…
robobun May 1, 2026
68d3d86
napi: replace reindented finalizer TODO with the actual rationale
robobun May 1, 2026
155b2cb
pe: size .pdata entries per machine; document DLL_THREAD_ATTACH gap
robobun May 1, 2026
2843f59
test(napi): loosen --compile temp-file assertion to match best-effort…
robobun May 1, 2026
1304aa9
test(compile-windows-linked-addon): match fixture machine to host arc…
robobun May 1, 2026
e27adad
pe/LinkedNodeModule: span-check VirtualProtect, defer not errdefer, d…
robobun May 1, 2026
68e9d13
test(napi): assert strict temp-file reduction, not a per-arch count
robobun May 1, 2026
53e569e
LinkedNodeModule: span-check .pdata before RtlAddFunctionTable; gate …
robobun May 1, 2026
f2afb87
pe: defer-not-errdefer for collectImports' inner entries list
robobun May 1, 2026
5d90a80
LinkedNodeModule: use Environment.isAarch64 instead of inline @import…
robobun May 1, 2026
5b41ad2
test(napi): drop dead platform conditionals in Windows-only disabled-…
robobun May 1, 2026
9a5272c
merge main: src/ restructure
robobun May 4, 2026
12dcb20
pe: skip-merge addons that import _CxxThrowException
robobun May 4, 2026
8ea590b
LinkedNodeModule: hold lock across DLHandleMap.add() for the binder t…
robobun May 4, 2026
c98f201
Hoist binderLockGuard scope-exit above RETURN_IF_EXCEPTION
robobun May 4, 2026
907cf20
merge main: Rust rewrite
robobun Jun 5, 2026
7e0378c
Port PE linked-addon merge and runtime binder to Rust
robobun Jun 5, 2026
987f74a
[autofix.ci] apply automated fixes
autofix-ci[bot] Jun 5, 2026
1bd1065
LinkedNodeModule: drop unvalidated reserve on blob entry count
robobun Jun 5, 2026
d52c88a
merge main
robobun Jun 16, 2026
e9566ab
Normalise dest_path to / when building the .bunL key
robobun Jun 16, 2026
51a0c6c
[autofix.ci] apply automated fixes
autofix-ci[bot] Jun 16, 2026
f146d5b
Merge branch 'main' into farm/f29511e0/pe-static-link-node-addons
robobun Jun 17, 2026
505f30f
LinkedNodeModule: span-check entry_point before the DllMain call
robobun Jun 17, 2026
94f9a81
LinkedNodeModule: span-check the export RVAs before returning them
robobun Jun 17, 2026
cad5f4f
merge main: .zig reference sources removed
robobun Jun 25, 2026
5bba211
pe_testing: fix stale $newZigFunction reference in doc comment
robobun Jun 25, 2026
922de96
merge main
robobun Jul 9, 2026
2b3812f
merge main
robobun Aug 14, 2026
a0d3f41
pe: restore validate() for the adversarial testing hook
robobun Aug 14, 2026
c5a9f29
[autofix.ci] apply automated fixes
autofix-ci[bot] Aug 14, 2026
c7be584
pe: share the section append path, trim comments, fix review findings
robobun Aug 14, 2026
97db374
pe: drop the unused BUN_PLUGIN_NAME export from the linked addon record
robobun Aug 14, 2026
a172dc4
pe: merge addon unwind tables into the exe's exception directory
robobun Aug 14, 2026
f321714
merge main
robobun Aug 14, 2026
0badeeb
pe: resolve handlers for chained unwind entries and collided unwinds
robobun Aug 14, 2026
c7c2fd1
sys(windows): shorten the exe_image_range docs
robobun Aug 14, 2026
4624c4a
pe: span-check the displaced handler before the trampoline calls it
robobun Aug 14, 2026
343aa54
pe: drop a redundant line from the find_redirect docs
robobun Aug 14, 2026
6838c61
merge main
robobun Aug 17, 2026
4198cfe
merge main
robobun Aug 18, 2026
9ecdd1e
pe: model-based fuzzer for add_linked_addon, concurrent Worker dlopen…
robobun Aug 18, 2026
372b622
pe: document that a merged addon never sees DLL_PROCESS_DETACH
robobun Aug 18, 2026
94666cd
napi tests: per-call jump buffers, C++ exception addon in the unwind …
robobun Aug 18, 2026
1e2a2bf
pe: make C++ throws work in merged addons, present chained records co…
robobun Aug 19, 2026
29aa75a
pe: refuse a SizeOfImage that is not 4-byte aligned, update the C++ t…
robobun Aug 19, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 94 additions & 1 deletion src/StandaloneModuleGraph.zig
Original file line number Diff line number Diff line change
Expand Up @@ -668,7 +668,73 @@ pub const StandaloneModuleGraph = struct {
}
};

pub fn inject(bytes: []const u8, self_exe: [:0]const u8, inject_options: InjectOptions, target: *const CompileTarget) bun.FD {
/// For each napi `.node` in `output_files` that is a valid PE image,
/// merge its sections into `pe_file` via `PEFile.addLinkedAddon` and
/// then append a `.bunL` section carrying the runtime metadata.
///
/// Any addon that cannot be merged safely (static TLS, malformed
/// headers, not a PE at all) is silently skipped; its raw bytes remain
/// in the `.bun` module graph so `process.dlopen` can fall back to the
/// extract-to-tempfile path. This keeps `--compile` behaviourally
/// identical whether or not the merge succeeds.
fn linkNativeAddonsForWindows(
pe_file: *bun.pe.PEFile,
output_files: []const bun.options.OutputFile,
module_prefix: []const u8,
) !void {
if (bun.feature_flag.BUN_FEATURE_FLAG_DISABLE_PE_ADDON_LINK.get()) return;

var arena = bun.ArenaAllocator.init(bun.default_allocator);
defer arena.deinit();
const alloc = arena.allocator();

var addons = std.array_list.Managed(bun.pe.PEFile.LinkedAddon).init(alloc);
var idx: u32 = 0;
for (output_files) |*of| {
if (of.loader != .napi) continue;
if (of.value != .buffer) continue;
if (!of.output_kind.isFileInStandaloneMode()) continue;
const contents = of.value.buffer.bytes;
if (!bun.pe.utils.isPE(contents)) continue;

// Must match `toBytes` exactly so the runtime lookup key
// (the `$bunfs/...` virtual path passed to `process.dlopen`)
// lines up with `LinkedAddon.name`.
const dest_path = bun.strings.removeLeadingDotSlash(of.dest_path);
const vpath = try std.fmt.allocPrint(alloc, "{s}{s}", .{ module_prefix, dest_path });

const linked = pe_file.addLinkedAddon(alloc, contents, idx, vpath) catch |err| switch (err) {
// Running out of header slots for more sections is not a
// build failure — the remaining addons just use the
// tempfile fallback at runtime.
error.InsufficientHeaderSpace => break,
else => return err,
} orelse continue;
try addons.append(linked);
idx += 1;
}

if (addons.items.len == 0) return;

const blob = try bun.pe.PEFile.serializeLinkedAddons(alloc, addons.items);
pe_file.addLinkedAddonSection(blob) catch |err| switch (err) {
// Same reasoning as above: without `.bunL` the runtime has
// nothing to look up and every addon takes the tempfile
// fallback, which is fine. Without `.bun` the build is
// useless, so leave the last slot for it.
error.InsufficientHeaderSpace => return,
else => return err,
};
}

pub fn inject(
bytes: []const u8,
self_exe: [:0]const u8,
inject_options: InjectOptions,
target: *const CompileTarget,
output_files: []const bun.options.OutputFile,
module_prefix: []const u8,
) bun.FD {
var buf: bun.PathBuffer = undefined;
var zname: [:0]const u8 = bun.fs.FileSystem.tmpname("bun-build", &buf, @as(u64, @bitCast(std.time.milliTimestamp()))) catch |err| {
Output.prettyErrorln("<r><red>error<r><d>:<r> failed to get temporary file name: {s}", .{@errorName(err)});
Expand Down Expand Up @@ -871,6 +937,31 @@ pub const StandaloneModuleGraph = struct {
return bun.invalid_fd;
};
defer pe_file.deinit();

// The Authenticode signature sits in an overlay past the
// last section. Appending addon sections there first would
// overwrite it and then make addBunSection's later strip
// trip SecurityDirInsideImage, so strip up-front.
// (addBunSection below strips again, which is a no-op on
// an already-unsigned image.)
pe_file.stripAuthenticode(.{ .require_overlay = true, .recompute_checksum = false }) catch |err| {
Output.prettyErrorln("Error stripping PE signature: {}", .{err});
cleanup(zname, cloned_executable_fd);
return bun.invalid_fd;
};

// Statically merge embedded .node addons so the compiled
// exe can `process.dlopen` them without writing a temp
// file and calling `LoadLibraryExW`. Must happen before
// `addBunSection` so the section order is
// [.bnN ...][.bunL][.bun] and the checksum is computed
// over the final image.
linkNativeAddonsForWindows(pe_file, output_files, module_prefix) catch |err| {
Output.prettyErrorln("Error linking native addon into PE file: {}", .{err});
cleanup(zname, cloned_executable_fd);
return bun.invalid_fd;
};

// Always strip authenticode when adding .bun section for --compile
pe_file.addBunSection(bytes, .strip_always) catch |err| {
Output.prettyErrorln("Error adding Bun section to PE file: {}", .{err});
Expand Down Expand Up @@ -1178,6 +1269,8 @@ pub const StandaloneModuleGraph = struct {
self_exe,
windows_options,
target,
output_files,
module_prefix,
);
defer if (fd != bun.invalid_fd) fd.close();
bun.debugAssert(fd.kind == .system);
Expand Down
Loading
Loading