mirror of
https://github.com/oven-sh/bun
synced 2026-02-10 02:48:50 +00:00
Fixes ENG-21287
Build times, from `bun run build && echo '//' >> src/main.zig && time
bun run build`
|Platform|0.14.1|0.15.2|Speedup|
|-|-|-|-|
|macos debug asan|126.90s|106.27s|1.19x|
|macos debug noasan|60.62s|50.85s|1.19x|
|linux debug asan|292.77s|241.45s|1.21x|
|linux debug noasan|146.58s|130.94s|1.12x|
|linux debug use_llvm=false|n/a|78.27s|1.87x|
|windows debug asan|177.13s|142.55s|1.24x|
Runtime performance:
- next build memory usage may have gone up by 5%. Otherwise seems the
same. Some code with writers may have gotten slower, especially one
instance of a counting writer and a few instances of unbuffered writers
that now have vtable overhead.
- File size reduced by 800kb (from 100.2mb to 99.4mb)
Improvements:
- `@export` hack is no longer needed for watch
- native x86_64 backend for linux builds faster. to use it, set use_llvm
false and no_link_obj false. also set `ASAN_OPTIONS=detect_leaks=0`
otherwise it will spam the output with tens of thousands of lines of
debug info errors. may need to use the zig lldb fork for debugging.
- zig test-obj, which we will be able to use for zig unit tests
Still an issue:
- false 'dependency loop' errors remain in watch mode
- watch mode crashes observed
Follow-up:
- [ ] search `comptime Writer: type` and `comptime W: type` and remove
- [ ] remove format_mode in our zig fork
- [ ] remove deprecated.zig autoFormatLabelFallback
- [ ] remove deprecated.zig autoFormatLabel
- [ ] remove deprecated.BufferedWriter and BufferedReader
- [ ] remove override_no_export_cpp_apis as it is no longer needed
- [ ] css Parser(W) -> Parser, and remove all the comptime writer: type
params
- [ ] remove deprecated writer fully
Files that add lines:
```
649 src/deprecated.zig
167 scripts/pack-codegen-for-zig-team.ts
54 scripts/cleartrace-impl.js
46 scripts/cleartrace.ts
43 src/windows.zig
18 src/fs.zig
17 src/bun.js/ConsoleObject.zig
16 src/output.zig
12 src/bun.js/test/debug.zig
12 src/bun.js/node/node_fs.zig
8 src/env_loader.zig
7 src/css/printer.zig
7 src/cli/init_command.zig
7 src/bun.js/node.zig
6 src/string/escapeRegExp.zig
6 src/install/PnpmMatcher.zig
5 src/bun.js/webcore/Blob.zig
4 src/crash_handler.zig
4 src/bun.zig
3 src/install/lockfile/bun.lock.zig
3 src/cli/update_interactive_command.zig
3 src/cli/pack_command.zig
3 build.zig
2 src/Progress.zig
2 src/install/lockfile/lockfile_json_stringify_for_debugging.zig
2 src/css/small_list.zig
2 src/bun.js/webcore/prompt.zig
1 test/internal/ban-words.test.ts
1 test/internal/ban-limits.json
1 src/watcher/WatcherTrace.zig
1 src/transpiler.zig
1 src/shell/builtin/cp.zig
1 src/js_printer.zig
1 src/io/PipeReader.zig
1 src/install/bin.zig
1 src/css/selectors/selector.zig
1 src/cli/run_command.zig
1 src/bun.js/RuntimeTranspilerStore.zig
1 src/bun.js/bindings/JSRef.zig
1 src/bake/DevServer.zig
```
Files that remove lines:
```
-1 src/test/recover.zig
-1 src/sql/postgres/SocketMonitor.zig
-1 src/sql/mysql/MySQLRequestQueue.zig
-1 src/sourcemap/CodeCoverage.zig
-1 src/css/values/color_js.zig
-1 src/compile_target.zig
-1 src/bundler/linker_context/convertStmtsForChunk.zig
-1 src/bundler/bundle_v2.zig
-1 src/bun.js/webcore/blob/read_file.zig
-1 src/ast/base.zig
-2 src/sql/postgres/protocol/ArrayList.zig
-2 src/shell/builtin/mkdir.zig
-2 src/install/PackageManager/patchPackage.zig
-2 src/install/PackageManager/PackageManagerDirectories.zig
-2 src/fmt.zig
-2 src/css/declaration.zig
-2 src/css/css_parser.zig
-2 src/collections/baby_list.zig
-2 src/bun.js/bindings/ZigStackFrame.zig
-2 src/ast/E.zig
-3 src/StandaloneModuleGraph.zig
-3 src/deps/picohttp.zig
-3 src/deps/libuv.zig
-3 src/btjs.zig
-4 src/threading/Futex.zig
-4 src/shell/builtin/touch.zig
-4 src/meta.zig
-4 src/install/lockfile.zig
-4 src/css/selectors/parser.zig
-5 src/shell/interpreter.zig
-5 src/css/error.zig
-5 src/bun.js/web_worker.zig
-5 src/bun.js.zig
-6 src/cli/test_command.zig
-6 src/bun.js/VirtualMachine.zig
-6 src/bun.js/uuid.zig
-6 src/bun.js/bindings/JSValue.zig
-9 src/bun.js/test/pretty_format.zig
-9 src/bun.js/api/BunObject.zig
-14 src/install/install_binding.zig
-14 src/fd.zig
-14 src/bun.js/node/path.zig
-14 scripts/pack-codegen-for-zig-team.sh
-17 src/bun.js/test/diff_format.zig
```
`git diff --numstat origin/main...HEAD | awk '{ print ($1-$2)"\t"$3 }' |
sort -rn`
---------
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Dylan Conway <dylan.conway567@gmail.com>
Co-authored-by: Meghan Denny <meghan@bun.com>
Co-authored-by: tayor.fish <contact@taylor.fish>
747 lines
30 KiB
Zig
747 lines
30 KiB
Zig
// Windows PE sections use standard file alignment (typically 512 bytes)
|
|
// No special 16KB alignment needed like macOS code signing
|
|
|
|
// New error types for PE manipulation
|
|
pub const Error = error{
|
|
OutOfBounds,
|
|
BadAlignment,
|
|
Overflow,
|
|
InvalidPEFile,
|
|
InvalidDOSSignature,
|
|
InvalidPESignature,
|
|
UnsupportedPEFormat,
|
|
InsufficientHeaderSpace,
|
|
TooManySections,
|
|
SectionExists,
|
|
InputIsSigned,
|
|
InvalidSecurityDirectory,
|
|
SecurityDirInsideImage,
|
|
UnexpectedOverlayPresent,
|
|
InvalidSectionData,
|
|
BunSectionNotFound,
|
|
InvalidBunSection,
|
|
InsufficientSpace,
|
|
SizeOfImageMismatch,
|
|
};
|
|
|
|
// Enums for strip modes and options
|
|
pub const StripMode = enum { none, strip_if_signed, strip_always };
|
|
pub const StripOpts = struct {
|
|
require_overlay: bool = true,
|
|
recompute_checksum: bool = true,
|
|
};
|
|
|
|
/// Windows PE Binary manipulation for codesigning standalone executables
|
|
pub const PEFile = struct {
|
|
data: std.array_list.Managed(u8),
|
|
allocator: Allocator,
|
|
// Store offsets instead of pointers to avoid invalidation after resize
|
|
dos_header_offset: usize,
|
|
pe_header_offset: usize,
|
|
optional_header_offset: usize,
|
|
section_headers_offset: usize,
|
|
num_sections: u16,
|
|
// Cached values from init
|
|
first_raw: u32,
|
|
last_file_end: u32,
|
|
last_va_end: u32,
|
|
|
|
const DOSHeader = extern struct {
|
|
e_magic: u16, // Magic number
|
|
e_cblp: u16, // Bytes on last page of file
|
|
e_cp: u16, // Pages in file
|
|
e_crlc: u16, // Relocations
|
|
e_cparhdr: u16, // Size of header in paragraphs
|
|
e_minalloc: u16, // Minimum extra paragraphs needed
|
|
e_maxalloc: u16, // Maximum extra paragraphs needed
|
|
e_ss: u16, // Initial relative SS value
|
|
e_sp: u16, // Initial SP value
|
|
e_csum: u16, // Checksum
|
|
e_ip: u16, // Initial IP value
|
|
e_cs: u16, // Initial relative CS value
|
|
e_lfarlc: u16, // Address of relocation table
|
|
e_ovno: u16, // Overlay number
|
|
e_res: [4]u16, // Reserved words
|
|
e_oemid: u16, // OEM identifier (for e_oeminfo)
|
|
e_oeminfo: u16, // OEM information; e_oemid specific
|
|
e_res2: [10]u16, // Reserved words
|
|
e_lfanew: u32, // File address of new exe header
|
|
};
|
|
|
|
const PEHeader = extern struct {
|
|
signature: u32, // PE signature
|
|
machine: u16, // Machine type
|
|
number_of_sections: u16, // Number of sections
|
|
time_date_stamp: u32, // Time/date stamp
|
|
pointer_to_symbol_table: u32, // Pointer to symbol table
|
|
number_of_symbols: u32, // Number of symbols
|
|
size_of_optional_header: u16, // Size of optional header
|
|
characteristics: u16, // Characteristics
|
|
};
|
|
|
|
const OptionalHeader64 = extern struct {
|
|
magic: u16, // Magic number
|
|
major_linker_version: u8, // Major linker version
|
|
minor_linker_version: u8, // Minor linker version
|
|
size_of_code: u32, // Size of code
|
|
size_of_initialized_data: u32, // Size of initialized data
|
|
size_of_uninitialized_data: u32, // Size of uninitialized data
|
|
address_of_entry_point: u32, // Address of entry point
|
|
base_of_code: u32, // Base of code
|
|
image_base: u64, // Image base
|
|
section_alignment: u32, // Section alignment
|
|
file_alignment: u32, // File alignment
|
|
major_operating_system_version: u16, // Major OS version
|
|
minor_operating_system_version: u16, // Minor OS version
|
|
major_image_version: u16, // Major image version
|
|
minor_image_version: u16, // Minor image version
|
|
major_subsystem_version: u16, // Major subsystem version
|
|
minor_subsystem_version: u16, // Minor subsystem version
|
|
win32_version_value: u32, // Win32 version value
|
|
size_of_image: u32, // Size of image
|
|
size_of_headers: u32, // Size of headers
|
|
checksum: u32, // Checksum
|
|
subsystem: u16, // Subsystem
|
|
dll_characteristics: u16, // DLL characteristics
|
|
size_of_stack_reserve: u64, // Size of stack reserve
|
|
size_of_stack_commit: u64, // Size of stack commit
|
|
size_of_heap_reserve: u64, // Size of heap reserve
|
|
size_of_heap_commit: u64, // Size of heap commit
|
|
loader_flags: u32, // Loader flags
|
|
number_of_rva_and_sizes: u32, // Number of RVA and sizes
|
|
data_directories: [16]DataDirectory, // Data directories
|
|
};
|
|
|
|
const DataDirectory = extern struct {
|
|
virtual_address: u32,
|
|
size: u32,
|
|
};
|
|
|
|
const SectionHeader = extern struct {
|
|
name: [8]u8, // Section name
|
|
virtual_size: u32, // Virtual size
|
|
virtual_address: u32, // Virtual address
|
|
size_of_raw_data: u32, // Size of raw data
|
|
pointer_to_raw_data: u32, // Pointer to raw data
|
|
pointer_to_relocations: u32, // Pointer to relocations
|
|
pointer_to_line_numbers: u32, // Pointer to line numbers
|
|
number_of_relocations: u16, // Number of relocations
|
|
number_of_line_numbers: u16, // Number of line numbers
|
|
characteristics: u32, // Characteristics
|
|
};
|
|
|
|
const PE_SIGNATURE = 0x00004550; // "PE\0\0"
|
|
const DOS_SIGNATURE = 0x5A4D; // "MZ"
|
|
const OPTIONAL_HEADER_MAGIC_64 = 0x020B;
|
|
|
|
// Section characteristics
|
|
const IMAGE_SCN_CNT_CODE = 0x00000020;
|
|
const IMAGE_SCN_CNT_INITIALIZED_DATA = 0x00000040;
|
|
const IMAGE_SCN_MEM_READ = 0x40000000;
|
|
const IMAGE_SCN_MEM_WRITE = 0x80000000;
|
|
const IMAGE_SCN_MEM_EXECUTE = 0x20000000;
|
|
|
|
// Directory indices and DLL characteristics
|
|
const IMAGE_DIRECTORY_ENTRY_SECURITY: usize = 4;
|
|
const IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY: u16 = 0x0080;
|
|
|
|
// Section name constant for exact comparison
|
|
const BUN_SECTION_NAME = [_]u8{ '.', 'b', 'u', 'n', 0, 0, 0, 0 };
|
|
|
|
// Safe access helpers for unaligned views
|
|
fn viewAtConst(comptime T: type, buf: []const u8, off: usize) !*align(1) const T {
|
|
if (off + @sizeOf(T) > buf.len) return error.OutOfBounds;
|
|
return @ptrCast(buf[off .. off + @sizeOf(T)].ptr);
|
|
}
|
|
|
|
fn viewAtMut(comptime T: type, buf: []u8, off: usize) !*align(1) T {
|
|
if (off + @sizeOf(T) > buf.len) return error.OutOfBounds;
|
|
return @ptrCast(buf[off .. off + @sizeOf(T)].ptr);
|
|
}
|
|
|
|
fn isPow2(x: u32) bool {
|
|
return x != 0 and (x & (x - 1)) == 0;
|
|
}
|
|
|
|
fn alignUpU32(v: u32, a: u32) !u32 {
|
|
if (a == 0) return v;
|
|
if (!isPow2(a)) return error.BadAlignment;
|
|
const add = a - 1;
|
|
if (v > std.math.maxInt(u32) - add) return error.Overflow;
|
|
return (v + add) & ~add;
|
|
}
|
|
|
|
fn alignUpUsize(v: usize, a: usize) !usize {
|
|
if (a == 0) return v;
|
|
if ((a & (a - 1)) != 0) return error.BadAlignment;
|
|
const add = a - 1;
|
|
if (v > std.math.maxInt(usize) - add) return error.Overflow;
|
|
return (v + add) & ~add;
|
|
}
|
|
|
|
// Helper methods to safely access headers using unaligned pointers
|
|
fn getDosHeader(self: *const PEFile) !*align(1) const DOSHeader {
|
|
return viewAtConst(DOSHeader, self.data.items, self.dos_header_offset);
|
|
}
|
|
|
|
fn getDosHeaderMut(self: *PEFile) !*align(1) DOSHeader {
|
|
return viewAtMut(DOSHeader, self.data.items, self.dos_header_offset);
|
|
}
|
|
|
|
fn getPEHeader(self: *const PEFile) !*align(1) const PEHeader {
|
|
return viewAtConst(PEHeader, self.data.items, self.pe_header_offset);
|
|
}
|
|
|
|
fn getPEHeaderMut(self: *PEFile) !*align(1) PEHeader {
|
|
return viewAtMut(PEHeader, self.data.items, self.pe_header_offset);
|
|
}
|
|
|
|
fn getOptionalHeader(self: *const PEFile) !*align(1) const OptionalHeader64 {
|
|
return viewAtConst(OptionalHeader64, self.data.items, self.optional_header_offset);
|
|
}
|
|
|
|
fn getOptionalHeaderMut(self: *PEFile) !*align(1) OptionalHeader64 {
|
|
return viewAtMut(OptionalHeader64, self.data.items, self.optional_header_offset);
|
|
}
|
|
|
|
fn getSectionHeaders(self: *const PEFile) ![]align(1) const SectionHeader {
|
|
const start = self.section_headers_offset;
|
|
const size = @sizeOf(SectionHeader) * self.num_sections;
|
|
if (start + size > self.data.items.len) return error.OutOfBounds;
|
|
const ptr: [*]align(1) const SectionHeader = @ptrCast(self.data.items[start..].ptr);
|
|
return ptr[0..self.num_sections];
|
|
}
|
|
|
|
fn getSectionHeadersMut(self: *PEFile) ![]align(1) SectionHeader {
|
|
const start = self.section_headers_offset;
|
|
const size = @sizeOf(SectionHeader) * self.num_sections;
|
|
if (start + size > self.data.items.len) return error.OutOfBounds;
|
|
const ptr: [*]align(1) SectionHeader = @ptrCast(self.data.items[start..].ptr);
|
|
return ptr[0..self.num_sections];
|
|
}
|
|
|
|
pub fn init(allocator: Allocator, pe_data: []const u8) !*PEFile {
|
|
// 1. Reserve capacity as before
|
|
var data = try std.array_list.Managed(u8).initCapacity(allocator, pe_data.len + 64 * 1024);
|
|
try data.appendSlice(pe_data);
|
|
|
|
const self = try allocator.create(PEFile);
|
|
errdefer allocator.destroy(self);
|
|
|
|
// 2. Validate DOS header
|
|
if (data.items.len < @sizeOf(DOSHeader)) {
|
|
return error.InvalidPEFile;
|
|
}
|
|
|
|
const dos_header = try viewAtConst(DOSHeader, data.items, 0);
|
|
if (dos_header.e_magic != DOS_SIGNATURE) {
|
|
return error.InvalidDOSSignature;
|
|
}
|
|
|
|
// Bound e_lfanew against file size, not 0x1000
|
|
if (dos_header.e_lfanew < @sizeOf(DOSHeader)) {
|
|
return error.InvalidPEFile;
|
|
}
|
|
if (dos_header.e_lfanew > data.items.len -| @sizeOf(PEHeader)) {
|
|
return error.InvalidPEFile;
|
|
}
|
|
|
|
// 3. Read PE header via viewAtMut
|
|
const pe_off = dos_header.e_lfanew;
|
|
const pe_header = try viewAtMut(PEHeader, data.items, pe_off);
|
|
if (pe_header.signature != PE_SIGNATURE) {
|
|
return error.InvalidPESignature;
|
|
}
|
|
|
|
// 4. Compute optional_header_offset
|
|
const optional_header_offset = pe_off + @sizeOf(PEHeader);
|
|
if (data.items.len < optional_header_offset + pe_header.size_of_optional_header) {
|
|
return error.InvalidPEFile;
|
|
}
|
|
if (pe_header.size_of_optional_header < @sizeOf(OptionalHeader64)) {
|
|
return error.InvalidPEFile;
|
|
}
|
|
|
|
// 5. Read optional header
|
|
const optional_header = try viewAtMut(OptionalHeader64, data.items, optional_header_offset);
|
|
if (optional_header.magic != OPTIONAL_HEADER_MAGIC_64) {
|
|
return error.UnsupportedPEFormat;
|
|
}
|
|
|
|
// Validate file_alignment and section_alignment
|
|
if (!isPow2(optional_header.file_alignment) or !isPow2(optional_header.section_alignment)) {
|
|
return error.BadAlignment;
|
|
}
|
|
// If section_alignment < 4096, then file_alignment == section_alignment
|
|
if (optional_header.section_alignment < 4096) {
|
|
if (optional_header.file_alignment != optional_header.section_alignment) {
|
|
return error.InvalidPEFile;
|
|
}
|
|
}
|
|
|
|
// 6. Compute section_headers_offset
|
|
const section_headers_offset = optional_header_offset + pe_header.size_of_optional_header;
|
|
const num_sections = pe_header.number_of_sections;
|
|
if (num_sections > 96) { // PE limit
|
|
return error.TooManySections;
|
|
}
|
|
const section_headers_size = @sizeOf(SectionHeader) * num_sections;
|
|
if (data.items.len < section_headers_offset + section_headers_size) {
|
|
return error.InvalidPEFile;
|
|
}
|
|
|
|
// 7. Precompute first_raw, last_file_end, last_va_end
|
|
var first_raw: u32 = @intCast(data.items.len);
|
|
var last_file_end: u32 = 0;
|
|
var last_va_end: u32 = 0;
|
|
|
|
if (num_sections > 0) {
|
|
const sections_ptr: [*]align(1) const SectionHeader = @ptrCast(data.items[section_headers_offset..].ptr);
|
|
const sections = sections_ptr[0..num_sections];
|
|
|
|
for (sections) |section| {
|
|
if (section.size_of_raw_data > 0) {
|
|
if (section.pointer_to_raw_data < first_raw) {
|
|
first_raw = section.pointer_to_raw_data;
|
|
}
|
|
const file_end = section.pointer_to_raw_data + section.size_of_raw_data;
|
|
if (file_end > last_file_end) {
|
|
last_file_end = file_end;
|
|
}
|
|
}
|
|
// Use effective virtual size (max of virtual_size and size_of_raw_data)
|
|
const vs_effective = @max(section.virtual_size, section.size_of_raw_data);
|
|
const va_end = section.virtual_address + (try alignUpU32(vs_effective, optional_header.section_alignment));
|
|
if (va_end > last_va_end) {
|
|
last_va_end = va_end;
|
|
}
|
|
}
|
|
}
|
|
|
|
self.* = .{
|
|
.data = data,
|
|
.allocator = allocator,
|
|
.dos_header_offset = 0,
|
|
.pe_header_offset = pe_off,
|
|
.optional_header_offset = optional_header_offset,
|
|
.section_headers_offset = section_headers_offset,
|
|
.num_sections = num_sections,
|
|
.first_raw = first_raw,
|
|
.last_file_end = last_file_end,
|
|
.last_va_end = last_va_end,
|
|
};
|
|
|
|
return self;
|
|
}
|
|
|
|
pub fn deinit(self: *PEFile) void {
|
|
self.data.deinit();
|
|
self.allocator.destroy(self);
|
|
}
|
|
|
|
/// Strip Authenticode signatures from the PE file
|
|
pub fn stripAuthenticode(self: *PEFile, opts: StripOpts) !void {
|
|
const data = self.data.items;
|
|
const opt = try viewAtMut(OptionalHeader64, data, self.optional_header_offset);
|
|
|
|
// Read Security directory (index 4)
|
|
const dd_ptr: *align(1) DataDirectory = &opt.data_directories[IMAGE_DIRECTORY_ENTRY_SECURITY];
|
|
const sec_off_u32 = dd_ptr.virtual_address; // file offset (not RVA)
|
|
const sec_size_u32 = dd_ptr.size;
|
|
|
|
if (sec_off_u32 == 0 or sec_size_u32 == 0) return; // nothing to strip
|
|
|
|
// Compute last_file_end from sections (reuse cached or recompute)
|
|
var last_raw_end: u32 = 0;
|
|
const sections = try self.getSectionHeaders();
|
|
for (sections) |s| {
|
|
const end = s.pointer_to_raw_data + s.size_of_raw_data;
|
|
if (end > last_raw_end) last_raw_end = end;
|
|
}
|
|
|
|
const file_len = data.len;
|
|
const sec_off = @as(usize, sec_off_u32);
|
|
const sec_size = @as(usize, sec_size_u32);
|
|
|
|
if (sec_off >= file_len or sec_size == 0) return error.InvalidSecurityDirectory;
|
|
if (opts.require_overlay and sec_off < @as(usize, last_raw_end))
|
|
return error.SecurityDirInsideImage;
|
|
|
|
// Remove certificate plus 8-byte padding at tail
|
|
const end_raw = try alignUpUsize(sec_off + sec_size, 8);
|
|
if (end_raw > file_len) return error.InvalidSecurityDirectory;
|
|
|
|
if (end_raw == file_len) {
|
|
try self.data.resize(sec_off);
|
|
} else {
|
|
const tail_len = file_len - end_raw;
|
|
// Use copyBackwards for potentially overlapping memory regions
|
|
std.mem.copyBackwards(u8, self.data.items[sec_off .. sec_off + tail_len], self.data.items[end_raw..file_len]);
|
|
try self.data.resize(sec_off + tail_len);
|
|
}
|
|
|
|
// Re-get pointers after resize
|
|
const opt_after = try self.getOptionalHeaderMut();
|
|
const dd_after: *align(1) DataDirectory = &opt_after.data_directories[IMAGE_DIRECTORY_ENTRY_SECURITY];
|
|
|
|
// Zero Security directory entry
|
|
dd_after.virtual_address = 0;
|
|
dd_after.size = 0;
|
|
|
|
// Clear FORCE_INTEGRITY bit if set
|
|
if ((opt_after.dll_characteristics & IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY) != 0)
|
|
opt_after.dll_characteristics &= ~IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY;
|
|
|
|
// Recompute checksum (recommended)
|
|
if (opts.recompute_checksum) try self.recomputePEChecksum();
|
|
|
|
// After strip, ensure no remaining overlay beyond last section
|
|
const after_strip_len = self.data.items.len;
|
|
if (@as(usize, last_raw_end) < after_strip_len)
|
|
return error.UnexpectedOverlayPresent;
|
|
}
|
|
|
|
/// Recompute PE checksum according to Windows spec
|
|
fn recomputePEChecksum(self: *PEFile) !void {
|
|
const data = self.data.items;
|
|
const checksum_off = self.optional_header_offset + @offsetOf(OptionalHeader64, "checksum");
|
|
|
|
// Zero checksum field before summing
|
|
@memset(self.data.items[checksum_off .. checksum_off + 4], 0);
|
|
|
|
var sum: u64 = 0;
|
|
var i: usize = 0;
|
|
|
|
// Sum 16-bit words
|
|
while (i + 1 < data.len) : (i += 2) {
|
|
const w: u16 = @as(u16, data[i]) | (@as(u16, data[i + 1]) << 8);
|
|
sum += w;
|
|
sum = (sum & 0xffff) + (sum >> 16); // fold periodically
|
|
}
|
|
// Odd trailing byte
|
|
if ((data.len & 1) != 0) {
|
|
sum += data[data.len - 1];
|
|
}
|
|
|
|
// Final folds + add length
|
|
sum = (sum & 0xffff) + (sum >> 16);
|
|
sum = (sum & 0xffff) + (sum >> 16);
|
|
sum += @as(u64, @intCast(data.len));
|
|
sum = (sum & 0xffff) + (sum >> 16);
|
|
const final_sum: u32 = @intCast((sum & 0xffff) + (sum >> 16));
|
|
|
|
const opt = try self.getOptionalHeaderMut();
|
|
opt.checksum = final_sum;
|
|
}
|
|
|
|
/// Add a new section to the PE file for storing Bun module data
|
|
pub fn addBunSection(self: *PEFile, data_to_embed: []const u8, strip: StripMode) !void {
|
|
// 1. Optional strip (before any addition)
|
|
if (strip == .strip_always) {
|
|
try self.stripAuthenticode(.{ .require_overlay = true, .recompute_checksum = true });
|
|
} else if (strip == .strip_if_signed) {
|
|
// Read Security directory to check if signed
|
|
const opt = try self.getOptionalHeader();
|
|
const dd = opt.data_directories[IMAGE_DIRECTORY_ENTRY_SECURITY];
|
|
if (dd.virtual_address != 0 or dd.size != 0) {
|
|
try self.stripAuthenticode(.{ .require_overlay = true, .recompute_checksum = true });
|
|
}
|
|
}
|
|
|
|
// 2. Re-read PE/Optional (pointers may have moved due to resize in strip)
|
|
const opt = try self.getOptionalHeaderMut();
|
|
|
|
// 3. Duplicate .bun guard - compare all 8 bytes exactly
|
|
const section_headers = try self.getSectionHeaders();
|
|
for (section_headers) |section| {
|
|
if (std.mem.eql(u8, section.name[0..8], &BUN_SECTION_NAME)) {
|
|
return error.SectionExists;
|
|
}
|
|
}
|
|
|
|
// Check if we can add another section
|
|
if (self.num_sections >= 96) { // PE limit
|
|
return error.TooManySections;
|
|
}
|
|
|
|
// 4. Compute header slack requirement
|
|
const new_headers_end = self.section_headers_offset + @sizeOf(SectionHeader) * (self.num_sections + 1);
|
|
const new_size_of_headers = try alignUpU32(@intCast(new_headers_end), opt.file_alignment);
|
|
|
|
// Determine first_raw (min PointerToRawData among sections with raw data, else data.len)
|
|
var first_raw: u32 = @intCast(self.data.items.len);
|
|
for (section_headers) |section| {
|
|
if (section.size_of_raw_data > 0) {
|
|
if (section.pointer_to_raw_data < first_raw) {
|
|
first_raw = section.pointer_to_raw_data;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Require new_size_of_headers <= first_raw
|
|
if (new_size_of_headers > first_raw) {
|
|
return error.InsufficientHeaderSpace;
|
|
}
|
|
|
|
// 5. Placement calculations
|
|
// Recompute last_file_end and last_va_end after strip
|
|
var last_file_end: u32 = 0;
|
|
var last_va_end: u32 = 0;
|
|
for (section_headers) |section| {
|
|
const file_end = section.pointer_to_raw_data + section.size_of_raw_data;
|
|
if (file_end > last_file_end) {
|
|
last_file_end = file_end;
|
|
}
|
|
// Use effective virtual size (max of virtual_size and size_of_raw_data)
|
|
const vs_effective = @max(section.virtual_size, section.size_of_raw_data);
|
|
const va_end = section.virtual_address + (try alignUpU32(vs_effective, opt.section_alignment));
|
|
if (va_end > last_va_end) {
|
|
last_va_end = va_end;
|
|
}
|
|
}
|
|
|
|
// Check for overflow before adding 4
|
|
if (data_to_embed.len > std.math.maxInt(u32) - 4) {
|
|
return error.Overflow;
|
|
}
|
|
const payload_len = @as(u32, @intCast(data_to_embed.len + 4)); // 4 for LE length prefix
|
|
const raw_size = try alignUpU32(payload_len, opt.file_alignment);
|
|
const new_va = try alignUpU32(last_va_end, opt.section_alignment);
|
|
const new_raw = try alignUpU32(last_file_end, opt.file_alignment);
|
|
|
|
// 6. Resize & zero only the new section area
|
|
const new_file_size = @as(usize, new_raw) + @as(usize, raw_size);
|
|
try self.data.resize(new_file_size);
|
|
@memset(self.data.items[@intCast(new_raw)..new_file_size], 0);
|
|
|
|
// 7. Write the new SectionHeader by byte copy
|
|
const sh = SectionHeader{
|
|
.name = [_]u8{ '.', 'b', 'u', 'n', 0, 0, 0, 0 },
|
|
.virtual_size = payload_len,
|
|
.virtual_address = new_va,
|
|
.size_of_raw_data = raw_size,
|
|
.pointer_to_raw_data = new_raw,
|
|
.pointer_to_relocations = 0,
|
|
.pointer_to_line_numbers = 0,
|
|
.number_of_relocations = 0,
|
|
.number_of_line_numbers = 0,
|
|
.characteristics = IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ,
|
|
};
|
|
|
|
const new_sh_off = self.section_headers_offset + @sizeOf(SectionHeader) * self.num_sections;
|
|
// Bounds check against first_raw (not file length)
|
|
if (new_sh_off + @sizeOf(SectionHeader) > first_raw) {
|
|
return error.InsufficientHeaderSpace;
|
|
}
|
|
std.mem.copyForwards(u8, self.data.items[new_sh_off .. new_sh_off + @sizeOf(SectionHeader)], std.mem.asBytes(&sh));
|
|
|
|
// 8. Write payload
|
|
// At data[new_raw ..]: write LE length prefix then data
|
|
std.mem.writeInt(u32, self.data.items[new_raw..][0..4], @intCast(data_to_embed.len), .little);
|
|
@memcpy(self.data.items[new_raw + 4 ..][0..data_to_embed.len], data_to_embed);
|
|
|
|
// 9. Update headers
|
|
// Get fresh pointers after resize
|
|
const pe_after = try self.getPEHeaderMut();
|
|
pe_after.number_of_sections += 1;
|
|
self.num_sections += 1;
|
|
|
|
const opt_after = try self.getOptionalHeaderMut();
|
|
// If opt.size_of_headers < new_size_of_headers
|
|
if (opt_after.size_of_headers < new_size_of_headers) {
|
|
opt_after.size_of_headers = new_size_of_headers;
|
|
}
|
|
// Calculate size_of_image: aligned end of last section
|
|
const section_va_end = new_va + sh.virtual_size;
|
|
opt_after.size_of_image = try alignUpU32(section_va_end, opt_after.section_alignment);
|
|
|
|
// Security directory must be zero (signature invalidated by change)
|
|
const dd_ptr: *align(1) DataDirectory = &opt_after.data_directories[IMAGE_DIRECTORY_ENTRY_SECURITY];
|
|
if (dd_ptr.virtual_address != 0 or dd_ptr.size != 0) {
|
|
dd_ptr.virtual_address = 0;
|
|
dd_ptr.size = 0;
|
|
}
|
|
|
|
// Do not touch size_of_initialized_data (leave as is)
|
|
|
|
// 10. Recompute checksum (recommended)
|
|
try self.recomputePEChecksum();
|
|
}
|
|
|
|
/// Find the .bun section and return its data
|
|
pub fn getBunSectionData(self: *const PEFile) ![]const u8 {
|
|
const section_headers = try self.getSectionHeaders();
|
|
for (section_headers) |section| {
|
|
if (std.mem.eql(u8, section.name[0..8], &BUN_SECTION_NAME)) {
|
|
if (section.size_of_raw_data < @sizeOf(u32)) {
|
|
return error.InvalidBunSection;
|
|
}
|
|
|
|
// Bounds check
|
|
if (section.pointer_to_raw_data >= self.data.items.len or
|
|
section.pointer_to_raw_data + section.size_of_raw_data > self.data.items.len)
|
|
{
|
|
return error.InvalidBunSection;
|
|
}
|
|
|
|
const section_data = self.data.items[section.pointer_to_raw_data..][0..section.size_of_raw_data];
|
|
const data_size = std.mem.readInt(u32, section_data[0..4], .little);
|
|
|
|
if (data_size + @sizeOf(u32) > section.size_of_raw_data) {
|
|
return error.InvalidBunSection;
|
|
}
|
|
|
|
return section_data[4..][0..data_size];
|
|
}
|
|
}
|
|
return error.BunSectionNotFound;
|
|
}
|
|
|
|
/// Get the length of the Bun section data
|
|
pub fn getBunSectionLength(self: *const PEFile) !u32 {
|
|
const section_headers = try self.getSectionHeaders();
|
|
for (section_headers) |section| {
|
|
if (std.mem.eql(u8, section.name[0..8], &BUN_SECTION_NAME)) {
|
|
if (section.size_of_raw_data < @sizeOf(u32)) {
|
|
return error.InvalidBunSection;
|
|
}
|
|
|
|
// Bounds check
|
|
if (section.pointer_to_raw_data >= self.data.items.len or
|
|
section.pointer_to_raw_data + @sizeOf(u32) > self.data.items.len)
|
|
{
|
|
return error.InvalidBunSection;
|
|
}
|
|
|
|
const section_data = self.data.items[section.pointer_to_raw_data..];
|
|
return std.mem.readInt(u32, section_data[0..4], .little);
|
|
}
|
|
}
|
|
return error.BunSectionNotFound;
|
|
}
|
|
|
|
/// Write the modified PE file
|
|
pub fn write(self: *const PEFile, writer: anytype) !void {
|
|
try writer.writeAll(self.data.items);
|
|
}
|
|
|
|
/// Validate the PE file structure
|
|
pub fn validate(self: *const PEFile) !void {
|
|
// Check DOS & PE signatures
|
|
const dos_header = try self.getDosHeader();
|
|
if (dos_header.e_magic != DOS_SIGNATURE) {
|
|
return error.InvalidDOSSignature;
|
|
}
|
|
|
|
const pe_header = try self.getPEHeader();
|
|
if (pe_header.signature != PE_SIGNATURE) {
|
|
return error.InvalidPESignature;
|
|
}
|
|
|
|
// Check optional header magic is 0x20B (64-bit)
|
|
const optional_header = try self.getOptionalHeader();
|
|
if (optional_header.magic != OPTIONAL_HEADER_MAGIC_64) {
|
|
return error.UnsupportedPEFormat;
|
|
}
|
|
|
|
// Validate file_alignment, section_alignment sanity
|
|
if (!isPow2(optional_header.file_alignment) or !isPow2(optional_header.section_alignment)) {
|
|
return error.BadAlignment;
|
|
}
|
|
// Relational rule
|
|
if (optional_header.section_alignment < 4096) {
|
|
if (optional_header.file_alignment != optional_header.section_alignment) {
|
|
return error.InvalidPEFile;
|
|
}
|
|
}
|
|
|
|
// Section headers region fits within size_of_headers and file
|
|
const section_headers_end = self.section_headers_offset + @sizeOf(SectionHeader) * self.num_sections;
|
|
if (section_headers_end > optional_header.size_of_headers or
|
|
section_headers_end > self.data.items.len)
|
|
{
|
|
return error.InvalidPEFile;
|
|
}
|
|
|
|
// Validate each section
|
|
const section_headers = try self.getSectionHeaders();
|
|
var max_va_end: u32 = 0;
|
|
|
|
for (section_headers, 0..) |section, i| {
|
|
// If size_of_raw_data > 0, validate raw data bounds
|
|
if (section.size_of_raw_data > 0) {
|
|
if (section.pointer_to_raw_data < optional_header.size_of_headers or
|
|
section.pointer_to_raw_data + section.size_of_raw_data > self.data.items.len)
|
|
{
|
|
return error.InvalidSectionData;
|
|
}
|
|
|
|
// Check for overlaps with other sections using correct interval test
|
|
for (section_headers[i + 1 ..]) |other| {
|
|
if (other.size_of_raw_data > 0) {
|
|
const section_start = section.pointer_to_raw_data;
|
|
const section_end = section_start + section.size_of_raw_data;
|
|
const other_start = other.pointer_to_raw_data;
|
|
const other_end = other_start + other.size_of_raw_data;
|
|
// Standard overlap test: max(start) < min(end)
|
|
if (@max(section_start, other_start) < @min(section_end, other_end)) {
|
|
return error.InvalidPEFile; // Section raw ranges overlap
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Track max virtual address end using effective virtual size
|
|
const vs_effective = @max(section.virtual_size, section.size_of_raw_data);
|
|
const va_end = section.virtual_address + (try alignUpU32(vs_effective, optional_header.section_alignment));
|
|
if (va_end > max_va_end) {
|
|
max_va_end = va_end;
|
|
}
|
|
}
|
|
|
|
// Verify size_of_image equals alignUp(max(VA + alignUp(VS, SA)), SA)
|
|
const expected_size_of_image = try alignUpU32(max_va_end, optional_header.section_alignment);
|
|
if (optional_header.size_of_image != expected_size_of_image) {
|
|
return error.SizeOfImageMismatch;
|
|
}
|
|
|
|
// Security directory should be 0,0 post-change (if we modified it)
|
|
// (This is optional validation, not critical)
|
|
|
|
// If checksum recomputed, field should be non-zero
|
|
// (Unless we intentionally write zero, which is allowed)
|
|
}
|
|
};
|
|
|
|
/// Utilities for PE file detection and validation
|
|
pub const utils = struct {
|
|
pub fn isPE(data: []const u8) bool {
|
|
if (data.len < @sizeOf(PEFile.DOSHeader)) return false;
|
|
|
|
const dos: *align(1) const PEFile.DOSHeader = @ptrCast(data.ptr);
|
|
if (dos.e_magic != PEFile.DOS_SIGNATURE) return false;
|
|
|
|
const off = dos.e_lfanew;
|
|
if (off < @sizeOf(PEFile.DOSHeader) or off > data.len -| @sizeOf(PEFile.PEHeader)) return false;
|
|
|
|
const pe: *align(1) const PEFile.PEHeader = @ptrCast(data[off..].ptr);
|
|
return pe.signature == PEFile.PE_SIGNATURE;
|
|
}
|
|
};
|
|
|
|
/// Windows-specific external interface for accessing embedded Bun data
|
|
/// This matches the macOS interface but for PE files
|
|
pub const BUN_COMPILED_SECTION_NAME = ".bun";
|
|
|
|
/// External C interface declarations - these are implemented in C++ bindings
|
|
/// The C++ code uses Windows PE APIs to directly access the .bun section
|
|
/// from the current process memory without loading the entire executable
|
|
extern "C" fn Bun__getStandaloneModuleGraphPELength() u32;
|
|
extern "C" fn Bun__getStandaloneModuleGraphPEData() ?[*]u8;
|
|
|
|
const bun = @import("bun");
|
|
const std = @import("std");
|
|
|
|
const mem = std.mem;
|
|
const Allocator = mem.Allocator;
|