fix(compile): use 8-byte header for embedded section to ensure bytecode alignment (#25377)

## Summary
- Change the size header in embedded Mach-O and PE sections from `u32`
(4 bytes) to `u64` (8 bytes)
- Ensures the data payload starts at an 8-byte aligned offset, which is
required for the bytecode cache

## Test plan
- [x] Test standalone compilation on macOS
- [ ] Test standalone compilation on Windows

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Dylan Conway
2025-12-06 16:37:09 -08:00
committed by GitHub
parent cde167cacd
commit 5eb2145b31
5 changed files with 37 additions and 32 deletions

View File

@@ -119,7 +119,7 @@ pub const StandaloneModuleGraph = struct {
}; };
const Macho = struct { const Macho = struct {
pub extern "C" fn Bun__getStandaloneModuleGraphMachoLength() ?*align(1) u32; pub extern "C" fn Bun__getStandaloneModuleGraphMachoLength() ?*align(1) u64;
pub fn getData() ?[]const u8 { pub fn getData() ?[]const u8 {
if (Bun__getStandaloneModuleGraphMachoLength()) |length| { if (Bun__getStandaloneModuleGraphMachoLength()) |length| {
@@ -127,8 +127,10 @@ pub const StandaloneModuleGraph = struct {
return null; return null;
} }
// BlobHeader has 8 bytes size (u64), so data starts at offset 8.
const data_offset = @sizeOf(u64);
const slice_ptr: [*]const u8 = @ptrCast(length); const slice_ptr: [*]const u8 = @ptrCast(length);
return slice_ptr[4..][0..length.*]; return slice_ptr[data_offset..][0..length.*];
} }
return null; return null;
@@ -136,7 +138,7 @@ pub const StandaloneModuleGraph = struct {
}; };
const PE = struct { const PE = struct {
pub extern "C" fn Bun__getStandaloneModuleGraphPELength() u32; pub extern "C" fn Bun__getStandaloneModuleGraphPELength() u64;
pub extern "C" fn Bun__getStandaloneModuleGraphPEData() ?[*]u8; pub extern "C" fn Bun__getStandaloneModuleGraphPEData() ?[*]u8;
pub fn getData() ?[]const u8 { pub fn getData() ?[]const u8 {

View File

@@ -910,14 +910,14 @@ extern "C" void Bun__signpost_emit(os_log_t log, os_signpost_type_t type, os_sig
extern "C" { extern "C" {
struct BlobHeader { struct BlobHeader {
uint32_t size; uint64_t size; // 64-bit to ensure data[] starts at 8-byte aligned offset (required for bytecode cache)
uint8_t data[]; uint8_t data[];
} __attribute__((aligned(BLOB_HEADER_ALIGNMENT))); } __attribute__((aligned(BLOB_HEADER_ALIGNMENT)));
} }
extern "C" BlobHeader __attribute__((section("__BUN,__bun"))) BUN_COMPILED = { 0, 0 }; extern "C" BlobHeader __attribute__((section("__BUN,__bun"))) BUN_COMPILED = { 0, 0 };
extern "C" uint32_t* Bun__getStandaloneModuleGraphMachoLength() extern "C" uint64_t* Bun__getStandaloneModuleGraphMachoLength()
{ {
return &BUN_COMPILED.size; return &BUN_COMPILED.size;
} }
@@ -927,7 +927,7 @@ extern "C" uint32_t* Bun__getStandaloneModuleGraphMachoLength()
#include <windows.h> #include <windows.h>
#include <winnt.h> #include <winnt.h>
static uint32_t* pe_section_size = nullptr; static uint64_t* pe_section_size = nullptr;
static uint8_t* pe_section_data = nullptr; static uint8_t* pe_section_data = nullptr;
// Helper function to find and map the .bun section // Helper function to find and map the .bun section
@@ -949,9 +949,10 @@ static bool initializePESection()
for (int i = 0; i < ntHeaders->FileHeader.NumberOfSections; i++) { for (int i = 0; i < ntHeaders->FileHeader.NumberOfSections; i++) {
if (strncmp((char*)sectionHeader->Name, ".bun", 4) == 0) { if (strncmp((char*)sectionHeader->Name, ".bun", 4) == 0) {
// Found the .bun section // Found the .bun section
// Section format: 8 bytes size (uint64_t) + data
BYTE* sectionData = (BYTE*)hModule + sectionHeader->VirtualAddress; BYTE* sectionData = (BYTE*)hModule + sectionHeader->VirtualAddress;
pe_section_size = (uint32_t*)sectionData; pe_section_size = (uint64_t*)sectionData;
pe_section_data = sectionData + sizeof(uint32_t); pe_section_data = sectionData + sizeof(uint64_t); // Skip size (8)
return true; return true;
} }
sectionHeader++; sectionHeader++;
@@ -960,7 +961,7 @@ static bool initializePESection()
return false; return false;
} }
extern "C" uint32_t Bun__getStandaloneModuleGraphPELength() extern "C" uint64_t Bun__getStandaloneModuleGraphPELength()
{ {
if (!initializePESection()) return 0; if (!initializePESection()) return 0;
return pe_section_size ? *pe_section_size : 0; return pe_section_size ? *pe_section_size : 0;

View File

@@ -44,7 +44,7 @@ pub const MachoFile = struct {
const PAGE_SIZE: u64 = 1 << 12; const PAGE_SIZE: u64 = 1 << 12;
const HASH_SIZE: usize = 32; // SHA256 = 32 bytes const HASH_SIZE: usize = 32; // SHA256 = 32 bytes
const header_size = @sizeOf(u32); const header_size = @sizeOf(u64);
const total_size = header_size + data.len; const total_size = header_size + data.len;
const aligned_size = alignSize(total_size, blob_alignment); const aligned_size = alignSize(total_size, blob_alignment);
@@ -164,14 +164,14 @@ pub const MachoFile = struct {
const prev_after_bun_slice = prev_data_slice[original_segsize..]; const prev_after_bun_slice = prev_data_slice[original_segsize..];
bun.memmove(after_bun_slice, prev_after_bun_slice); bun.memmove(after_bun_slice, prev_after_bun_slice);
// Now we copy the u32 size header // Now we copy the u64 size header (8 bytes for alignment)
std.mem.writeInt(u32, self.data.items[original_fileoff..][0..4], @intCast(data.len), .little); std.mem.writeInt(u64, self.data.items[original_fileoff..][0..8], @intCast(data.len), .little);
// Now we copy the data itself // Now we copy the data itself
@memcpy(self.data.items[original_fileoff + 4 ..][0..data.len], data); @memcpy(self.data.items[original_fileoff + 8 ..][0..data.len], data);
// Lastly, we zero any of the padding that was added // Lastly, we zero any of the padding that was added
const padding_bytes = self.data.items[original_fileoff..][data.len + 4 .. aligned_size]; const padding_bytes = self.data.items[original_fileoff..][data.len + 8 .. aligned_size];
@memset(padding_bytes, 0); @memset(padding_bytes, 0);
if (code_sign_cmd) |cs| { if (code_sign_cmd) |cs| {

View File

@@ -500,11 +500,11 @@ pub const PEFile = struct {
} }
} }
// Check for overflow before adding 4 // Check for overflow before adding 8
if (data_to_embed.len > std.math.maxInt(u32) - 4) { if (data_to_embed.len > std.math.maxInt(u32) - 8) {
return error.Overflow; return error.Overflow;
} }
const payload_len = @as(u32, @intCast(data_to_embed.len + 4)); // 4 for LE length prefix const payload_len = @as(u32, @intCast(data_to_embed.len + 8)); // 8 for LE length prefix
const raw_size = try alignUpU32(payload_len, opt.file_alignment); const raw_size = try alignUpU32(payload_len, opt.file_alignment);
const new_va = try alignUpU32(last_va_end, opt.section_alignment); const new_va = try alignUpU32(last_va_end, opt.section_alignment);
const new_raw = try alignUpU32(last_file_end, opt.file_alignment); const new_raw = try alignUpU32(last_file_end, opt.file_alignment);
@@ -536,9 +536,9 @@ pub const PEFile = struct {
std.mem.copyForwards(u8, self.data.items[new_sh_off .. new_sh_off + @sizeOf(SectionHeader)], std.mem.asBytes(&sh)); std.mem.copyForwards(u8, self.data.items[new_sh_off .. new_sh_off + @sizeOf(SectionHeader)], std.mem.asBytes(&sh));
// 8. Write payload // 8. Write payload
// At data[new_raw ..]: write LE length prefix then data // At data[new_raw ..]: write u64 LE length prefix, then data
std.mem.writeInt(u32, self.data.items[new_raw..][0..4], @intCast(data_to_embed.len), .little); std.mem.writeInt(u64, self.data.items[new_raw..][0..8], @intCast(data_to_embed.len), .little);
@memcpy(self.data.items[new_raw + 4 ..][0..data_to_embed.len], data_to_embed); @memcpy(self.data.items[new_raw + 8 ..][0..data_to_embed.len], data_to_embed);
// 9. Update headers // 9. Update headers
// Get fresh pointers after resize // Get fresh pointers after resize
@@ -573,7 +573,8 @@ pub const PEFile = struct {
const section_headers = try self.getSectionHeaders(); const section_headers = try self.getSectionHeaders();
for (section_headers) |section| { for (section_headers) |section| {
if (std.mem.eql(u8, section.name[0..8], &BUN_SECTION_NAME)) { if (std.mem.eql(u8, section.name[0..8], &BUN_SECTION_NAME)) {
if (section.size_of_raw_data < @sizeOf(u32)) { // Header: 8 bytes size (u64)
if (section.size_of_raw_data < @sizeOf(u64)) {
return error.InvalidBunSection; return error.InvalidBunSection;
} }
@@ -585,36 +586,37 @@ pub const PEFile = struct {
} }
const section_data = self.data.items[section.pointer_to_raw_data..][0..section.size_of_raw_data]; 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); const data_size = std.mem.readInt(u64, section_data[0..8], .little);
if (data_size + @sizeOf(u32) > section.size_of_raw_data) { if (data_size + @sizeOf(u64) > section.size_of_raw_data) {
return error.InvalidBunSection; return error.InvalidBunSection;
} }
return section_data[4..][0..data_size]; // Data starts at offset 8 (after u64 size)
return section_data[8..][0..data_size];
} }
} }
return error.BunSectionNotFound; return error.BunSectionNotFound;
} }
/// Get the length of the Bun section data /// Get the length of the Bun section data
pub fn getBunSectionLength(self: *const PEFile) !u32 { pub fn getBunSectionLength(self: *const PEFile) !u64 {
const section_headers = try self.getSectionHeaders(); const section_headers = try self.getSectionHeaders();
for (section_headers) |section| { for (section_headers) |section| {
if (std.mem.eql(u8, section.name[0..8], &BUN_SECTION_NAME)) { if (std.mem.eql(u8, section.name[0..8], &BUN_SECTION_NAME)) {
if (section.size_of_raw_data < @sizeOf(u32)) { if (section.size_of_raw_data < @sizeOf(u64)) {
return error.InvalidBunSection; return error.InvalidBunSection;
} }
// Bounds check // Bounds check
if (section.pointer_to_raw_data >= self.data.items.len or if (section.pointer_to_raw_data >= self.data.items.len or
section.pointer_to_raw_data + @sizeOf(u32) > self.data.items.len) section.pointer_to_raw_data + @sizeOf(u64) > self.data.items.len)
{ {
return error.InvalidBunSection; return error.InvalidBunSection;
} }
const section_data = self.data.items[section.pointer_to_raw_data..]; const section_data = self.data.items[section.pointer_to_raw_data..];
return std.mem.readInt(u32, section_data[0..4], .little); return std.mem.readInt(u64, section_data[0..8], .little);
} }
} }
return error.BunSectionNotFound; return error.BunSectionNotFound;

View File

@@ -119,8 +119,8 @@ describe.if(isWindows)("PE codesigning integrity", () => {
// Read the .bun section data // Read the .bun section data
const sectionData = new Uint8Array(this.buffer, bunSection.pointerToRawData, bunSection.sizeOfRawData); const sectionData = new Uint8Array(this.buffer, bunSection.pointerToRawData, bunSection.sizeOfRawData);
// First 4 bytes should be the data size // First 8 bytes should be the data size (u64 for 8-byte alignment)
const dataSize = new DataView(sectionData.buffer, bunSection.pointerToRawData).getUint32(0, true); const dataSize = Number(new DataView(sectionData.buffer, bunSection.pointerToRawData).getBigUint64(0, true));
// Validate the size is reasonable - it should match or be close to virtual size // Validate the size is reasonable - it should match or be close to virtual size
if (dataSize > bunSection.sizeOfRawData || dataSize === 0) { if (dataSize > bunSection.sizeOfRawData || dataSize === 0) {
@@ -133,8 +133,8 @@ describe.if(isWindows)("PE codesigning integrity", () => {
throw new Error(`Invalid .bun section: data size ${dataSize} exceeds virtual size ${bunSection.virtualSize}`); throw new Error(`Invalid .bun section: data size ${dataSize} exceeds virtual size ${bunSection.virtualSize}`);
} }
// Extract the actual embedded data (skip the 4-byte size header) // Extract the actual embedded data (skip the 8-byte size header)
const embeddedData = sectionData.slice(4, 4 + dataSize); const embeddedData = sectionData.slice(8, 8 + dataSize);
return { return {
section: bunSection, section: bunSection,