| ... | ... | @@ -761,43 +761,34 @@ pub fn flush(self: *MachO, comp: *Compilation) !void { |
| 761 | 761 | try self.addDataInCodeLC(); |
| 762 | 762 | try self.addCodeSignatureLC(); |
| 763 | 763 | |
| 764 | | if (use_stage1) { |
| 765 | | try self.parseTextBlocks(); |
| 766 | | try self.allocateTextSegment(); |
| 767 | | try self.allocateDataConstSegment(); |
| 768 | | try self.allocateDataSegment(); |
| 769 | | self.allocateLinkeditSegment(); |
| 770 | | try self.allocateTextBlocks(); |
| 771 | | try self.flushZld(); |
| 772 | | } else { |
| 773 | | try self.parseTextBlocks(); |
| 774 | | try self.allocateGlobalSymbols(); |
| 775 | | { |
| 776 | | log.debug("locals:", .{}); |
| 777 | | for (self.locals.items) |sym| { |
| 778 | | log.debug(" {s}: {}", .{ self.getString(sym.n_strx), sym }); |
| 779 | | } |
| 780 | | log.debug("globals:", .{}); |
| 781 | | for (self.globals.items) |sym| { |
| 782 | | log.debug(" {s}: {}", .{ self.getString(sym.n_strx), sym }); |
| 783 | | } |
| 784 | | log.debug("undefs:", .{}); |
| 785 | | for (self.undefs.items) |sym| { |
| 786 | | log.debug(" {s}: {}", .{ self.getString(sym.n_strx), sym }); |
| 787 | | } |
| 788 | | log.debug("unresolved:", .{}); |
| 789 | | for (self.unresolved.keys()) |key| { |
| 790 | | log.debug(" {d} => {s}", .{ key, self.unresolved.get(key).? }); |
| 791 | | } |
| 792 | | log.debug("resolved:", .{}); |
| 793 | | var it = self.symbol_resolver.iterator(); |
| 794 | | while (it.next()) |entry| { |
| 795 | | log.debug(" {s} => {}", .{ self.getString(entry.key_ptr.*), entry.value_ptr.* }); |
| 796 | | } |
| 764 | try self.parseTextBlocks(); |
| 765 | try self.allocateGlobalSymbols(); |
| 766 | { |
| 767 | log.debug("locals:", .{}); |
| 768 | for (self.locals.items) |sym| { |
| 769 | log.debug(" {s}: {}", .{ self.getString(sym.n_strx), sym }); |
| 770 | } |
| 771 | log.debug("globals:", .{}); |
| 772 | for (self.globals.items) |sym| { |
| 773 | log.debug(" {s}: {}", .{ self.getString(sym.n_strx), sym }); |
| 774 | } |
| 775 | log.debug("undefs:", .{}); |
| 776 | for (self.undefs.items) |sym| { |
| 777 | log.debug(" {s}: {}", .{ self.getString(sym.n_strx), sym }); |
| 778 | } |
| 779 | log.debug("unresolved:", .{}); |
| 780 | for (self.unresolved.keys()) |key| { |
| 781 | log.debug(" {d} => {s}", .{ key, self.unresolved.get(key).? }); |
| 782 | } |
| 783 | log.debug("resolved:", .{}); |
| 784 | var it = self.symbol_resolver.iterator(); |
| 785 | while (it.next()) |entry| { |
| 786 | log.debug(" {s} => {}", .{ self.getString(entry.key_ptr.*), entry.value_ptr.* }); |
| 797 | 787 | } |
| 798 | | try self.writeAtoms(); |
| 799 | | try self.flushModule(comp); |
| 800 | 788 | } |
| 789 | try self.writeAtoms(); |
| 790 | try self.writeDices(); |
| 791 | try self.flushModule(comp); |
| 801 | 792 | } |
| 802 | 793 | |
| 803 | 794 | if (!self.base.options.disable_lld_caching) { |
| ... | ... | @@ -1638,248 +1629,6 @@ pub fn getMatchingSection(self: *MachO, sect: macho.section_64) !?MatchingSectio |
| 1638 | 1629 | return res; |
| 1639 | 1630 | } |
| 1640 | 1631 | |
| 1641 | | fn allocateTextSegment(self: *MachO) !void { |
| 1642 | | const seg = &self.load_commands.items[self.text_segment_cmd_index.?].Segment; |
| 1643 | | const base_vmaddr = self.load_commands.items[self.pagezero_segment_cmd_index.?].Segment.inner.vmsize; |
| 1644 | | seg.inner.fileoff = 0; |
| 1645 | | seg.inner.vmaddr = base_vmaddr; |
| 1646 | | |
| 1647 | | var sizeofcmds: u64 = 0; |
| 1648 | | for (self.load_commands.items) |lc| { |
| 1649 | | sizeofcmds += lc.cmdsize(); |
| 1650 | | } |
| 1651 | | |
| 1652 | | try self.allocateSegment(self.text_segment_cmd_index.?, @sizeOf(macho.mach_header_64) + sizeofcmds); |
| 1653 | | |
| 1654 | | // Shift all sections to the back to minimize jump size between __TEXT and __DATA segments. |
| 1655 | | var min_alignment: u32 = 0; |
| 1656 | | for (seg.sections.items) |sect| { |
| 1657 | | const alignment = try math.powi(u32, 2, sect.@"align"); |
| 1658 | | min_alignment = math.max(min_alignment, alignment); |
| 1659 | | } |
| 1660 | | |
| 1661 | | assert(min_alignment > 0); |
| 1662 | | const last_sect_idx = seg.sections.items.len - 1; |
| 1663 | | const last_sect = seg.sections.items[last_sect_idx]; |
| 1664 | | const shift: u32 = blk: { |
| 1665 | | const diff = seg.inner.filesize - last_sect.offset - last_sect.size; |
| 1666 | | const factor = @divTrunc(diff, min_alignment); |
| 1667 | | break :blk @intCast(u32, factor * min_alignment); |
| 1668 | | }; |
| 1669 | | |
| 1670 | | if (shift > 0) { |
| 1671 | | for (seg.sections.items) |*sect| { |
| 1672 | | sect.offset += shift; |
| 1673 | | sect.addr += shift; |
| 1674 | | } |
| 1675 | | } |
| 1676 | | } |
| 1677 | | |
| 1678 | | fn allocateDataConstSegment(self: *MachO) !void { |
| 1679 | | const seg = &self.load_commands.items[self.data_const_segment_cmd_index.?].Segment; |
| 1680 | | const text_seg = self.load_commands.items[self.text_segment_cmd_index.?].Segment; |
| 1681 | | seg.inner.fileoff = text_seg.inner.fileoff + text_seg.inner.filesize; |
| 1682 | | seg.inner.vmaddr = text_seg.inner.vmaddr + text_seg.inner.vmsize; |
| 1683 | | try self.allocateSegment(self.data_const_segment_cmd_index.?, 0); |
| 1684 | | } |
| 1685 | | |
| 1686 | | fn allocateDataSegment(self: *MachO) !void { |
| 1687 | | const seg = &self.load_commands.items[self.data_segment_cmd_index.?].Segment; |
| 1688 | | const data_const_seg = self.load_commands.items[self.data_const_segment_cmd_index.?].Segment; |
| 1689 | | seg.inner.fileoff = data_const_seg.inner.fileoff + data_const_seg.inner.filesize; |
| 1690 | | seg.inner.vmaddr = data_const_seg.inner.vmaddr + data_const_seg.inner.vmsize; |
| 1691 | | try self.allocateSegment(self.data_segment_cmd_index.?, 0); |
| 1692 | | } |
| 1693 | | |
| 1694 | | fn allocateLinkeditSegment(self: *MachO) void { |
| 1695 | | const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; |
| 1696 | | const data_seg = self.load_commands.items[self.data_segment_cmd_index.?].Segment; |
| 1697 | | seg.inner.fileoff = data_seg.inner.fileoff + data_seg.inner.filesize; |
| 1698 | | seg.inner.vmaddr = data_seg.inner.vmaddr + data_seg.inner.vmsize; |
| 1699 | | } |
| 1700 | | |
| 1701 | | fn allocateSegment(self: *MachO, index: u16, offset: u64) !void { |
| 1702 | | const seg = &self.load_commands.items[index].Segment; |
| 1703 | | |
| 1704 | | // Allocate the sections according to their alignment at the beginning of the segment. |
| 1705 | | var start: u64 = offset; |
| 1706 | | for (seg.sections.items) |*sect, sect_id| { |
| 1707 | | const alignment = try math.powi(u32, 2, sect.@"align"); |
| 1708 | | const start_aligned = mem.alignForwardGeneric(u64, start, alignment); |
| 1709 | | const end_aligned = mem.alignForwardGeneric(u64, start_aligned + sect.size, alignment); |
| 1710 | | const file_offset = @intCast(u32, seg.inner.fileoff + start_aligned); |
| 1711 | | |
| 1712 | | blk: { |
| 1713 | | if (index == self.data_segment_cmd_index.?) { |
| 1714 | | if (self.bss_section_index) |idx| { |
| 1715 | | if (sect_id == idx) { |
| 1716 | | self.bss_file_offset = file_offset; |
| 1717 | | break :blk; |
| 1718 | | } |
| 1719 | | } |
| 1720 | | if (self.tlv_bss_section_index) |idx| { |
| 1721 | | if (sect_id == idx) { |
| 1722 | | self.tlv_bss_file_offset = file_offset; |
| 1723 | | break :blk; |
| 1724 | | } |
| 1725 | | } |
| 1726 | | } |
| 1727 | | sect.offset = @intCast(u32, seg.inner.fileoff + start_aligned); |
| 1728 | | } |
| 1729 | | |
| 1730 | | sect.addr = seg.inner.vmaddr + start_aligned; |
| 1731 | | start = end_aligned; |
| 1732 | | } |
| 1733 | | |
| 1734 | | const seg_size_aligned = mem.alignForwardGeneric(u64, start, self.page_size); |
| 1735 | | seg.inner.filesize = seg_size_aligned; |
| 1736 | | seg.inner.vmsize = seg_size_aligned; |
| 1737 | | } |
| 1738 | | |
| 1739 | | fn allocateTextBlocks(self: *MachO) !void { |
| 1740 | | var it = self.blocks.iterator(); |
| 1741 | | while (it.next()) |entry| { |
| 1742 | | const match = entry.key_ptr.*; |
| 1743 | | var block: *TextBlock = entry.value_ptr.*; |
| 1744 | | |
| 1745 | | // Find the first block |
| 1746 | | while (block.prev) |prev| { |
| 1747 | | block = prev; |
| 1748 | | } |
| 1749 | | |
| 1750 | | const seg = self.load_commands.items[match.seg].Segment; |
| 1751 | | const sect = seg.sections.items[match.sect]; |
| 1752 | | |
| 1753 | | var base_addr: u64 = sect.addr; |
| 1754 | | const n_sect = @intCast(u8, self.section_ordinals.getIndex(match).? + 1); |
| 1755 | | |
| 1756 | | log.debug(" within section {s},{s}", .{ commands.segmentName(sect), commands.sectionName(sect) }); |
| 1757 | | log.debug(" {}", .{sect}); |
| 1758 | | |
| 1759 | | while (true) { |
| 1760 | | const block_alignment = try math.powi(u32, 2, block.alignment); |
| 1761 | | base_addr = mem.alignForwardGeneric(u64, base_addr, block_alignment); |
| 1762 | | |
| 1763 | | const sym = &self.locals.items[block.local_sym_index]; |
| 1764 | | sym.n_value = base_addr; |
| 1765 | | sym.n_sect = n_sect; |
| 1766 | | |
| 1767 | | log.debug(" {s}: start=0x{x}, end=0x{x}, size={}, align={}", .{ |
| 1768 | | self.getString(sym.n_strx), |
| 1769 | | base_addr, |
| 1770 | | base_addr + block.size, |
| 1771 | | block.size, |
| 1772 | | block.alignment, |
| 1773 | | }); |
| 1774 | | |
| 1775 | | // Update each alias (if any) |
| 1776 | | for (block.aliases.items) |index| { |
| 1777 | | const alias_sym = &self.locals.items[index]; |
| 1778 | | alias_sym.n_value = base_addr; |
| 1779 | | alias_sym.n_sect = n_sect; |
| 1780 | | } |
| 1781 | | |
| 1782 | | // Update each symbol contained within the TextBlock |
| 1783 | | for (block.contained.items) |sym_at_off| { |
| 1784 | | const contained_sym = &self.locals.items[sym_at_off.local_sym_index]; |
| 1785 | | contained_sym.n_value = base_addr + sym_at_off.offset; |
| 1786 | | contained_sym.n_sect = n_sect; |
| 1787 | | } |
| 1788 | | |
| 1789 | | base_addr += block.size; |
| 1790 | | |
| 1791 | | if (block.next) |next| { |
| 1792 | | block = next; |
| 1793 | | } else break; |
| 1794 | | } |
| 1795 | | } |
| 1796 | | |
| 1797 | | // Update globals |
| 1798 | | { |
| 1799 | | var sym_it = self.symbol_resolver.valueIterator(); |
| 1800 | | while (sym_it.next()) |resolv| { |
| 1801 | | if (resolv.where != .global) continue; |
| 1802 | | |
| 1803 | | assert(resolv.local_sym_index != 0); |
| 1804 | | const local_sym = self.locals.items[resolv.local_sym_index]; |
| 1805 | | const sym = &self.globals.items[resolv.where_index]; |
| 1806 | | sym.n_value = local_sym.n_value; |
| 1807 | | sym.n_sect = local_sym.n_sect; |
| 1808 | | } |
| 1809 | | } |
| 1810 | | } |
| 1811 | | |
| 1812 | | fn writeTextBlocks(self: *MachO) !void { |
| 1813 | | var it = self.blocks.iterator(); |
| 1814 | | while (it.next()) |entry| { |
| 1815 | | const match = entry.key_ptr.*; |
| 1816 | | var block: *TextBlock = entry.value_ptr.*; |
| 1817 | | |
| 1818 | | while (block.prev) |prev| { |
| 1819 | | block = prev; |
| 1820 | | } |
| 1821 | | |
| 1822 | | const seg = self.load_commands.items[match.seg].Segment; |
| 1823 | | const sect = seg.sections.items[match.sect]; |
| 1824 | | const sect_type = commands.sectionType(sect); |
| 1825 | | |
| 1826 | | log.debug(" for section {s},{s}", .{ commands.segmentName(sect), commands.sectionName(sect) }); |
| 1827 | | log.debug(" {}", .{sect}); |
| 1828 | | |
| 1829 | | var code = try self.base.allocator.alloc(u8, sect.size); |
| 1830 | | defer self.base.allocator.free(code); |
| 1831 | | |
| 1832 | | const file_offset: u64 = blk: { |
| 1833 | | if (self.data_segment_cmd_index.? == match.seg) { |
| 1834 | | if (self.bss_section_index) |idx| { |
| 1835 | | if (idx == match.sect) break :blk self.bss_file_offset.?; |
| 1836 | | } |
| 1837 | | if (self.tlv_bss_section_index) |idx| { |
| 1838 | | if (idx == match.sect) break :blk self.tlv_bss_file_offset.?; |
| 1839 | | } |
| 1840 | | } |
| 1841 | | break :blk sect.offset; |
| 1842 | | }; |
| 1843 | | |
| 1844 | | if (sect_type == macho.S_ZEROFILL or sect_type == macho.S_THREAD_LOCAL_ZEROFILL) { |
| 1845 | | mem.set(u8, code, 0); |
| 1846 | | } else { |
| 1847 | | var base_off: u64 = 0; |
| 1848 | | |
| 1849 | | while (true) { |
| 1850 | | const block_alignment = try math.powi(u32, 2, block.alignment); |
| 1851 | | const aligned_base_off = mem.alignForwardGeneric(u64, base_off, block_alignment); |
| 1852 | | |
| 1853 | | const sym = self.locals.items[block.local_sym_index]; |
| 1854 | | log.debug(" {s}: start=0x{x}, end=0x{x}, size={}, align={}", .{ |
| 1855 | | self.getString(sym.n_strx), |
| 1856 | | aligned_base_off, |
| 1857 | | aligned_base_off + block.size, |
| 1858 | | block.size, |
| 1859 | | block.alignment, |
| 1860 | | }); |
| 1861 | | |
| 1862 | | try block.resolveRelocs(self); |
| 1863 | | mem.copy(u8, code[aligned_base_off..][0..block.size], block.code.items); |
| 1864 | | |
| 1865 | | // TODO NOP for machine code instead of just zeroing out |
| 1866 | | const padding_len = aligned_base_off - base_off; |
| 1867 | | mem.set(u8, code[base_off..][0..padding_len], 0); |
| 1868 | | |
| 1869 | | base_off = aligned_base_off + block.size; |
| 1870 | | |
| 1871 | | if (block.next) |next| { |
| 1872 | | block = next; |
| 1873 | | } else break; |
| 1874 | | } |
| 1875 | | |
| 1876 | | mem.set(u8, code[base_off..], 0); |
| 1877 | | } |
| 1878 | | |
| 1879 | | try self.base.file.?.pwriteAll(code, file_offset); |
| 1880 | | } |
| 1881 | | } |
| 1882 | | |
| 1883 | 1632 | pub fn createEmptyAtom(self: *MachO, local_sym_index: u32, size: u64, alignment: u32) !*TextBlock { |
| 1884 | 1633 | const code = try self.base.allocator.alloc(u8, size); |
| 1885 | 1634 | defer self.base.allocator.free(code); |
| ... | ... | @@ -2920,17 +2669,23 @@ fn parseTextBlocks(self: *MachO) !void { |
| 2920 | 2669 | } |
| 2921 | 2670 | |
| 2922 | 2671 | fn addDataInCodeLC(self: *MachO) !void { |
| 2923 | | if (self.data_in_code_cmd_index == null) { |
| 2924 | | self.data_in_code_cmd_index = @intCast(u16, self.load_commands.items.len); |
| 2925 | | try self.load_commands.append(self.base.allocator, .{ |
| 2926 | | .LinkeditData = .{ |
| 2927 | | .cmd = macho.LC_DATA_IN_CODE, |
| 2928 | | .cmdsize = @sizeOf(macho.linkedit_data_command), |
| 2929 | | .dataoff = 0, |
| 2930 | | .datasize = 0, |
| 2931 | | }, |
| 2932 | | }); |
| 2933 | | } |
| 2672 | if (self.data_in_code_cmd_index != null) return; |
| 2673 | self.data_in_code_cmd_index = @intCast(u16, self.load_commands.items.len); |
| 2674 | try self.load_commands.append(self.base.allocator, .{ |
| 2675 | .LinkeditData = .{ |
| 2676 | .cmd = macho.LC_DATA_IN_CODE, |
| 2677 | .cmdsize = @sizeOf(macho.linkedit_data_command), |
| 2678 | .dataoff = 0, |
| 2679 | .datasize = 0, |
| 2680 | }, |
| 2681 | }); |
| 2682 | const dice_cmd = &self.load_commands.items[self.data_in_code_cmd_index.?].LinkeditData; |
| 2683 | const needed_size = 10 * @sizeOf(macho.data_in_code_entry); |
| 2684 | const dataoff = self.findFreeSpaceLinkedit(needed_size, @alignOf(macho.data_in_code_entry), null); |
| 2685 | log.debug("found data-in-code free space 0x{x} to 0x{x}", .{ dataoff, dataoff + needed_size }); |
| 2686 | dice_cmd.dataoff = @intCast(u32, dataoff); |
| 2687 | dice_cmd.datasize = needed_size; |
| 2688 | self.load_commands_dirty = true; |
| 2934 | 2689 | } |
| 2935 | 2690 | |
| 2936 | 2691 | fn addCodeSignatureLC(self: *MachO) !void { |
| ... | ... | @@ -2982,42 +2737,6 @@ fn addLoadDylibLCs(self: *MachO) !void { |
| 2982 | 2737 | } |
| 2983 | 2738 | } |
| 2984 | 2739 | |
| 2985 | | fn flushZld(self: *MachO) !void { |
| 2986 | | try self.writeTextBlocks(); |
| 2987 | | try self.setEntryPoint(); |
| 2988 | | try self.writeRebaseInfoTableZld(); |
| 2989 | | try self.writeBindInfoTableZld(); |
| 2990 | | try self.writeLazyBindInfoTableZld(); |
| 2991 | | try self.writeExportInfoZld(); |
| 2992 | | try self.writeDices(); |
| 2993 | | |
| 2994 | | { |
| 2995 | | const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; |
| 2996 | | const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab; |
| 2997 | | symtab.symoff = @intCast(u32, seg.inner.fileoff + seg.inner.filesize); |
| 2998 | | } |
| 2999 | | |
| 3000 | | try self.writeSymbolTable(); |
| 3001 | | try self.writeStringTableZld(); |
| 3002 | | |
| 3003 | | { |
| 3004 | | // Seal __LINKEDIT size |
| 3005 | | const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; |
| 3006 | | seg.inner.vmsize = mem.alignForwardGeneric(u64, seg.inner.filesize, self.page_size); |
| 3007 | | } |
| 3008 | | |
| 3009 | | if (self.requires_adhoc_codesig) { |
| 3010 | | try self.writeCodeSignaturePadding(); |
| 3011 | | } |
| 3012 | | |
| 3013 | | try self.writeLoadCommands(); |
| 3014 | | try self.writeHeader(); |
| 3015 | | |
| 3016 | | if (self.requires_adhoc_codesig) { |
| 3017 | | try self.writeCodeSignature(); |
| 3018 | | } |
| 3019 | | } |
| 3020 | | |
| 3021 | 2740 | fn setEntryPoint(self: *MachO) !void { |
| 3022 | 2741 | if (self.base.options.output_mode != .Exe) return; |
| 3023 | 2742 | |
| ... | ... | @@ -3039,343 +2758,6 @@ fn setEntryPoint(self: *MachO) !void { |
| 3039 | 2758 | self.load_commands_dirty = true; |
| 3040 | 2759 | } |
| 3041 | 2760 | |
| 3042 | | fn writeRebaseInfoTableZld(self: *MachO) !void { |
| 3043 | | var pointers = std.ArrayList(bind.Pointer).init(self.base.allocator); |
| 3044 | | defer pointers.deinit(); |
| 3045 | | |
| 3046 | | { |
| 3047 | | var it = self.blocks.iterator(); |
| 3048 | | while (it.next()) |entry| { |
| 3049 | | const match = entry.key_ptr.*; |
| 3050 | | var block: *TextBlock = entry.value_ptr.*; |
| 3051 | | |
| 3052 | | if (match.seg == self.text_segment_cmd_index.?) continue; // __TEXT is non-writable |
| 3053 | | |
| 3054 | | const seg = self.load_commands.items[match.seg].Segment; |
| 3055 | | |
| 3056 | | while (true) { |
| 3057 | | const sym = self.locals.items[block.local_sym_index]; |
| 3058 | | const base_offset = sym.n_value - seg.inner.vmaddr; |
| 3059 | | |
| 3060 | | for (block.rebases.items) |offset| { |
| 3061 | | try pointers.append(.{ |
| 3062 | | .offset = base_offset + offset, |
| 3063 | | .segment_id = match.seg, |
| 3064 | | }); |
| 3065 | | } |
| 3066 | | |
| 3067 | | if (block.prev) |prev| { |
| 3068 | | block = prev; |
| 3069 | | } else break; |
| 3070 | | } |
| 3071 | | } |
| 3072 | | } |
| 3073 | | |
| 3074 | | const size = try bind.rebaseInfoSize(pointers.items); |
| 3075 | | var buffer = try self.base.allocator.alloc(u8, @intCast(usize, size)); |
| 3076 | | defer self.base.allocator.free(buffer); |
| 3077 | | |
| 3078 | | var stream = std.io.fixedBufferStream(buffer); |
| 3079 | | try bind.writeRebaseInfo(pointers.items, stream.writer()); |
| 3080 | | |
| 3081 | | const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; |
| 3082 | | const dyld_info = &self.load_commands.items[self.dyld_info_cmd_index.?].DyldInfoOnly; |
| 3083 | | dyld_info.rebase_off = @intCast(u32, seg.inner.fileoff); |
| 3084 | | dyld_info.rebase_size = @intCast(u32, mem.alignForwardGeneric(u64, buffer.len, @sizeOf(u64))); |
| 3085 | | seg.inner.filesize += dyld_info.rebase_size; |
| 3086 | | |
| 3087 | | log.debug("writing rebase info from 0x{x} to 0x{x}", .{ dyld_info.rebase_off, dyld_info.rebase_off + dyld_info.rebase_size }); |
| 3088 | | |
| 3089 | | try self.base.file.?.pwriteAll(buffer, dyld_info.rebase_off); |
| 3090 | | } |
| 3091 | | |
| 3092 | | fn writeBindInfoTableZld(self: *MachO) !void { |
| 3093 | | var pointers = std.ArrayList(bind.Pointer).init(self.base.allocator); |
| 3094 | | defer pointers.deinit(); |
| 3095 | | |
| 3096 | | { |
| 3097 | | var it = self.blocks.iterator(); |
| 3098 | | while (it.next()) |entry| { |
| 3099 | | const match = entry.key_ptr.*; |
| 3100 | | var block: *TextBlock = entry.value_ptr.*; |
| 3101 | | |
| 3102 | | if (match.seg == self.text_segment_cmd_index.?) continue; // __TEXT is non-writable |
| 3103 | | |
| 3104 | | const seg = self.load_commands.items[match.seg].Segment; |
| 3105 | | |
| 3106 | | while (true) { |
| 3107 | | const sym = self.locals.items[block.local_sym_index]; |
| 3108 | | const base_offset = sym.n_value - seg.inner.vmaddr; |
| 3109 | | |
| 3110 | | for (block.bindings.items) |binding| { |
| 3111 | | const bind_sym = self.undefs.items[binding.local_sym_index]; |
| 3112 | | try pointers.append(.{ |
| 3113 | | .offset = binding.offset + base_offset, |
| 3114 | | .segment_id = match.seg, |
| 3115 | | .dylib_ordinal = @divExact(bind_sym.n_desc, macho.N_SYMBOL_RESOLVER), |
| 3116 | | .name = self.getString(bind_sym.n_strx), |
| 3117 | | }); |
| 3118 | | } |
| 3119 | | |
| 3120 | | if (block.prev) |prev| { |
| 3121 | | block = prev; |
| 3122 | | } else break; |
| 3123 | | } |
| 3124 | | } |
| 3125 | | } |
| 3126 | | |
| 3127 | | const size = try bind.bindInfoSize(pointers.items); |
| 3128 | | var buffer = try self.base.allocator.alloc(u8, @intCast(usize, size)); |
| 3129 | | defer self.base.allocator.free(buffer); |
| 3130 | | |
| 3131 | | var stream = std.io.fixedBufferStream(buffer); |
| 3132 | | try bind.writeBindInfo(pointers.items, stream.writer()); |
| 3133 | | |
| 3134 | | const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; |
| 3135 | | const dyld_info = &self.load_commands.items[self.dyld_info_cmd_index.?].DyldInfoOnly; |
| 3136 | | dyld_info.bind_off = @intCast(u32, seg.inner.fileoff + seg.inner.filesize); |
| 3137 | | dyld_info.bind_size = @intCast(u32, mem.alignForwardGeneric(u64, buffer.len, @alignOf(u64))); |
| 3138 | | seg.inner.filesize += dyld_info.bind_size; |
| 3139 | | |
| 3140 | | log.debug("writing binding info from 0x{x} to 0x{x}", .{ dyld_info.bind_off, dyld_info.bind_off + dyld_info.bind_size }); |
| 3141 | | |
| 3142 | | try self.base.file.?.pwriteAll(buffer, dyld_info.bind_off); |
| 3143 | | } |
| 3144 | | |
| 3145 | | fn writeLazyBindInfoTableZld(self: *MachO) !void { |
| 3146 | | var pointers = std.ArrayList(bind.Pointer).init(self.base.allocator); |
| 3147 | | defer pointers.deinit(); |
| 3148 | | |
| 3149 | | if (self.la_symbol_ptr_section_index) |sect| blk: { |
| 3150 | | var atom = self.blocks.get(.{ |
| 3151 | | .seg = self.data_segment_cmd_index.?, |
| 3152 | | .sect = sect, |
| 3153 | | }) orelse break :blk; |
| 3154 | | const seg = self.load_commands.items[self.data_segment_cmd_index.?].Segment; |
| 3155 | | |
| 3156 | | while (true) { |
| 3157 | | const sym = self.locals.items[atom.local_sym_index]; |
| 3158 | | const base_offset = sym.n_value - seg.inner.vmaddr; |
| 3159 | | |
| 3160 | | for (atom.lazy_bindings.items) |binding| { |
| 3161 | | const bind_sym = self.undefs.items[binding.local_sym_index]; |
| 3162 | | try pointers.append(.{ |
| 3163 | | .offset = binding.offset + base_offset, |
| 3164 | | .segment_id = self.data_segment_cmd_index.?, |
| 3165 | | .dylib_ordinal = @divExact(bind_sym.n_desc, macho.N_SYMBOL_RESOLVER), |
| 3166 | | .name = self.getString(bind_sym.n_strx), |
| 3167 | | }); |
| 3168 | | } |
| 3169 | | if (atom.prev) |prev| { |
| 3170 | | atom = prev; |
| 3171 | | } else break; |
| 3172 | | } |
| 3173 | | } |
| 3174 | | |
| 3175 | | const size = try bind.lazyBindInfoSize(pointers.items); |
| 3176 | | var buffer = try self.base.allocator.alloc(u8, @intCast(usize, size)); |
| 3177 | | defer self.base.allocator.free(buffer); |
| 3178 | | |
| 3179 | | var stream = std.io.fixedBufferStream(buffer); |
| 3180 | | try bind.writeLazyBindInfo(pointers.items, stream.writer()); |
| 3181 | | |
| 3182 | | const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; |
| 3183 | | const dyld_info = &self.load_commands.items[self.dyld_info_cmd_index.?].DyldInfoOnly; |
| 3184 | | dyld_info.lazy_bind_off = @intCast(u32, seg.inner.fileoff + seg.inner.filesize); |
| 3185 | | dyld_info.lazy_bind_size = @intCast(u32, mem.alignForwardGeneric(u64, buffer.len, @alignOf(u64))); |
| 3186 | | seg.inner.filesize += dyld_info.lazy_bind_size; |
| 3187 | | |
| 3188 | | log.debug("writing lazy binding info from 0x{x} to 0x{x}", .{ dyld_info.lazy_bind_off, dyld_info.lazy_bind_off + dyld_info.lazy_bind_size }); |
| 3189 | | |
| 3190 | | try self.base.file.?.pwriteAll(buffer, dyld_info.lazy_bind_off); |
| 3191 | | try self.populateLazyBindOffsetsInStubHelper(buffer); |
| 3192 | | } |
| 3193 | | |
| 3194 | | fn writeExportInfoZld(self: *MachO) !void { |
| 3195 | | var trie: Trie = .{}; |
| 3196 | | defer trie.deinit(self.base.allocator); |
| 3197 | | |
| 3198 | | const text_segment = self.load_commands.items[self.text_segment_cmd_index.?].Segment; |
| 3199 | | const base_address = text_segment.inner.vmaddr; |
| 3200 | | |
| 3201 | | // TODO handle macho.EXPORT_SYMBOL_FLAGS_REEXPORT and macho.EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER. |
| 3202 | | log.debug("writing export trie", .{}); |
| 3203 | | |
| 3204 | | for (self.globals.items) |sym| { |
| 3205 | | const sym_name = self.getString(sym.n_strx); |
| 3206 | | log.debug(" | putting '{s}' defined at 0x{x}", .{ sym_name, sym.n_value }); |
| 3207 | | |
| 3208 | | try trie.put(self.base.allocator, .{ |
| 3209 | | .name = sym_name, |
| 3210 | | .vmaddr_offset = sym.n_value - base_address, |
| 3211 | | .export_flags = macho.EXPORT_SYMBOL_FLAGS_KIND_REGULAR, |
| 3212 | | }); |
| 3213 | | } |
| 3214 | | |
| 3215 | | try trie.finalize(self.base.allocator); |
| 3216 | | |
| 3217 | | var buffer = try self.base.allocator.alloc(u8, @intCast(usize, trie.size)); |
| 3218 | | defer self.base.allocator.free(buffer); |
| 3219 | | |
| 3220 | | var stream = std.io.fixedBufferStream(buffer); |
| 3221 | | const nwritten = try trie.write(stream.writer()); |
| 3222 | | assert(nwritten == trie.size); |
| 3223 | | |
| 3224 | | const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; |
| 3225 | | const dyld_info = &self.load_commands.items[self.dyld_info_cmd_index.?].DyldInfoOnly; |
| 3226 | | dyld_info.export_off = @intCast(u32, seg.inner.fileoff + seg.inner.filesize); |
| 3227 | | dyld_info.export_size = @intCast(u32, mem.alignForwardGeneric(u64, buffer.len, @alignOf(u64))); |
| 3228 | | seg.inner.filesize += dyld_info.export_size; |
| 3229 | | |
| 3230 | | log.debug("writing export info from 0x{x} to 0x{x}", .{ dyld_info.export_off, dyld_info.export_off + dyld_info.export_size }); |
| 3231 | | |
| 3232 | | try self.base.file.?.pwriteAll(buffer, dyld_info.export_off); |
| 3233 | | } |
| 3234 | | |
| 3235 | | fn writeSymbolTable(self: *MachO) !void { |
| 3236 | | const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; |
| 3237 | | const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab; |
| 3238 | | |
| 3239 | | var locals = std.ArrayList(macho.nlist_64).init(self.base.allocator); |
| 3240 | | defer locals.deinit(); |
| 3241 | | try locals.appendSlice(self.locals.items); |
| 3242 | | |
| 3243 | | if (self.has_stabs) { |
| 3244 | | for (self.objects.items) |object| { |
| 3245 | | if (object.debug_info == null) continue; |
| 3246 | | |
| 3247 | | // Open scope |
| 3248 | | try locals.ensureUnusedCapacity(3); |
| 3249 | | locals.appendAssumeCapacity(.{ |
| 3250 | | .n_strx = try self.makeString(object.tu_comp_dir.?), |
| 3251 | | .n_type = macho.N_SO, |
| 3252 | | .n_sect = 0, |
| 3253 | | .n_desc = 0, |
| 3254 | | .n_value = 0, |
| 3255 | | }); |
| 3256 | | locals.appendAssumeCapacity(.{ |
| 3257 | | .n_strx = try self.makeString(object.tu_name.?), |
| 3258 | | .n_type = macho.N_SO, |
| 3259 | | .n_sect = 0, |
| 3260 | | .n_desc = 0, |
| 3261 | | .n_value = 0, |
| 3262 | | }); |
| 3263 | | locals.appendAssumeCapacity(.{ |
| 3264 | | .n_strx = try self.makeString(object.name), |
| 3265 | | .n_type = macho.N_OSO, |
| 3266 | | .n_sect = 0, |
| 3267 | | .n_desc = 1, |
| 3268 | | .n_value = object.mtime orelse 0, |
| 3269 | | }); |
| 3270 | | |
| 3271 | | for (object.text_blocks.items) |block| { |
| 3272 | | if (block.stab) |stab| { |
| 3273 | | const nlists = try stab.asNlists(block.local_sym_index, self); |
| 3274 | | defer self.base.allocator.free(nlists); |
| 3275 | | try locals.appendSlice(nlists); |
| 3276 | | } else { |
| 3277 | | for (block.contained.items) |sym_at_off| { |
| 3278 | | const stab = sym_at_off.stab orelse continue; |
| 3279 | | const nlists = try stab.asNlists(sym_at_off.local_sym_index, self); |
| 3280 | | defer self.base.allocator.free(nlists); |
| 3281 | | try locals.appendSlice(nlists); |
| 3282 | | } |
| 3283 | | } |
| 3284 | | } |
| 3285 | | |
| 3286 | | // Close scope |
| 3287 | | try locals.append(.{ |
| 3288 | | .n_strx = 0, |
| 3289 | | .n_type = macho.N_SO, |
| 3290 | | .n_sect = 0, |
| 3291 | | .n_desc = 0, |
| 3292 | | .n_value = 0, |
| 3293 | | }); |
| 3294 | | } |
| 3295 | | } |
| 3296 | | |
| 3297 | | const nlocals = locals.items.len; |
| 3298 | | const nexports = self.globals.items.len; |
| 3299 | | const nundefs = self.undefs.items.len; |
| 3300 | | |
| 3301 | | const locals_off = symtab.symoff; |
| 3302 | | const locals_size = nlocals * @sizeOf(macho.nlist_64); |
| 3303 | | log.debug("writing local symbols from 0x{x} to 0x{x}", .{ locals_off, locals_size + locals_off }); |
| 3304 | | try self.base.file.?.pwriteAll(mem.sliceAsBytes(locals.items), locals_off); |
| 3305 | | |
| 3306 | | const exports_off = locals_off + locals_size; |
| 3307 | | const exports_size = nexports * @sizeOf(macho.nlist_64); |
| 3308 | | log.debug("writing exported symbols from 0x{x} to 0x{x}", .{ exports_off, exports_size + exports_off }); |
| 3309 | | try self.base.file.?.pwriteAll(mem.sliceAsBytes(self.globals.items), exports_off); |
| 3310 | | |
| 3311 | | const undefs_off = exports_off + exports_size; |
| 3312 | | const undefs_size = nundefs * @sizeOf(macho.nlist_64); |
| 3313 | | log.debug("writing undefined symbols from 0x{x} to 0x{x}", .{ undefs_off, undefs_size + undefs_off }); |
| 3314 | | try self.base.file.?.pwriteAll(mem.sliceAsBytes(self.undefs.items), undefs_off); |
| 3315 | | |
| 3316 | | symtab.nsyms = @intCast(u32, nlocals + nexports + nundefs); |
| 3317 | | seg.inner.filesize += locals_size + exports_size + undefs_size; |
| 3318 | | |
| 3319 | | // Update dynamic symbol table. |
| 3320 | | const dysymtab = &self.load_commands.items[self.dysymtab_cmd_index.?].Dysymtab; |
| 3321 | | dysymtab.nlocalsym = @intCast(u32, nlocals); |
| 3322 | | dysymtab.iextdefsym = dysymtab.nlocalsym; |
| 3323 | | dysymtab.nextdefsym = @intCast(u32, nexports); |
| 3324 | | dysymtab.iundefsym = dysymtab.nlocalsym + dysymtab.nextdefsym; |
| 3325 | | dysymtab.nundefsym = @intCast(u32, nundefs); |
| 3326 | | |
| 3327 | | const text_segment = &self.load_commands.items[self.text_segment_cmd_index.?].Segment; |
| 3328 | | const stubs = &text_segment.sections.items[self.stubs_section_index.?]; |
| 3329 | | const data_const_segment = &self.load_commands.items[self.data_const_segment_cmd_index.?].Segment; |
| 3330 | | const got = &data_const_segment.sections.items[self.got_section_index.?]; |
| 3331 | | const data_segment = &self.load_commands.items[self.data_segment_cmd_index.?].Segment; |
| 3332 | | const la_symbol_ptr = &data_segment.sections.items[self.la_symbol_ptr_section_index.?]; |
| 3333 | | |
| 3334 | | const nstubs = @intCast(u32, self.stubs_map.keys().len); |
| 3335 | | const ngot_entries = @intCast(u32, self.got_entries_map.keys().len); |
| 3336 | | |
| 3337 | | dysymtab.indirectsymoff = @intCast(u32, seg.inner.fileoff + seg.inner.filesize); |
| 3338 | | dysymtab.nindirectsyms = nstubs * 2 + ngot_entries; |
| 3339 | | |
| 3340 | | const needed_size = dysymtab.nindirectsyms * @sizeOf(u32); |
| 3341 | | seg.inner.filesize += needed_size; |
| 3342 | | |
| 3343 | | log.debug("writing indirect symbol table from 0x{x} to 0x{x}", .{ |
| 3344 | | dysymtab.indirectsymoff, |
| 3345 | | dysymtab.indirectsymoff + needed_size, |
| 3346 | | }); |
| 3347 | | |
| 3348 | | var buf = try self.base.allocator.alloc(u8, needed_size); |
| 3349 | | defer self.base.allocator.free(buf); |
| 3350 | | |
| 3351 | | var stream = std.io.fixedBufferStream(buf); |
| 3352 | | var writer = stream.writer(); |
| 3353 | | |
| 3354 | | stubs.reserved1 = 0; |
| 3355 | | for (self.stubs_map.keys()) |key| { |
| 3356 | | try writer.writeIntLittle(u32, dysymtab.iundefsym + key); |
| 3357 | | } |
| 3358 | | |
| 3359 | | got.reserved1 = nstubs; |
| 3360 | | for (self.got_entries_map.keys()) |key| { |
| 3361 | | switch (key.where) { |
| 3362 | | .undef => { |
| 3363 | | try writer.writeIntLittle(u32, dysymtab.iundefsym + key.where_index); |
| 3364 | | }, |
| 3365 | | .local => { |
| 3366 | | try writer.writeIntLittle(u32, macho.INDIRECT_SYMBOL_LOCAL); |
| 3367 | | }, |
| 3368 | | } |
| 3369 | | } |
| 3370 | | |
| 3371 | | la_symbol_ptr.reserved1 = got.reserved1 + ngot_entries; |
| 3372 | | for (self.stubs_map.keys()) |key| { |
| 3373 | | try writer.writeIntLittle(u32, dysymtab.iundefsym + key); |
| 3374 | | } |
| 3375 | | |
| 3376 | | try self.base.file.?.pwriteAll(buf, dysymtab.indirectsymoff); |
| 3377 | | } |
| 3378 | | |
| 3379 | 2761 | pub fn deinit(self: *MachO) void { |
| 3380 | 2762 | if (build_options.have_llvm) { |
| 3381 | 2763 | if (self.llvm_object) |llvm_object| llvm_object.destroy(self.base.allocator); |
| ... | ... | @@ -3812,12 +3194,6 @@ fn placeDecl(self: *MachO, decl: *Module.Decl, code_len: usize) !*macho.nlist_64 |
| 3812 | 3194 | try ds.writeLocalSymbol(decl.link.macho.local_sym_index); |
| 3813 | 3195 | } |
| 3814 | 3196 | |
| 3815 | | // // Resolve relocations |
| 3816 | | // try decl.link.macho.resolveRelocs(self); |
| 3817 | | // // TODO this requires further investigation: should we dispose of resolved relocs, or keep them |
| 3818 | | // // so that we can reapply them when moving/growing sections? |
| 3819 | | // decl.link.macho.relocs.clearAndFree(self.base.allocator); |
| 3820 | | |
| 3821 | 3197 | return symbol; |
| 3822 | 3198 | } |
| 3823 | 3199 | |
| ... | ... | @@ -5003,9 +4379,8 @@ fn writeIndirectSymbolTable(self: *MachO) !void { |
| 5003 | 4379 | fn writeDices(self: *MachO) !void { |
| 5004 | 4380 | if (!self.has_dices) return; |
| 5005 | 4381 | |
| 5006 | | const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; |
| 5007 | | const dice_cmd = &self.load_commands.items[self.data_in_code_cmd_index.?].LinkeditData; |
| 5008 | | const fileoff = seg.inner.fileoff + seg.inner.filesize; |
| 4382 | const tracy = trace(@src()); |
| 4383 | defer tracy.end(); |
| 5009 | 4384 | |
| 5010 | 4385 | var buf = std.ArrayList(u8).init(self.base.allocator); |
| 5011 | 4386 | defer buf.deinit(); |
| ... | ... | @@ -5043,15 +4418,26 @@ fn writeDices(self: *MachO) !void { |
| 5043 | 4418 | } else break; |
| 5044 | 4419 | } |
| 5045 | 4420 | |
| 5046 | | const datasize = @intCast(u32, buf.items.len); |
| 5047 | | |
| 5048 | | dice_cmd.dataoff = @intCast(u32, fileoff); |
| 5049 | | dice_cmd.datasize = datasize; |
| 5050 | | seg.inner.filesize += datasize; |
| 4421 | const dice_cmd = &self.load_commands.items[self.data_in_code_cmd_index.?].LinkeditData; |
| 4422 | const allocated_size = self.allocatedSizeLinkedit(dice_cmd.dataoff); |
| 4423 | const needed_size = @intCast(u32, buf.items.len); |
| 5051 | 4424 | |
| 5052 | | log.debug("writing data-in-code from 0x{x} to 0x{x}", .{ fileoff, fileoff + datasize }); |
| 4425 | if (needed_size > allocated_size) { |
| 4426 | dice_cmd.datasize = 0; |
| 4427 | dice_cmd.dataoff = @intCast(u32, self.findFreeSpaceLinkedit( |
| 4428 | needed_size, |
| 4429 | @alignOf(macho.data_in_code_entry), |
| 4430 | dice_cmd.dataoff, |
| 4431 | )); |
| 4432 | } |
| 4433 | dice_cmd.datasize = needed_size; |
| 4434 | log.debug("writing data-in-code from 0x{x} to 0x{x}", .{ |
| 4435 | dice_cmd.dataoff, |
| 4436 | dice_cmd.dataoff + dice_cmd.datasize, |
| 4437 | }); |
| 5053 | 4438 | |
| 5054 | | try self.base.file.?.pwriteAll(buf.items, fileoff); |
| 4439 | try self.base.file.?.pwriteAll(buf.items, dice_cmd.dataoff); |
| 4440 | self.load_commands_dirty = true; |
| 5055 | 4441 | } |
| 5056 | 4442 | |
| 5057 | 4443 | fn writeCodeSignaturePadding(self: *MachO) !void { |
| ... | ... | @@ -5130,7 +4516,7 @@ fn writeExportInfo(self: *MachO) !void { |
| 5130 | 4516 | |
| 5131 | 4517 | for (self.globals.items) |sym| { |
| 5132 | 4518 | const sym_name = self.getString(sym.n_strx); |
| 5133 | | log.debug(" | putting '{s}' defined at 0x{x}", .{ sym_name, sym.n_value }); |
| 4519 | log.debug(" (putting '{s}' defined at 0x{x})", .{ sym_name, sym.n_value }); |
| 5134 | 4520 | |
| 5135 | 4521 | try trie.put(self.base.allocator, .{ |
| 5136 | 4522 | .name = sym_name, |
| ... | ... | @@ -5453,30 +4839,16 @@ fn writeStringTable(self: *MachO) !void { |
| 5453 | 4839 | self.strtab_needs_relocation = false; |
| 5454 | 4840 | } |
| 5455 | 4841 | symtab.strsize = @intCast(u32, needed_size); |
| 5456 | | log.debug("writing string table from 0x{x} to 0x{x}", .{ symtab.stroff, symtab.stroff + symtab.strsize }); |
| 4842 | log.debug("writing string table from 0x{x} to 0x{x}", .{ |
| 4843 | symtab.stroff, |
| 4844 | symtab.stroff + symtab.strsize, |
| 4845 | }); |
| 5457 | 4846 | |
| 5458 | 4847 | try self.base.file.?.pwriteAll(self.strtab.items, symtab.stroff); |
| 5459 | 4848 | self.load_commands_dirty = true; |
| 5460 | 4849 | self.strtab_dirty = false; |
| 5461 | 4850 | } |
| 5462 | 4851 | |
| 5463 | | fn writeStringTableZld(self: *MachO) !void { |
| 5464 | | const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; |
| 5465 | | const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab; |
| 5466 | | symtab.stroff = @intCast(u32, seg.inner.fileoff + seg.inner.filesize); |
| 5467 | | symtab.strsize = @intCast(u32, mem.alignForwardGeneric(u64, self.strtab.items.len, @alignOf(u64))); |
| 5468 | | seg.inner.filesize += symtab.strsize; |
| 5469 | | |
| 5470 | | log.debug("writing string table from 0x{x} to 0x{x}", .{ symtab.stroff, symtab.stroff + symtab.strsize }); |
| 5471 | | |
| 5472 | | try self.base.file.?.pwriteAll(self.strtab.items, symtab.stroff); |
| 5473 | | |
| 5474 | | if (symtab.strsize > self.strtab.items.len) { |
| 5475 | | // This is potentially the last section, so we need to pad it out. |
| 5476 | | try self.base.file.?.pwriteAll(&[_]u8{0}, seg.inner.fileoff + seg.inner.filesize - 1); |
| 5477 | | } |
| 5478 | | } |
| 5479 | | |
| 5480 | 4852 | fn updateLinkeditSegmentSizes(self: *MachO) !void { |
| 5481 | 4853 | if (!self.load_commands_dirty) return; |
| 5482 | 4854 | |