Commit e1451f92f8

Jakub Konka <kubkon@jakubkonka.com>
2020-12-26 20:55:11
macho: move findFreeSpace into SegmentCommand
One exception will be treatment of the __LINKEDIT segment which will be handled separately inside MachO directly since it doesn't include any sections.
1 parent 595397d
Changed files (2)
src
src/link/MachO/commands.zig
@@ -7,7 +7,11 @@ const macho = std.macho;
 const testing = std.testing;
 
 const Allocator = std.mem.Allocator;
-const makeStaticString = @import("../MachO.zig").makeStaticString;
+const MachO = @import("../MachO.zig");
+const makeStaticString = MachO.makeStaticString;
+const satMul = MachO.satMul;
+const alloc_num = MachO.alloc_num;
+const alloc_den = MachO.alloc_den;
 
 pub const LoadCommand = union(enum) {
     Segment: SegmentCommand,
@@ -188,6 +192,35 @@ pub const SegmentCommand = struct {
         self.sections.deinit(alloc);
     }
 
+    pub fn allocatedSize(self: SegmentCommand, start: u64) u64 {
+        assert(start > 0);
+        var min_pos: u64 = std.math.maxInt(u64);
+        for (self.sections.items) |section| {
+            if (section.offset > start and section.offset < min_pos) min_pos = section.offset;
+        }
+        return min_pos - start;
+    }
+
+    fn detectAllocCollision(self: SegmentCommand, start: u64, size: u64) ?u64 {
+        const end = start + satMul(size, alloc_num) / alloc_den;
+        for (self.sections.items) |section| {
+            const increased_size = satMul(section.size, alloc_num) / alloc_den;
+            const test_end = section.offset + increased_size;
+            if (end > section.offset and start < test_end) {
+                return test_end;
+            }
+        }
+        return null;
+    }
+
+    pub fn findFreeSpace(self: SegmentCommand, object_size: u64, min_alignment: u16, start: ?u64) u64 {
+        var st: u64 = if (start) |v| v else self.inner.fileoff;
+        while (self.detectAllocCollision(st, object_size)) |item_end| {
+            st = mem.alignForwardGeneric(u64, item_end, min_alignment);
+        }
+        return st;
+    }
+
     fn eql(self: SegmentCommand, other: SegmentCommand) bool {
         if (!meta.eql(self.inner, other.inner)) return false;
         const lhs = self.sections.items;
src/link/MachO.zig
@@ -155,8 +155,8 @@ pub const PieFixup = struct {
 };
 
 /// `alloc_num / alloc_den` is the factor of padding when allocating.
-const alloc_num = 4;
-const alloc_den = 3;
+pub const alloc_num = 4;
+pub const alloc_den = 3;
 
 /// Default path to dyld
 /// TODO instead of hardcoding it, we should probably look through some env vars and search paths
@@ -1358,7 +1358,7 @@ pub fn populateMissingMetadata(self: *MachO) !void {
         };
         const flags = macho.S_REGULAR | macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS;
         const needed_size = self.base.options.program_code_size_hint;
-        const off = self.findFreeSpace(text_segment, needed_size, @as(u16, 1) << alignment);
+        const off = text_segment.findFreeSpace(needed_size, @as(u16, 1) << alignment, self.header_pad);
 
         log.debug("found __text section free space 0x{x} to 0x{x}", .{ off, off + needed_size });
 
@@ -1386,7 +1386,7 @@ pub fn populateMissingMetadata(self: *MachO) !void {
 
         const flags = macho.S_REGULAR | macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS;
         const needed_size = @sizeOf(u64) * self.base.options.symbol_count_hint;
-        const off = self.findFreeSpace(text_segment, needed_size, @alignOf(u64));
+        const off = text_segment.findFreeSpace(needed_size, @alignOf(u64), self.header_pad);
         assert(off + needed_size <= text_segment.inner.fileoff + text_segment.inner.filesize); // TODO Must expand __TEXT segment.
 
         log.debug("found __ziggot section free space 0x{x} to 0x{x}", .{ off, off + needed_size });
@@ -1437,11 +1437,10 @@ pub fn populateMissingMetadata(self: *MachO) !void {
     }
     if (self.dyld_info_cmd_index == null) {
         self.dyld_info_cmd_index = @intCast(u16, self.load_commands.items.len);
-        const linkedit_segment = self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment;
 
         // TODO Preallocate rebase, binding, and lazy binding info.
         const export_size = 2;
-        const export_off = self.findFreeSpace(&linkedit_segment, export_size, 1);
+        const export_off = self.findFreeSpaceLinkedit(export_size, 1);
 
         log.debug("found export info free space 0x{x} to 0x{x}", .{ export_off, export_off + export_size });
 
@@ -1466,16 +1465,15 @@ pub fn populateMissingMetadata(self: *MachO) !void {
     }
     if (self.symtab_cmd_index == null) {
         self.symtab_cmd_index = @intCast(u16, self.load_commands.items.len);
-        const linkedit_segment = self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment;
 
         const symtab_size = self.base.options.symbol_count_hint * @sizeOf(macho.nlist_64);
-        const symtab_off = self.findFreeSpace(&linkedit_segment, symtab_size, @sizeOf(macho.nlist_64));
+        const symtab_off = self.findFreeSpaceLinkedit(symtab_size, @sizeOf(macho.nlist_64));
 
         log.debug("found symbol table free space 0x{x} to 0x{x}", .{ symtab_off, symtab_off + symtab_size });
 
         try self.string_table.append(self.base.allocator, 0); // Need a null at position 0.
         const strtab_size = self.string_table.items.len;
-        const strtab_off = self.findFreeSpace(&linkedit_segment, strtab_size, 1);
+        const strtab_off = self.findFreeSpaceLinkedit(strtab_size, 1);
 
         log.debug("found string table free space 0x{x} to 0x{x}", .{ strtab_off, strtab_off + strtab_size });
 
@@ -1613,7 +1611,6 @@ pub fn populateMissingMetadata(self: *MachO) !void {
     }
     if (self.code_signature_cmd_index == null) {
         self.code_signature_cmd_index = @intCast(u16, self.load_commands.items.len);
-        const linkedit_segment = self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment;
         try self.load_commands.append(self.base.allocator, .{
             .LinkeditData = .{
                 .cmd = macho.LC_CODE_SIGNATURE,
@@ -1790,48 +1787,41 @@ fn nextSegmentAddressAndOffset(self: *MachO) NextSegmentAddressAndOffset {
     };
 }
 
-fn allocatedSize(self: *MachO, segment: *const SegmentCommand, start: u64) u64 {
+fn allocatedSizeLinkedit(self: *MachO, start: u64) u64 {
     assert(start > 0);
     var min_pos: u64 = std.math.maxInt(u64);
 
-    if (parseAndCmpName(&segment.inner.segname, "__LINKEDIT")) {
-        assert(segment.sections.items.len == 0);
-        // __LINKEDIT is a weird segment where sections get their own load commands so we
-        // special-case it.
-        if (self.dyld_info_cmd_index) |idx| {
-            const dyld_info = self.load_commands.items[idx].DyldInfoOnly;
-            if (dyld_info.rebase_off > start and dyld_info.rebase_off < min_pos) min_pos = dyld_info.rebase_off;
-            if (dyld_info.bind_off > start and dyld_info.bind_off < min_pos) min_pos = dyld_info.bind_off;
-            if (dyld_info.weak_bind_off > start and dyld_info.weak_bind_off < min_pos) min_pos = dyld_info.weak_bind_off;
-            if (dyld_info.lazy_bind_off > start and dyld_info.lazy_bind_off < min_pos) min_pos = dyld_info.lazy_bind_off;
-            if (dyld_info.export_off > start and dyld_info.export_off < min_pos) min_pos = dyld_info.export_off;
-        }
+    // __LINKEDIT is a weird segment where sections get their own load commands so we
+    // special-case it.
+    if (self.dyld_info_cmd_index) |idx| {
+        const dyld_info = self.load_commands.items[idx].DyldInfoOnly;
+        if (dyld_info.rebase_off > start and dyld_info.rebase_off < min_pos) min_pos = dyld_info.rebase_off;
+        if (dyld_info.bind_off > start and dyld_info.bind_off < min_pos) min_pos = dyld_info.bind_off;
+        if (dyld_info.weak_bind_off > start and dyld_info.weak_bind_off < min_pos) min_pos = dyld_info.weak_bind_off;
+        if (dyld_info.lazy_bind_off > start and dyld_info.lazy_bind_off < min_pos) min_pos = dyld_info.lazy_bind_off;
+        if (dyld_info.export_off > start and dyld_info.export_off < min_pos) min_pos = dyld_info.export_off;
+    }
 
-        if (self.function_starts_cmd_index) |idx| {
-            const fstart = self.load_commands.items[idx].LinkeditData;
-            if (fstart.dataoff > start and fstart.dataoff < min_pos) min_pos = fstart.dataoff;
-        }
+    if (self.function_starts_cmd_index) |idx| {
+        const fstart = self.load_commands.items[idx].LinkeditData;
+        if (fstart.dataoff > start and fstart.dataoff < min_pos) min_pos = fstart.dataoff;
+    }
 
-        if (self.data_in_code_cmd_index) |idx| {
-            const dic = self.load_commands.items[idx].LinkeditData;
-            if (dic.dataoff > start and dic.dataoff < min_pos) min_pos = dic.dataoff;
-        }
+    if (self.data_in_code_cmd_index) |idx| {
+        const dic = self.load_commands.items[idx].LinkeditData;
+        if (dic.dataoff > start and dic.dataoff < min_pos) min_pos = dic.dataoff;
+    }
 
-        if (self.dysymtab_cmd_index) |idx| {
-            const dysymtab = self.load_commands.items[idx].Dysymtab;
-            if (dysymtab.indirectsymoff > start and dysymtab.indirectsymoff < min_pos) min_pos = dysymtab.indirectsymoff;
-            // TODO Handle more dynamic symbol table sections.
-        }
+    if (self.dysymtab_cmd_index) |idx| {
+        const dysymtab = self.load_commands.items[idx].Dysymtab;
+        if (dysymtab.indirectsymoff > start and dysymtab.indirectsymoff < min_pos) min_pos = dysymtab.indirectsymoff;
+        // TODO Handle more dynamic symbol table sections.
+    }
 
-        if (self.symtab_cmd_index) |idx| {
-            const symtab = self.load_commands.items[idx].Symtab;
-            if (symtab.symoff > start and symtab.symoff < min_pos) min_pos = symtab.symoff;
-            if (symtab.stroff > start and symtab.stroff < min_pos) min_pos = symtab.stroff;
-        }
-    } else {
-        for (segment.sections.items) |section| {
-            if (section.offset > start and section.offset < min_pos) min_pos = section.offset;
-        }
+    if (self.symtab_cmd_index) |idx| {
+        const symtab = self.load_commands.items[idx].Symtab;
+        if (symtab.symoff > start and symtab.symoff < min_pos) min_pos = symtab.symoff;
+        if (symtab.stroff > start and symtab.stroff < min_pos) min_pos = symtab.stroff;
     }
 
     return min_pos - start;
@@ -1846,101 +1836,90 @@ inline fn checkForCollision(start: u64, end: u64, off: u64, size: u64) ?u64 {
     return null;
 }
 
-fn detectAllocCollision(self: *MachO, segment: *const SegmentCommand, start: u64, size: u64) ?u64 {
+fn detectAllocCollisionLinkedit(self: *MachO, start: u64, size: u64) ?u64 {
     const end = start + satMul(size, alloc_num) / alloc_den;
 
-    if (parseAndCmpName(&segment.inner.segname, "__LINKEDIT")) {
-        assert(segment.sections.items.len == 0);
-        // __LINKEDIT is a weird segment where sections get their own load commands so we
-        // special-case it.
-        if (self.dyld_info_cmd_index) |idx| outer: {
-            if (self.load_commands.items.len == idx) break :outer;
-            const dyld_info = self.load_commands.items[idx].DyldInfoOnly;
-            if (checkForCollision(start, end, dyld_info.rebase_off, dyld_info.rebase_size)) |pos| {
-                return pos;
-            }
-            // Binding info
-            if (checkForCollision(start, end, dyld_info.bind_off, dyld_info.bind_size)) |pos| {
-                return pos;
-            }
-            // Weak binding info
-            if (checkForCollision(start, end, dyld_info.weak_bind_off, dyld_info.weak_bind_size)) |pos| {
-                return pos;
-            }
-            // Lazy binding info
-            if (checkForCollision(start, end, dyld_info.lazy_bind_off, dyld_info.lazy_bind_size)) |pos| {
-                return pos;
-            }
-            // Export info
-            if (checkForCollision(start, end, dyld_info.export_off, dyld_info.export_size)) |pos| {
-                return pos;
-            }
+    // __LINKEDIT is a weird segment where sections get their own load commands so we
+    // special-case it.
+    if (self.dyld_info_cmd_index) |idx| outer: {
+        if (self.load_commands.items.len == idx) break :outer;
+        const dyld_info = self.load_commands.items[idx].DyldInfoOnly;
+        if (checkForCollision(start, end, dyld_info.rebase_off, dyld_info.rebase_size)) |pos| {
+            return pos;
+        }
+        // Binding info
+        if (checkForCollision(start, end, dyld_info.bind_off, dyld_info.bind_size)) |pos| {
+            return pos;
+        }
+        // Weak binding info
+        if (checkForCollision(start, end, dyld_info.weak_bind_off, dyld_info.weak_bind_size)) |pos| {
+            return pos;
+        }
+        // Lazy binding info
+        if (checkForCollision(start, end, dyld_info.lazy_bind_off, dyld_info.lazy_bind_size)) |pos| {
+            return pos;
         }
+        // Export info
+        if (checkForCollision(start, end, dyld_info.export_off, dyld_info.export_size)) |pos| {
+            return pos;
+        }
+    }
 
-        if (self.function_starts_cmd_index) |idx| outer: {
-            if (self.load_commands.items.len == idx) break :outer;
-            const fstart = self.load_commands.items[idx].LinkeditData;
-            if (checkForCollision(start, end, fstart.dataoff, fstart.datasize)) |pos| {
-                return pos;
-            }
+    if (self.function_starts_cmd_index) |idx| outer: {
+        if (self.load_commands.items.len == idx) break :outer;
+        const fstart = self.load_commands.items[idx].LinkeditData;
+        if (checkForCollision(start, end, fstart.dataoff, fstart.datasize)) |pos| {
+            return pos;
         }
+    }
 
-        if (self.data_in_code_cmd_index) |idx| outer: {
-            if (self.load_commands.items.len == idx) break :outer;
-            const dic = self.load_commands.items[idx].LinkeditData;
-            if (checkForCollision(start, end, dic.dataoff, dic.datasize)) |pos| {
-                return pos;
-            }
+    if (self.data_in_code_cmd_index) |idx| outer: {
+        if (self.load_commands.items.len == idx) break :outer;
+        const dic = self.load_commands.items[idx].LinkeditData;
+        if (checkForCollision(start, end, dic.dataoff, dic.datasize)) |pos| {
+            return pos;
         }
+    }
 
-        if (self.dysymtab_cmd_index) |idx| outer: {
-            if (self.load_commands.items.len == idx) break :outer;
-            const dysymtab = self.load_commands.items[idx].Dysymtab;
-            // Indirect symbol table
-            const nindirectsize = dysymtab.nindirectsyms * @sizeOf(u32);
-            if (checkForCollision(start, end, dysymtab.indirectsymoff, nindirectsize)) |pos| {
-                return pos;
-            }
-            // TODO Handle more dynamic symbol table sections.
+    if (self.dysymtab_cmd_index) |idx| outer: {
+        if (self.load_commands.items.len == idx) break :outer;
+        const dysymtab = self.load_commands.items[idx].Dysymtab;
+        // Indirect symbol table
+        const nindirectsize = dysymtab.nindirectsyms * @sizeOf(u32);
+        if (checkForCollision(start, end, dysymtab.indirectsymoff, nindirectsize)) |pos| {
+            return pos;
         }
+        // TODO Handle more dynamic symbol table sections.
+    }
 
-        if (self.symtab_cmd_index) |idx| outer: {
-            if (self.load_commands.items.len == idx) break :outer;
-            const symtab = self.load_commands.items[idx].Symtab;
-            // Symbol table
-            const symsize = symtab.nsyms * @sizeOf(macho.nlist_64);
-            if (checkForCollision(start, end, symtab.symoff, symsize)) |pos| {
-                return pos;
-            }
-            // String table
-            if (checkForCollision(start, end, symtab.stroff, symtab.strsize)) |pos| {
-                return pos;
-            }
+    if (self.symtab_cmd_index) |idx| outer: {
+        if (self.load_commands.items.len == idx) break :outer;
+        const symtab = self.load_commands.items[idx].Symtab;
+        // Symbol table
+        const symsize = symtab.nsyms * @sizeOf(macho.nlist_64);
+        if (checkForCollision(start, end, symtab.symoff, symsize)) |pos| {
+            return pos;
         }
-    } else {
-        for (segment.sections.items) |section| {
-            if (checkForCollision(start, end, section.offset, section.size)) |pos| {
-                return pos;
-            }
+        // String table
+        if (checkForCollision(start, end, symtab.stroff, symtab.strsize)) |pos| {
+            return pos;
         }
     }
 
     return null;
 }
 
-fn findFreeSpace(self: *MachO, segment: *const SegmentCommand, object_size: u64, min_alignment: u16) u64 {
-    var start: u64 = if (parseAndCmpName(&segment.inner.segname, "__TEXT"))
-        self.header_pad
-    else
-        segment.inner.fileoff;
-    while (self.detectAllocCollision(segment, start, object_size)) |item_end| {
+fn findFreeSpaceLinkedit(self: *MachO, object_size: u64, min_alignment: u16) u64 {
+    const linkedit = self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment;
+    var start: u64 = linkedit.inner.fileoff;
+    while (self.detectAllocCollisionLinkedit(start, object_size)) |item_end| {
         start = mem.alignForwardGeneric(u64, item_end, min_alignment);
     }
     return start;
 }
 
 /// Saturating multiplication
-fn satMul(a: anytype, b: anytype) @TypeOf(a, b) {
+pub fn satMul(a: anytype, b: anytype) @TypeOf(a, b) {
     const T = @TypeOf(a, b);
     return std.math.mul(T, a, b) catch std.math.maxInt(T);
 }
@@ -1993,9 +1972,9 @@ fn relocateSymbolTable(self: *MachO) !void {
     if (symtab.nsyms < nsyms) {
         const linkedit_segment = self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment;
         const needed_size = nsyms * @sizeOf(macho.nlist_64);
-        if (needed_size > self.allocatedSize(&linkedit_segment, symtab.symoff)) {
+        if (needed_size > self.allocatedSizeLinkedit(symtab.symoff)) {
             // Move the entire symbol table to a new location
-            const new_symoff = self.findFreeSpace(&linkedit_segment, needed_size, @alignOf(macho.nlist_64));
+            const new_symoff = self.findFreeSpaceLinkedit(needed_size, @alignOf(macho.nlist_64));
             const existing_size = symtab.nsyms * @sizeOf(macho.nlist_64);
 
             log.debug("relocating symbol table from 0x{x}-0x{x} to 0x{x}-0x{x}", .{
@@ -2140,12 +2119,12 @@ fn writeExportTrie(self: *MachO) !void {
 
     const linkedit_segment = self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment;
     const dyld_info = &self.load_commands.items[self.dyld_info_cmd_index.?].DyldInfoOnly;
-    const allocated_size = self.allocatedSize(&linkedit_segment, dyld_info.export_off);
+    const allocated_size = self.allocatedSizeLinkedit(dyld_info.export_off);
     const needed_size = mem.alignForwardGeneric(u64, buffer.len, @alignOf(u64));
 
     if (needed_size > allocated_size) {
         dyld_info.export_off = 0;
-        dyld_info.export_off = @intCast(u32, self.findFreeSpace(&linkedit_segment, needed_size, 1));
+        dyld_info.export_off = @intCast(u32, self.findFreeSpaceLinkedit(needed_size, 1));
     }
     dyld_info.export_size = @intCast(u32, needed_size);
     log.debug("writing export info from 0x{x} to 0x{x}", .{ dyld_info.export_off, dyld_info.export_off + dyld_info.export_size });
@@ -2170,12 +2149,12 @@ fn writeBindingInfoTable(self: *MachO) !void {
 
     const linkedit_segment = self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment;
     const dyld_info = &self.load_commands.items[self.dyld_info_cmd_index.?].DyldInfoOnly;
-    const allocated_size = self.allocatedSize(&linkedit_segment, dyld_info.bind_off);
+    const allocated_size = self.allocatedSizeLinkedit(dyld_info.bind_off);
     const needed_size = mem.alignForwardGeneric(u64, buffer.len, @alignOf(u64));
 
     if (needed_size > allocated_size) {
         dyld_info.bind_off = 0;
-        dyld_info.bind_off = @intCast(u32, self.findFreeSpace(&linkedit_segment, needed_size, 1));
+        dyld_info.bind_off = @intCast(u32, self.findFreeSpaceLinkedit(needed_size, 1));
     }
 
     dyld_info.bind_size = @intCast(u32, needed_size);
@@ -2198,12 +2177,12 @@ fn writeLazyBindingInfoTable(self: *MachO) !void {
 
     const linkedit_segment = self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment;
     const dyld_info = &self.load_commands.items[self.dyld_info_cmd_index.?].DyldInfoOnly;
-    const allocated_size = self.allocatedSize(&linkedit_segment, dyld_info.lazy_bind_off);
+    const allocated_size = self.allocatedSizeLinkedit(dyld_info.lazy_bind_off);
     const needed_size = mem.alignForwardGeneric(u64, buffer.len, @alignOf(u64));
 
     if (needed_size > allocated_size) {
         dyld_info.lazy_bind_off = 0;
-        dyld_info.lazy_bind_off = @intCast(u32, self.findFreeSpace(&linkedit_segment, needed_size, 1));
+        dyld_info.lazy_bind_off = @intCast(u32, self.findFreeSpaceLinkedit(needed_size, 1));
     }
 
     dyld_info.lazy_bind_size = @intCast(u32, needed_size);
@@ -2222,12 +2201,12 @@ fn writeStringTable(self: *MachO) !void {
 
     const linkedit_segment = self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment;
     const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab;
-    const allocated_size = self.allocatedSize(&linkedit_segment, symtab.stroff);
+    const allocated_size = self.allocatedSizeLinkedit(symtab.stroff);
     const needed_size = mem.alignForwardGeneric(u64, self.string_table.items.len, @alignOf(u64));
 
     if (needed_size > allocated_size) {
         symtab.strsize = 0;
-        symtab.stroff = @intCast(u32, self.findFreeSpace(&linkedit_segment, needed_size, 1));
+        symtab.stroff = @intCast(u32, self.findFreeSpaceLinkedit(needed_size, 1));
     }
     symtab.strsize = @intCast(u32, needed_size);
     log.debug("writing string table from 0x{x} to 0x{x}", .{ symtab.stroff, symtab.stroff + symtab.strsize });
@@ -2280,7 +2259,7 @@ fn updateLinkeditSegmentSizes(self: *MachO) !void {
     const filesize = final_offset - linkedit_segment.inner.fileoff;
     linkedit_segment.inner.filesize = filesize;
     linkedit_segment.inner.vmsize = mem.alignForwardGeneric(u64, filesize, self.page_size);
-    try self.base.file.?.pwriteAll(&[_]u8{ 0 }, final_offset);
+    try self.base.file.?.pwriteAll(&[_]u8{0}, final_offset);
     self.load_commands_dirty = true;
 }
 
@@ -2301,7 +2280,7 @@ fn writeLoadCommands(self: *MachO) !void {
     }
 
     const off = @sizeOf(macho.mach_header_64);
-    log.debug("writing {} load commands from 0x{x} to 0x{x}", .{self.load_commands.items.len, off, off + sizeofcmds});
+    log.debug("writing {} load commands from 0x{x} to 0x{x}", .{ self.load_commands.items.len, off, off + sizeofcmds });
     try self.base.file.?.pwriteAll(buffer, off);
     self.load_commands_dirty = false;
 }