authorgravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2021-02-23 23:45:36+01:00
committergravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2021-03-17 19:59:13+01:00
logf52f23618d51367bfd498403e303090039f3b6b6
tree6bc21d040e26ca85845b00f53f1de6460613e243
parent0f7b036eb75f52c69e8458bd12563a7ecf4c9237

macho: start upstreaming zld


9 files changed, 2950 insertions(+), 0 deletions(-)

src/Compilation.zig+2
...@@ -447,6 +447,7 @@ pub const InitOptions = struct {...@@ -447,6 +447,7 @@ pub const InitOptions = struct {
447 want_lto: ?bool = null,447 want_lto: ?bool = null,
448 use_llvm: ?bool = null,448 use_llvm: ?bool = null,
449 use_lld: ?bool = null,449 use_lld: ?bool = null,
450 use_zld: ?bool = null,
450 use_clang: ?bool = null,451 use_clang: ?bool = null,
451 rdynamic: bool = false,452 rdynamic: bool = false,
452 strip: bool = false,453 strip: bool = false,
...@@ -1020,6 +1021,7 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {...@@ -1020,6 +1021,7 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
1020 .link_mode = link_mode,1021 .link_mode = link_mode,
1021 .object_format = ofmt,1022 .object_format = ofmt,
1022 .optimize_mode = options.optimize_mode,1023 .optimize_mode = options.optimize_mode,
1024 .use_zld = options.use_zld orelse false,
1023 .use_lld = use_lld,1025 .use_lld = use_lld,
1024 .use_llvm = use_llvm,1026 .use_llvm = use_llvm,
1025 .system_linker_hack = darwin_options.system_linker_hack,1027 .system_linker_hack = darwin_options.system_linker_hack,
src/link.zig+2
...@@ -61,6 +61,8 @@ pub const Options = struct {...@@ -61,6 +61,8 @@ pub const Options = struct {
61 /// Darwin-only. If this is true, `use_llvm` is true, and `is_native_os` is true, this link code will61 /// Darwin-only. If this is true, `use_llvm` is true, and `is_native_os` is true, this link code will
62 /// use system linker `ld` instead of the LLD.62 /// use system linker `ld` instead of the LLD.
63 system_linker_hack: bool,63 system_linker_hack: bool,
64 /// Experimental Zig linker.
65 use_zld: bool,
64 link_libc: bool,66 link_libc: bool,
65 link_libcpp: bool,67 link_libcpp: bool,
66 function_sections: bool,68 function_sections: bool,
src/link/MachO.zig+26
...@@ -26,6 +26,7 @@ const target_util = @import("../target.zig");...@@ -26,6 +26,7 @@ const target_util = @import("../target.zig");
26const DebugSymbols = @import("MachO/DebugSymbols.zig");26const DebugSymbols = @import("MachO/DebugSymbols.zig");
27const Trie = @import("MachO/Trie.zig");27const Trie = @import("MachO/Trie.zig");
28const CodeSignature = @import("MachO/CodeSignature.zig");28const CodeSignature = @import("MachO/CodeSignature.zig");
29const Zld = @import("MachO/Zld.zig");
2930
30usingnamespace @import("MachO/commands.zig");31usingnamespace @import("MachO/commands.zig");
3132
...@@ -637,6 +638,31 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void {...@@ -637,6 +638,31 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void {
637 var argv = std.ArrayList([]const u8).init(self.base.allocator);638 var argv = std.ArrayList([]const u8).init(self.base.allocator);
638 defer argv.deinit();639 defer argv.deinit();
639640
641 if (true) {
642 // if (self.base.options.use_zld) {
643 var zld = Zld.init(self.base.allocator);
644 defer zld.deinit();
645 zld.arch = target.cpu.arch;
646
647 var input_files = std.ArrayList([]const u8).init(self.base.allocator);
648 defer input_files.deinit();
649 // Positional arguments to the linker such as object files.
650 try input_files.appendSlice(self.base.options.objects);
651 for (comp.c_object_table.items()) |entry| {
652 try input_files.append(entry.key.status.success.object_path);
653 }
654 if (module_obj_path) |p| {
655 try input_files.append(p);
656 }
657 try input_files.append(comp.compiler_rt_static_lib.?.full_object_path);
658 // libc++ dep
659 if (self.base.options.link_libcpp) {
660 try input_files.append(comp.libcxxabi_static_lib.?.full_object_path);
661 try input_files.append(comp.libcxx_static_lib.?.full_object_path);
662 }
663 return zld.link(input_files.items, full_out_path);
664 }
665
640 // TODO https://github.com/ziglang/zig/issues/6971666 // TODO https://github.com/ziglang/zig/issues/6971
641 // Note that there is no need to check if running natively since we do that already667 // Note that there is no need to check if running natively since we do that already
642 // when setting `system_linker_hack` in Compilation struct.668 // when setting `system_linker_hack` in Compilation struct.
src/link/MachO/Archive.zig created+247
...@@ -0,0 +1,247 @@
1const Archive = @This();
2
3const std = @import("std");
4const assert = std.debug.assert;
5const fs = std.fs;
6const log = std.log.scoped(.archive);
7const macho = std.macho;
8const mem = std.mem;
9
10const Allocator = mem.Allocator;
11const Object = @import("Object.zig");
12const parseName = @import("Zld.zig").parseName;
13
14usingnamespace @import("commands.zig");
15
16allocator: *Allocator,
17file: fs.File,
18header: ar_hdr,
19name: []u8,
20
21objects: std.ArrayListUnmanaged(Object) = .{},
22
23// Archive files start with the ARMAG identifying string. Then follows a
24// `struct ar_hdr', and as many bytes of member file data as its `ar_size'
25// member indicates, for each member file.
26/// String that begins an archive file.
27const ARMAG: *const [SARMAG:0]u8 = "!<arch>\n";
28/// Size of that string.
29const SARMAG: u4 = 8;
30
31/// String in ar_fmag at the end of each header.
32const ARFMAG: *const [2:0]u8 = "`\n";
33
34const ar_hdr = extern struct {
35 /// Member file name, sometimes / terminated.
36 ar_name: [16]u8,
37
38 /// File date, decimal seconds since Epoch.
39 ar_date: [12]u8,
40
41 /// User ID, in ASCII format.
42 ar_uid: [6]u8,
43
44 /// Group ID, in ASCII format.
45 ar_gid: [6]u8,
46
47 /// File mode, in ASCII octal.
48 ar_mode: [8]u8,
49
50 /// File size, in ASCII decimal.
51 ar_size: [10]u8,
52
53 /// Always contains ARFMAG.
54 ar_fmag: [2]u8,
55
56 const NameOrLength = union(enum) {
57 Name: []const u8,
58 Length: u64,
59 };
60 pub fn nameOrLength(self: ar_hdr) !NameOrLength {
61 const value = getValue(&self.ar_name);
62 const slash_index = mem.indexOf(u8, value, "/") orelse return error.MalformedArchive;
63 const len = value.len;
64 if (slash_index == len - 1) {
65 // Name stored directly
66 return NameOrLength{ .Name = value };
67 } else {
68 // Name follows the header directly and its length is encoded in
69 // the name field.
70 const length = try std.fmt.parseInt(u64, value[slash_index + 1 ..], 10);
71 return NameOrLength{ .Length = length };
72 }
73 }
74
75 pub fn size(self: ar_hdr) !u64 {
76 const value = getValue(&self.ar_size);
77 return std.fmt.parseInt(u64, value, 10);
78 }
79
80 fn getValue(raw: []const u8) []const u8 {
81 return mem.trimRight(u8, raw, &[_]u8{@as(u8, 0x20)});
82 }
83};
84
85pub fn deinit(self: *Archive) void {
86 self.allocator.free(self.name);
87 for (self.objects.items) |*object| {
88 object.deinit();
89 }
90 self.objects.deinit(self.allocator);
91 self.file.close();
92}
93
94/// Caller owns the returned Archive instance and is responsible for calling
95/// `deinit` to free allocated memory.
96pub fn initFromFile(allocator: *Allocator, arch: std.Target.Cpu.Arch, ar_name: []const u8, file: fs.File) !Archive {
97 var reader = file.reader();
98 var magic = try readMagic(allocator, reader);
99 defer allocator.free(magic);
100
101 if (!mem.eql(u8, magic, ARMAG)) {
102 // Reset file cursor.
103 try file.seekTo(0);
104 return error.NotArchive;
105 }
106
107 const header = try reader.readStruct(ar_hdr);
108
109 if (!mem.eql(u8, &header.ar_fmag, ARFMAG))
110 return error.MalformedArchive;
111
112 var embedded_name = try getName(allocator, header, reader);
113 log.debug("parsing archive '{s}' at '{s}'", .{ embedded_name, ar_name });
114 defer allocator.free(embedded_name);
115
116 var name = try allocator.dupe(u8, ar_name);
117 var self = Archive{
118 .allocator = allocator,
119 .file = file,
120 .header = header,
121 .name = name,
122 };
123
124 var object_offsets = try self.readTableOfContents(reader);
125 defer self.allocator.free(object_offsets);
126
127 var i: usize = 1;
128 while (i < object_offsets.len) : (i += 1) {
129 const offset = object_offsets[i];
130 try reader.context.seekTo(offset);
131 try self.readObject(arch, ar_name, reader);
132 }
133
134 return self;
135}
136
137fn readTableOfContents(self: *Archive, reader: anytype) ![]u32 {
138 const symtab_size = try reader.readIntLittle(u32);
139 var symtab = try self.allocator.alloc(u8, symtab_size);
140 defer self.allocator.free(symtab);
141 try reader.readNoEof(symtab);
142
143 const strtab_size = try reader.readIntLittle(u32);
144 var strtab = try self.allocator.alloc(u8, strtab_size);
145 defer self.allocator.free(strtab);
146 try reader.readNoEof(strtab);
147
148 var symtab_stream = std.io.fixedBufferStream(symtab);
149 var symtab_reader = symtab_stream.reader();
150
151 var object_offsets = std.ArrayList(u32).init(self.allocator);
152 try object_offsets.append(0);
153 var last: usize = 0;
154
155 while (true) {
156 const n_strx = symtab_reader.readIntLittle(u32) catch |err| switch (err) {
157 error.EndOfStream => break,
158 else => |e| return e,
159 };
160 const object_offset = try symtab_reader.readIntLittle(u32);
161
162 // TODO Store the table of contents for later reuse.
163
164 // Here, we assume that symbols are NOT sorted in any way, and
165 // they point to objects in sequence.
166 if (object_offsets.items[last] != object_offset) {
167 try object_offsets.append(object_offset);
168 last += 1;
169 }
170 }
171
172 return object_offsets.toOwnedSlice();
173}
174
175fn readObject(self: *Archive, arch: std.Target.Cpu.Arch, ar_name: []const u8, reader: anytype) !void {
176 const object_header = try reader.readStruct(ar_hdr);
177
178 if (!mem.eql(u8, &object_header.ar_fmag, ARFMAG))
179 return error.MalformedArchive;
180
181 var object_name = try getName(self.allocator, object_header, reader);
182 log.debug("extracting object '{s}' from archive '{s}'", .{ object_name, self.name });
183
184 const offset = @intCast(u32, try reader.context.getPos());
185 const header = try reader.readStruct(macho.mach_header_64);
186
187 const this_arch: std.Target.Cpu.Arch = switch (header.cputype) {
188 macho.CPU_TYPE_ARM64 => .aarch64,
189 macho.CPU_TYPE_X86_64 => .x86_64,
190 else => |value| {
191 log.err("unsupported cpu architecture 0x{x}", .{value});
192 return error.UnsupportedCpuArchitecture;
193 },
194 };
195 if (this_arch != arch) {
196 log.err("mismatched cpu architecture: found {s}, expected {s}", .{ this_arch, arch });
197 return error.MismatchedCpuArchitecture;
198 }
199
200 // TODO Implement std.fs.File.clone() or similar.
201 var new_file = try fs.cwd().openFile(ar_name, .{});
202 var object = Object{
203 .allocator = self.allocator,
204 .name = object_name,
205 .file = new_file,
206 .header = header,
207 };
208
209 try object.readLoadCommands(reader, .{ .offset = offset });
210 try object.readSymtab();
211 try object.readStrtab();
212
213 log.debug("\n\n", .{});
214 log.debug("{s} defines symbols", .{object.name});
215 for (object.symtab.items) |sym| {
216 const symname = object.getString(sym.n_strx);
217 log.debug("'{s}': {}", .{ symname, sym });
218 }
219
220 try self.objects.append(self.allocator, object);
221}
222
223fn readMagic(allocator: *Allocator, reader: anytype) ![]u8 {
224 var magic = std.ArrayList(u8).init(allocator);
225 try magic.ensureCapacity(SARMAG);
226 var i: usize = 0;
227 while (i < SARMAG) : (i += 1) {
228 const next = try reader.readByte();
229 magic.appendAssumeCapacity(next);
230 }
231 return magic.toOwnedSlice();
232}
233
234fn getName(allocator: *Allocator, header: ar_hdr, reader: anytype) ![]u8 {
235 const name_or_length = try header.nameOrLength();
236 var name: []u8 = undefined;
237 switch (name_or_length) {
238 .Name => |n| {
239 name = try allocator.dupe(u8, n);
240 },
241 .Length => |len| {
242 name = try allocator.alloc(u8, len);
243 try reader.readNoEof(name);
244 },
245 }
246 return name;
247}
src/link/MachO/Object.zig created+204
...@@ -0,0 +1,204 @@
1const Object = @This();
2
3const std = @import("std");
4const assert = std.debug.assert;
5const fs = std.fs;
6const log = std.log.scoped(.object);
7const macho = std.macho;
8const mem = std.mem;
9
10const Allocator = mem.Allocator;
11const parseName = @import("Zld.zig").parseName;
12
13usingnamespace @import("commands.zig");
14
15allocator: *Allocator,
16file: fs.File,
17name: []u8,
18
19header: macho.mach_header_64,
20
21load_commands: std.ArrayListUnmanaged(LoadCommand) = .{},
22
23segment_cmd_index: ?u16 = null,
24symtab_cmd_index: ?u16 = null,
25dysymtab_cmd_index: ?u16 = null,
26build_version_cmd_index: ?u16 = null,
27
28text_section_index: ?u16 = null,
29
30dwarf_debug_info_index: ?u16 = null,
31dwarf_debug_abbrev_index: ?u16 = null,
32dwarf_debug_str_index: ?u16 = null,
33dwarf_debug_line_index: ?u16 = null,
34dwarf_debug_ranges_index: ?u16 = null,
35
36symtab: std.ArrayListUnmanaged(macho.nlist_64) = .{},
37strtab: std.ArrayListUnmanaged(u8) = .{},
38
39directory: std.AutoHashMapUnmanaged(DirectoryKey, u16) = .{},
40
41pub const DirectoryKey = struct {
42 segname: [16]u8,
43 sectname: [16]u8,
44};
45
46pub fn deinit(self: *Object) void {
47 for (self.load_commands.items) |*lc| {
48 lc.deinit(self.allocator);
49 }
50 self.load_commands.deinit(self.allocator);
51 self.symtab.deinit(self.allocator);
52 self.strtab.deinit(self.allocator);
53 self.directory.deinit(self.allocator);
54 self.allocator.free(self.name);
55 self.file.close();
56}
57
58/// Caller owns the returned Object instance and is responsible for calling
59/// `deinit` to free allocated memory.
60pub fn initFromFile(allocator: *Allocator, arch: std.Target.Cpu.Arch, name: []const u8, file: fs.File) !Object {
61 var reader = file.reader();
62 const header = try reader.readStruct(macho.mach_header_64);
63
64 if (header.filetype != macho.MH_OBJECT) {
65 // Reset file cursor.
66 try file.seekTo(0);
67 return error.NotObject;
68 }
69
70 const this_arch: std.Target.Cpu.Arch = switch (header.cputype) {
71 macho.CPU_TYPE_ARM64 => .aarch64,
72 macho.CPU_TYPE_X86_64 => .x86_64,
73 else => |value| {
74 log.err("unsupported cpu architecture 0x{x}", .{value});
75 return error.UnsupportedCpuArchitecture;
76 },
77 };
78 if (this_arch != arch) {
79 log.err("mismatched cpu architecture: found {s}, expected {s}", .{ this_arch, arch });
80 return error.MismatchedCpuArchitecture;
81 }
82
83 var self = Object{
84 .allocator = allocator,
85 .name = try allocator.dupe(u8, name),
86 .file = file,
87 .header = header,
88 };
89
90 try self.readLoadCommands(reader, .{});
91 try self.readSymtab();
92 try self.readStrtab();
93
94 log.debug("\n\n", .{});
95 log.debug("{s} defines symbols", .{self.name});
96 for (self.symtab.items) |sym| {
97 const symname = self.getString(sym.n_strx);
98 log.debug("'{s}': {}", .{ symname, sym });
99 }
100
101 return self;
102}
103
104pub const ReadOffset = struct {
105 offset: ?u32 = null,
106};
107
108pub fn readLoadCommands(self: *Object, reader: anytype, offset: ReadOffset) !void {
109 const offset_mod = offset.offset orelse 0;
110 try self.load_commands.ensureCapacity(self.allocator, self.header.ncmds);
111
112 var i: u16 = 0;
113 while (i < self.header.ncmds) : (i += 1) {
114 var cmd = try LoadCommand.read(self.allocator, reader);
115 switch (cmd.cmd()) {
116 macho.LC_SEGMENT_64 => {
117 self.segment_cmd_index = i;
118 var seg = cmd.Segment;
119 for (seg.sections.items) |*sect, j| {
120 const index = @intCast(u16, j);
121 const segname = parseName(&sect.segname);
122 const sectname = parseName(&sect.sectname);
123 if (mem.eql(u8, segname, "__DWARF")) {
124 if (mem.eql(u8, sectname, "__debug_info")) {
125 self.dwarf_debug_info_index = index;
126 } else if (mem.eql(u8, sectname, "__debug_abbrev")) {
127 self.dwarf_debug_abbrev_index = index;
128 } else if (mem.eql(u8, sectname, "__debug_str")) {
129 self.dwarf_debug_str_index = index;
130 } else if (mem.eql(u8, sectname, "__debug_line")) {
131 self.dwarf_debug_line_index = index;
132 } else if (mem.eql(u8, sectname, "__debug_ranges")) {
133 self.dwarf_debug_ranges_index = index;
134 }
135 } else if (mem.eql(u8, segname, "__TEXT")) {
136 if (mem.eql(u8, sectname, "__text")) {
137 self.text_section_index = index;
138 }
139 }
140
141 try self.directory.putNoClobber(self.allocator, .{
142 .segname = sect.segname,
143 .sectname = sect.sectname,
144 }, index);
145
146 sect.offset += offset_mod;
147 if (sect.reloff > 0)
148 sect.reloff += offset_mod;
149 }
150
151 seg.inner.fileoff += offset_mod;
152 },
153 macho.LC_SYMTAB => {
154 self.symtab_cmd_index = i;
155 cmd.Symtab.symoff += offset_mod;
156 cmd.Symtab.stroff += offset_mod;
157 },
158 macho.LC_DYSYMTAB => {
159 self.dysymtab_cmd_index = i;
160 },
161 macho.LC_BUILD_VERSION => {
162 self.build_version_cmd_index = i;
163 },
164 else => {
165 log.info("Unknown load command detected: 0x{x}.", .{cmd.cmd()});
166 },
167 }
168 self.load_commands.appendAssumeCapacity(cmd);
169 }
170}
171
172pub fn readSymtab(self: *Object) !void {
173 const symtab_cmd = self.load_commands.items[self.symtab_cmd_index.?].Symtab;
174 var buffer = try self.allocator.alloc(u8, @sizeOf(macho.nlist_64) * symtab_cmd.nsyms);
175 defer self.allocator.free(buffer);
176 _ = try self.file.preadAll(buffer, symtab_cmd.symoff);
177 try self.symtab.ensureCapacity(self.allocator, symtab_cmd.nsyms);
178 // TODO this align case should not be needed.
179 // Probably a bug in stage1.
180 const slice = @alignCast(@alignOf(macho.nlist_64), mem.bytesAsSlice(macho.nlist_64, buffer));
181 self.symtab.appendSliceAssumeCapacity(slice);
182}
183
184pub fn readStrtab(self: *Object) !void {
185 const symtab_cmd = self.load_commands.items[self.symtab_cmd_index.?].Symtab;
186 var buffer = try self.allocator.alloc(u8, symtab_cmd.strsize);
187 defer self.allocator.free(buffer);
188 _ = try self.file.preadAll(buffer, symtab_cmd.stroff);
189 try self.strtab.ensureCapacity(self.allocator, symtab_cmd.strsize);
190 self.strtab.appendSliceAssumeCapacity(buffer);
191}
192
193pub fn getString(self: *const Object, str_off: u32) []const u8 {
194 assert(str_off < self.strtab.items.len);
195 return mem.spanZ(@ptrCast([*:0]const u8, self.strtab.items.ptr + str_off));
196}
197
198pub fn readSection(self: Object, allocator: *Allocator, index: u16) ![]u8 {
199 const seg = self.load_commands.items[self.segment_cmd_index.?].Segment;
200 const sect = seg.sections.items[index];
201 var buffer = try allocator.alloc(u8, sect.size);
202 _ = try self.file.preadAll(buffer, sect.offset);
203 return buffer;
204}
src/link/MachO/Zld.zig created+2301
...@@ -0,0 +1,2301 @@
1const Zld = @This();
2
3const std = @import("std");
4const assert = std.debug.assert;
5const dwarf = std.dwarf;
6const leb = std.leb;
7const mem = std.mem;
8const meta = std.meta;
9const fs = std.fs;
10const macho = std.macho;
11const math = std.math;
12const log = std.log.scoped(.zld);
13
14const Allocator = mem.Allocator;
15const CodeSignature = @import("CodeSignature.zig");
16const Archive = @import("Archive.zig");
17const Object = @import("Object.zig");
18const Trie = @import("Trie.zig");
19
20usingnamespace @import("commands.zig");
21usingnamespace @import("bind.zig");
22usingnamespace @import("reloc.zig");
23
24allocator: *Allocator,
25
26arch: ?std.Target.Cpu.Arch = null,
27page_size: ?u16 = null,
28file: ?fs.File = null,
29out_path: ?[]const u8 = null,
30
31// TODO Eventually, we will want to keep track of the archives themselves to be able to exclude objects
32// contained within from landing in the final artifact. For now however, since we don't optimise the binary
33// at all, we just move all objects from the archives into the final artifact.
34objects: std.ArrayListUnmanaged(Object) = .{},
35
36load_commands: std.ArrayListUnmanaged(LoadCommand) = .{},
37
38pagezero_segment_cmd_index: ?u16 = null,
39text_segment_cmd_index: ?u16 = null,
40data_segment_cmd_index: ?u16 = null,
41linkedit_segment_cmd_index: ?u16 = null,
42dyld_info_cmd_index: ?u16 = null,
43symtab_cmd_index: ?u16 = null,
44dysymtab_cmd_index: ?u16 = null,
45dylinker_cmd_index: ?u16 = null,
46libsystem_cmd_index: ?u16 = null,
47data_in_code_cmd_index: ?u16 = null,
48function_starts_cmd_index: ?u16 = null,
49main_cmd_index: ?u16 = null,
50version_min_cmd_index: ?u16 = null,
51source_version_cmd_index: ?u16 = null,
52uuid_cmd_index: ?u16 = null,
53code_signature_cmd_index: ?u16 = null,
54
55text_section_index: ?u16 = null,
56stubs_section_index: ?u16 = null,
57stub_helper_section_index: ?u16 = null,
58got_section_index: ?u16 = null,
59tlv_section_index: ?u16 = null,
60la_symbol_ptr_section_index: ?u16 = null,
61data_section_index: ?u16 = null,
62
63locals: std.StringArrayHashMapUnmanaged(macho.nlist_64) = .{},
64exports: std.StringArrayHashMapUnmanaged(macho.nlist_64) = .{},
65nonlazy_imports: std.StringArrayHashMapUnmanaged(Import) = .{},
66lazy_imports: std.StringArrayHashMapUnmanaged(Import) = .{},
67threadlocal_imports: std.StringArrayHashMapUnmanaged(Import) = .{},
68local_rebases: std.ArrayListUnmanaged(Pointer) = .{},
69
70strtab: std.ArrayListUnmanaged(u8) = .{},
71
72stub_helper_stubs_start_off: ?u64 = null,
73
74segments_directory: std.AutoHashMapUnmanaged([16]u8, u16) = .{},
75directory: std.AutoHashMapUnmanaged(DirectoryKey, DirectoryEntry) = .{},
76
77const DirectoryKey = struct {
78 segname: [16]u8,
79 sectname: [16]u8,
80};
81
82const DirectoryEntry = struct {
83 seg_index: u16,
84 sect_index: u16,
85};
86
87const DebugInfo = struct {
88 inner: dwarf.DwarfInfo,
89 debug_info: []u8,
90 debug_abbrev: []u8,
91 debug_str: []u8,
92 debug_line: []u8,
93 debug_ranges: []u8,
94
95 pub fn parseFromObject(allocator: *Allocator, object: Object) !?DebugInfo {
96 var debug_info = blk: {
97 const index = object.dwarf_debug_info_index orelse return null;
98 break :blk try object.readSection(allocator, index);
99 };
100 var debug_abbrev = blk: {
101 const index = object.dwarf_debug_abbrev_index orelse return null;
102 break :blk try object.readSection(allocator, index);
103 };
104 var debug_str = blk: {
105 const index = object.dwarf_debug_str_index orelse return null;
106 break :blk try object.readSection(allocator, index);
107 };
108 var debug_line = blk: {
109 const index = object.dwarf_debug_line_index orelse return null;
110 break :blk try object.readSection(allocator, index);
111 };
112 var debug_ranges = blk: {
113 if (object.dwarf_debug_ranges_index) |ind| {
114 break :blk try object.readSection(allocator, ind);
115 }
116 break :blk try allocator.alloc(u8, 0);
117 };
118
119 var inner: dwarf.DwarfInfo = .{
120 .endian = .Little,
121 .debug_info = debug_info,
122 .debug_abbrev = debug_abbrev,
123 .debug_str = debug_str,
124 .debug_line = debug_line,
125 .debug_ranges = debug_ranges,
126 };
127 try dwarf.openDwarfDebugInfo(&inner, allocator);
128
129 return DebugInfo{
130 .inner = inner,
131 .debug_info = debug_info,
132 .debug_abbrev = debug_abbrev,
133 .debug_str = debug_str,
134 .debug_line = debug_line,
135 .debug_ranges = debug_ranges,
136 };
137 }
138
139 pub fn deinit(self: *DebugInfo, allocator: *Allocator) void {
140 allocator.free(self.debug_info);
141 allocator.free(self.debug_abbrev);
142 allocator.free(self.debug_str);
143 allocator.free(self.debug_line);
144 allocator.free(self.debug_ranges);
145 self.inner.abbrev_table_list.deinit();
146 self.inner.compile_unit_list.deinit();
147 self.inner.func_list.deinit();
148 }
149};
150
151pub const Import = struct {
152 /// MachO symbol table entry.
153 symbol: macho.nlist_64,
154
155 /// Id of the dynamic library where the specified entries can be found.
156 dylib_ordinal: i64,
157
158 /// Index of this import within the import list.
159 index: u32,
160};
161
162/// Default path to dyld
163/// TODO instead of hardcoding it, we should probably look through some env vars and search paths
164/// instead but this will do for now.
165const DEFAULT_DYLD_PATH: [*:0]const u8 = "/usr/lib/dyld";
166
167/// Default lib search path
168/// TODO instead of hardcoding it, we should probably look through some env vars and search paths
169/// instead but this will do for now.
170const DEFAULT_LIB_SEARCH_PATH: []const u8 = "/usr/lib";
171
172const LIB_SYSTEM_NAME: [*:0]const u8 = "System";
173/// TODO we should search for libSystem and fail if it doesn't exist, instead of hardcoding it
174const LIB_SYSTEM_PATH: [*:0]const u8 = DEFAULT_LIB_SEARCH_PATH ++ "/libSystem.B.dylib";
175
176pub fn init(allocator: *Allocator) Zld {
177 return .{ .allocator = allocator };
178}
179
180pub fn deinit(self: *Zld) void {
181 self.strtab.deinit(self.allocator);
182 self.local_rebases.deinit(self.allocator);
183 for (self.lazy_imports.items()) |*entry| {
184 self.allocator.free(entry.key);
185 }
186 self.lazy_imports.deinit(self.allocator);
187 for (self.threadlocal_imports.items()) |*entry| {
188 self.allocator.free(entry.key);
189 }
190 self.threadlocal_imports.deinit(self.allocator);
191 for (self.nonlazy_imports.items()) |*entry| {
192 self.allocator.free(entry.key);
193 }
194 self.nonlazy_imports.deinit(self.allocator);
195 for (self.exports.items()) |*entry| {
196 self.allocator.free(entry.key);
197 }
198 self.exports.deinit(self.allocator);
199 for (self.locals.items()) |*entry| {
200 self.allocator.free(entry.key);
201 }
202 self.locals.deinit(self.allocator);
203 for (self.objects.items) |*object| {
204 object.deinit();
205 }
206 self.objects.deinit(self.allocator);
207 for (self.load_commands.items) |*lc| {
208 lc.deinit(self.allocator);
209 }
210 self.load_commands.deinit(self.allocator);
211 self.segments_directory.deinit(self.allocator);
212 self.directory.deinit(self.allocator);
213 if (self.file) |*f| f.close();
214}
215
216pub fn link(self: *Zld, files: []const []const u8, out_path: []const u8) !void {
217 if (files.len == 0) return error.NoInputFiles;
218 if (out_path.len == 0) return error.EmptyOutputPath;
219
220 if (self.arch == null) {
221 // Try inferring the arch from the object files.
222 self.arch = blk: {
223 const file = try fs.cwd().openFile(files[0], .{});
224 defer file.close();
225 var reader = file.reader();
226 const header = try reader.readStruct(macho.mach_header_64);
227 const arch: std.Target.Cpu.Arch = switch (header.cputype) {
228 macho.CPU_TYPE_X86_64 => .x86_64,
229 macho.CPU_TYPE_ARM64 => .aarch64,
230 else => |value| {
231 log.err("unsupported cpu architecture 0x{x}", .{value});
232 return error.UnsupportedCpuArchitecture;
233 },
234 };
235 break :blk arch;
236 };
237 }
238
239 self.page_size = switch (self.arch.?) {
240 .aarch64 => 0x4000,
241 .x86_64 => 0x1000,
242 else => unreachable,
243 };
244 self.out_path = out_path;
245 self.file = try fs.cwd().createFile(out_path, .{
246 .truncate = true,
247 .read = true,
248 .mode = if (std.Target.current.os.tag == .windows) 0 else 0o777,
249 });
250
251 try self.populateMetadata();
252 try self.parseInputFiles(files);
253 try self.resolveImports();
254 self.allocateTextSegment();
255 self.allocateDataSegment();
256 self.allocateLinkeditSegment();
257 try self.writeStubHelperCommon();
258 try self.resolveSymbols();
259 try self.doRelocs();
260 try self.flush();
261}
262
263fn parseInputFiles(self: *Zld, files: []const []const u8) !void {
264 for (files) |file_name| {
265 const file = try fs.cwd().openFile(file_name, .{});
266
267 try_object: {
268 var object = Object.initFromFile(self.allocator, self.arch.?, file_name, file) catch |err| switch (err) {
269 error.NotObject => break :try_object,
270 else => |e| return e,
271 };
272 const index = self.objects.items.len;
273 try self.objects.append(self.allocator, object);
274 const p_object = &self.objects.items[index];
275 try self.parseObjectFile(p_object);
276 continue;
277 }
278
279 try_archive: {
280 var archive = Archive.initFromFile(self.allocator, self.arch.?, file_name, file) catch |err| switch (err) {
281 error.NotArchive => break :try_archive,
282 else => |e| return e,
283 };
284 defer archive.deinit();
285 while (archive.objects.popOrNull()) |object| {
286 const index = self.objects.items.len;
287 try self.objects.append(self.allocator, object);
288 const p_object = &self.objects.items[index];
289 try self.parseObjectFile(p_object);
290 }
291 continue;
292 }
293
294 log.err("unexpected file type: expected object '.o' or archive '.a': {s}", .{file_name});
295 return error.UnexpectedInputFileType;
296 }
297}
298
299fn parseObjectFile(self: *Zld, object: *const Object) !void {
300 const seg_cmd = object.load_commands.items[object.segment_cmd_index.?].Segment;
301 for (seg_cmd.sections.items) |sect| {
302 const sectname = parseName(&sect.sectname);
303
304 const seg_index = self.segments_directory.get(sect.segname) orelse {
305 log.info("segname {s} not found in the output artifact", .{sect.segname});
306 continue;
307 };
308 const seg = &self.load_commands.items[seg_index].Segment;
309 const res = try self.directory.getOrPut(self.allocator, .{
310 .segname = sect.segname,
311 .sectname = sect.sectname,
312 });
313 if (!res.found_existing) {
314 const sect_index = @intCast(u16, seg.sections.items.len);
315 if (mem.eql(u8, sectname, "__thread_vars")) {
316 self.tlv_section_index = sect_index;
317 }
318 try seg.append(self.allocator, .{
319 .sectname = makeStaticString(&sect.sectname),
320 .segname = makeStaticString(&sect.segname),
321 .addr = 0,
322 .size = 0,
323 .offset = 0,
324 .@"align" = sect.@"align",
325 .reloff = 0,
326 .nreloc = 0,
327 .flags = sect.flags,
328 .reserved1 = 0,
329 .reserved2 = 0,
330 .reserved3 = 0,
331 });
332 res.entry.value = .{
333 .seg_index = seg_index,
334 .sect_index = sect_index,
335 };
336 }
337 const dest_sect = &seg.sections.items[res.entry.value.sect_index];
338 dest_sect.size += sect.size;
339 seg.inner.filesize += sect.size;
340 }
341}
342
343fn resolveImports(self: *Zld) !void {
344 var imports = std.StringArrayHashMap(bool).init(self.allocator);
345 defer imports.deinit();
346
347 for (self.objects.items) |object| {
348 for (object.symtab.items) |sym| {
349 if (isLocal(&sym)) continue;
350
351 const name = object.getString(sym.n_strx);
352 const res = try imports.getOrPut(name);
353 if (isExport(&sym)) {
354 res.entry.value = false;
355 continue;
356 }
357 if (res.found_existing and !res.entry.value)
358 continue;
359 res.entry.value = true;
360 }
361 }
362
363 for (imports.items()) |entry| {
364 if (!entry.value) continue;
365
366 const sym_name = entry.key;
367 const n_strx = try self.makeString(sym_name);
368 var new_sym: macho.nlist_64 = .{
369 .n_strx = n_strx,
370 .n_type = macho.N_UNDF | macho.N_EXT,
371 .n_value = 0,
372 .n_desc = macho.REFERENCE_FLAG_UNDEFINED_NON_LAZY | macho.N_SYMBOL_RESOLVER,
373 .n_sect = 0,
374 };
375 var key = try self.allocator.dupe(u8, sym_name);
376 // TODO handle symbol resolution from non-libc dylibs.
377 const dylib_ordinal = 1;
378
379 // TODO need to rework this. Perhaps should create a set of all possible libc
380 // symbols which are expected to be nonlazy?
381 if (mem.eql(u8, sym_name, "___stdoutp") or
382 mem.eql(u8, sym_name, "___stderrp") or
383 mem.eql(u8, sym_name, "___stdinp") or
384 mem.eql(u8, sym_name, "___stack_chk_guard") or
385 mem.eql(u8, sym_name, "_environ"))
386 {
387 log.debug("writing nonlazy symbol '{s}'", .{sym_name});
388 const index = @intCast(u32, self.nonlazy_imports.items().len);
389 try self.nonlazy_imports.putNoClobber(self.allocator, key, .{
390 .symbol = new_sym,
391 .dylib_ordinal = dylib_ordinal,
392 .index = index,
393 });
394 } else if (mem.eql(u8, sym_name, "__tlv_bootstrap")) {
395 log.debug("writing threadlocal symbol '{s}'", .{sym_name});
396 const index = @intCast(u32, self.threadlocal_imports.items().len);
397 try self.threadlocal_imports.putNoClobber(self.allocator, key, .{
398 .symbol = new_sym,
399 .dylib_ordinal = dylib_ordinal,
400 .index = index,
401 });
402 } else {
403 log.debug("writing lazy symbol '{s}'", .{sym_name});
404 const index = @intCast(u32, self.lazy_imports.items().len);
405 try self.lazy_imports.putNoClobber(self.allocator, key, .{
406 .symbol = new_sym,
407 .dylib_ordinal = dylib_ordinal,
408 .index = index,
409 });
410 }
411 }
412
413 const n_strx = try self.makeString("dyld_stub_binder");
414 const name = try self.allocator.dupe(u8, "dyld_stub_binder");
415 log.debug("writing nonlazy symbol 'dyld_stub_binder'", .{});
416 const index = @intCast(u32, self.nonlazy_imports.items().len);
417 try self.nonlazy_imports.putNoClobber(self.allocator, name, .{
418 .symbol = .{
419 .n_strx = n_strx,
420 .n_type = std.macho.N_UNDF | std.macho.N_EXT,
421 .n_sect = 0,
422 .n_desc = std.macho.REFERENCE_FLAG_UNDEFINED_NON_LAZY | std.macho.N_SYMBOL_RESOLVER,
423 .n_value = 0,
424 },
425 .dylib_ordinal = 1,
426 .index = index,
427 });
428}
429
430fn allocateTextSegment(self: *Zld) void {
431 const seg = &self.load_commands.items[self.text_segment_cmd_index.?].Segment;
432 const nexterns = @intCast(u32, self.lazy_imports.items().len);
433
434 // Set stubs and stub_helper sizes
435 const stubs = &seg.sections.items[self.stubs_section_index.?];
436 const stub_helper = &seg.sections.items[self.stub_helper_section_index.?];
437 stubs.size += nexterns * stubs.reserved2;
438
439 const stub_size: u4 = switch (self.arch.?) {
440 .x86_64 => 10,
441 .aarch64 => 3 * @sizeOf(u32),
442 else => unreachable,
443 };
444 stub_helper.size += nexterns * stub_size;
445
446 var sizeofcmds: u64 = 0;
447 for (self.load_commands.items) |lc| {
448 sizeofcmds += lc.cmdsize();
449 }
450
451 self.allocateSegment(self.text_segment_cmd_index.?, 0, sizeofcmds, true);
452}
453
454fn allocateDataSegment(self: *Zld) void {
455 const seg = &self.load_commands.items[self.data_segment_cmd_index.?].Segment;
456 const nonlazy = @intCast(u32, self.nonlazy_imports.items().len);
457 const lazy = @intCast(u32, self.lazy_imports.items().len);
458
459 // Set got size
460 const got = &seg.sections.items[self.got_section_index.?];
461 got.size += nonlazy * @sizeOf(u64);
462
463 // Set la_symbol_ptr and data size
464 const la_symbol_ptr = &seg.sections.items[self.la_symbol_ptr_section_index.?];
465 const data = &seg.sections.items[self.data_section_index.?];
466 la_symbol_ptr.size += lazy * @sizeOf(u64);
467 data.size += @sizeOf(u64); // TODO when do we need more?
468
469 const text_seg = self.load_commands.items[self.text_segment_cmd_index.?].Segment;
470 const offset = text_seg.inner.fileoff + text_seg.inner.filesize;
471 self.allocateSegment(self.data_segment_cmd_index.?, offset, 0, false);
472}
473
474fn allocateLinkeditSegment(self: *Zld) void {
475 const data_seg = self.load_commands.items[self.data_segment_cmd_index.?].Segment;
476 const offset = data_seg.inner.fileoff + data_seg.inner.filesize;
477 self.allocateSegment(self.linkedit_segment_cmd_index.?, offset, 0, false);
478}
479
480fn allocateSegment(self: *Zld, index: u16, offset: u64, start: u64, reverse: bool) void {
481 const base_vmaddr = self.load_commands.items[self.pagezero_segment_cmd_index.?].Segment.inner.vmsize;
482 const seg = &self.load_commands.items[index].Segment;
483
484 // Calculate segment size
485 var total_size = start;
486 for (seg.sections.items) |sect| {
487 total_size += sect.size;
488 }
489 const aligned_size = mem.alignForwardGeneric(u64, total_size, self.page_size.?);
490 seg.inner.vmaddr = base_vmaddr + offset;
491 seg.inner.vmsize = aligned_size;
492 seg.inner.fileoff = offset;
493 seg.inner.filesize = aligned_size;
494
495 // Allocate section offsets
496 if (reverse) {
497 var end_off: u64 = seg.inner.fileoff + seg.inner.filesize;
498 var count: usize = seg.sections.items.len;
499 while (count > 0) : (count -= 1) {
500 const sec = &seg.sections.items[count - 1];
501 end_off -= mem.alignForwardGeneric(u64, sec.size, @sizeOf(u32)); // TODO Should we always align to 4?
502 sec.offset = @intCast(u32, end_off);
503 sec.addr = base_vmaddr + end_off;
504 }
505 } else {
506 var next_off: u64 = seg.inner.fileoff;
507 for (seg.sections.items) |*sect| {
508 sect.offset = @intCast(u32, next_off);
509 sect.addr = base_vmaddr + next_off;
510 next_off += mem.alignForwardGeneric(u64, sect.size, @sizeOf(u32)); // TODO Should we always align to 4?
511 }
512 }
513}
514
515fn writeStubHelperCommon(self: *Zld) !void {
516 const text_segment = &self.load_commands.items[self.text_segment_cmd_index.?].Segment;
517 const stub_helper = &text_segment.sections.items[self.stub_helper_section_index.?];
518 const data_segment = &self.load_commands.items[self.data_segment_cmd_index.?].Segment;
519 const data = &data_segment.sections.items[self.data_section_index.?];
520 const la_symbol_ptr = data_segment.sections.items[self.la_symbol_ptr_section_index.?];
521 const got = &data_segment.sections.items[self.got_section_index.?];
522
523 self.stub_helper_stubs_start_off = blk: {
524 switch (self.arch.?) {
525 .x86_64 => {
526 const code_size = 15;
527 var code: [code_size]u8 = undefined;
528 // lea %r11, [rip + disp]
529 code[0] = 0x4c;
530 code[1] = 0x8d;
531 code[2] = 0x1d;
532 {
533 const target_addr = data.addr + data.size - @sizeOf(u64);
534 const displacement = try math.cast(u32, target_addr - stub_helper.addr - 7);
535 mem.writeIntLittle(u32, code[3..7], displacement);
536 }
537 // push %r11
538 code[7] = 0x41;
539 code[8] = 0x53;
540 // jmp [rip + disp]
541 code[9] = 0xff;
542 code[10] = 0x25;
543 {
544 const dyld_stub_binder = self.nonlazy_imports.get("dyld_stub_binder").?;
545 const addr = (got.addr + dyld_stub_binder.index * @sizeOf(u64));
546 const displacement = try math.cast(u32, addr - stub_helper.addr - code_size);
547 mem.writeIntLittle(u32, code[11..], displacement);
548 }
549 try self.file.?.pwriteAll(&code, stub_helper.offset);
550 break :blk stub_helper.offset + code_size;
551 },
552 .aarch64 => {
553 var code: [4 * @sizeOf(u32)]u8 = undefined;
554 {
555 const target_addr = data.addr + data.size - @sizeOf(u64);
556 const displacement = @bitCast(u21, try math.cast(i21, target_addr - stub_helper.addr));
557 // adr x17, disp
558 mem.writeIntLittle(u32, code[0..4], Arm64.adr(17, displacement).toU32());
559 }
560 // stp x16, x17, [sp, #-16]!
561 code[4] = 0xf0;
562 code[5] = 0x47;
563 code[6] = 0xbf;
564 code[7] = 0xa9;
565 {
566 const dyld_stub_binder = self.nonlazy_imports.get("dyld_stub_binder").?;
567 const addr = (got.addr + dyld_stub_binder.index * @sizeOf(u64));
568 const displacement = try math.divExact(u64, addr - stub_helper.addr - 2 * @sizeOf(u32), 4);
569 const literal = try math.cast(u19, displacement);
570 // ldr x16, label
571 mem.writeIntLittle(u32, code[8..12], Arm64.ldr(16, literal, 1).toU32());
572 }
573 // br x16
574 code[12] = 0x00;
575 code[13] = 0x02;
576 code[14] = 0x1f;
577 code[15] = 0xd6;
578 try self.file.?.pwriteAll(&code, stub_helper.offset);
579 break :blk stub_helper.offset + 4 * @sizeOf(u32);
580 },
581 else => unreachable,
582 }
583 };
584
585 for (self.lazy_imports.items()) |_, i| {
586 const index = @intCast(u32, i);
587 try self.writeLazySymbolPointer(index);
588 try self.writeStub(index);
589 try self.writeStubInStubHelper(index);
590 }
591}
592
593fn writeLazySymbolPointer(self: *Zld, index: u32) !void {
594 const text_segment = self.load_commands.items[self.text_segment_cmd_index.?].Segment;
595 const stub_helper = text_segment.sections.items[self.stub_helper_section_index.?];
596 const data_segment = self.load_commands.items[self.data_segment_cmd_index.?].Segment;
597 const la_symbol_ptr = data_segment.sections.items[self.la_symbol_ptr_section_index.?];
598
599 const stub_size: u4 = switch (self.arch.?) {
600 .x86_64 => 10,
601 .aarch64 => 3 * @sizeOf(u32),
602 else => unreachable,
603 };
604 const stub_off = self.stub_helper_stubs_start_off.? + index * stub_size;
605 const end = stub_helper.addr + stub_off - stub_helper.offset;
606 var buf: [@sizeOf(u64)]u8 = undefined;
607 mem.writeIntLittle(u64, &buf, end);
608 const off = la_symbol_ptr.offset + index * @sizeOf(u64);
609 log.debug("writing lazy symbol pointer entry 0x{x} at 0x{x}", .{ end, off });
610 try self.file.?.pwriteAll(&buf, off);
611}
612
613fn writeStub(self: *Zld, index: u32) !void {
614 const text_segment = self.load_commands.items[self.text_segment_cmd_index.?].Segment;
615 const stubs = text_segment.sections.items[self.stubs_section_index.?];
616 const data_segment = self.load_commands.items[self.data_segment_cmd_index.?].Segment;
617 const la_symbol_ptr = data_segment.sections.items[self.la_symbol_ptr_section_index.?];
618
619 const stub_off = stubs.offset + index * stubs.reserved2;
620 const stub_addr = stubs.addr + index * stubs.reserved2;
621 const la_ptr_addr = la_symbol_ptr.addr + index * @sizeOf(u64);
622 log.debug("writing stub at 0x{x}", .{stub_off});
623 var code = try self.allocator.alloc(u8, stubs.reserved2);
624 defer self.allocator.free(code);
625 switch (self.arch.?) {
626 .x86_64 => {
627 assert(la_ptr_addr >= stub_addr + stubs.reserved2);
628 const displacement = try math.cast(u32, la_ptr_addr - stub_addr - stubs.reserved2);
629 // jmp
630 code[0] = 0xff;
631 code[1] = 0x25;
632 mem.writeIntLittle(u32, code[2..][0..4], displacement);
633 },
634 .aarch64 => {
635 assert(la_ptr_addr >= stub_addr);
636 const displacement = try math.divExact(u64, la_ptr_addr - stub_addr, 4);
637 const literal = try math.cast(u19, displacement);
638 // ldr x16, literal
639 mem.writeIntLittle(u32, code[0..4], Arm64.ldr(16, literal, 1).toU32());
640 // br x16
641 mem.writeIntLittle(u32, code[4..8], Arm64.br(16).toU32());
642 },
643 else => unreachable,
644 }
645 try self.file.?.pwriteAll(code, stub_off);
646}
647
648fn writeStubInStubHelper(self: *Zld, index: u32) !void {
649 const text_segment = self.load_commands.items[self.text_segment_cmd_index.?].Segment;
650 const stub_helper = text_segment.sections.items[self.stub_helper_section_index.?];
651
652 const stub_size: u4 = switch (self.arch.?) {
653 .x86_64 => 10,
654 .aarch64 => 3 * @sizeOf(u32),
655 else => unreachable,
656 };
657 const stub_off = self.stub_helper_stubs_start_off.? + index * stub_size;
658 var code = try self.allocator.alloc(u8, stub_size);
659 defer self.allocator.free(code);
660 switch (self.arch.?) {
661 .x86_64 => {
662 const displacement = try math.cast(
663 i32,
664 @intCast(i64, stub_helper.offset) - @intCast(i64, stub_off) - stub_size,
665 );
666 // pushq
667 code[0] = 0x68;
668 mem.writeIntLittle(u32, code[1..][0..4], 0x0); // Just a placeholder populated in `populateLazyBindOffsetsInStubHelper`.
669 // jmpq
670 code[5] = 0xe9;
671 mem.writeIntLittle(u32, code[6..][0..4], @bitCast(u32, displacement));
672 },
673 .aarch64 => {
674 const displacement = try math.cast(i28, @intCast(i64, stub_helper.offset) - @intCast(i64, stub_off) - 4);
675 const literal = @divExact(stub_size - @sizeOf(u32), 4);
676 // ldr w16, literal
677 mem.writeIntLittle(u32, code[0..4], Arm64.ldr(16, literal, 0).toU32());
678 // b disp
679 mem.writeIntLittle(u32, code[4..8], Arm64.b(displacement).toU32());
680 mem.writeIntLittle(u32, code[8..12], 0x0); // Just a placeholder populated in `populateLazyBindOffsetsInStubHelper`.
681 },
682 else => unreachable,
683 }
684 try self.file.?.pwriteAll(code, stub_off);
685}
686
687fn resolveSymbols(self: *Zld) !void {
688 const Address = struct {
689 addr: u64,
690 size: u64,
691 };
692 var next_address = std.AutoHashMap(DirectoryKey, Address).init(self.allocator);
693 defer next_address.deinit();
694
695 for (self.objects.items) |object| {
696 const seg = object.load_commands.items[object.segment_cmd_index.?].Segment;
697
698 for (seg.sections.items) |sect| {
699 const key: DirectoryKey = .{
700 .segname = sect.segname,
701 .sectname = sect.sectname,
702 };
703 const indices = self.directory.get(key) orelse continue;
704 const out_seg = self.load_commands.items[indices.seg_index].Segment;
705 const out_sect = out_seg.sections.items[indices.sect_index];
706
707 const res = try next_address.getOrPut(key);
708 const next = &res.entry.value;
709 if (res.found_existing) {
710 next.addr += next.size;
711 } else {
712 next.addr = out_sect.addr;
713 }
714 next.size = sect.size;
715 }
716
717 for (object.symtab.items) |sym| {
718 if (isImport(&sym)) continue;
719
720 const sym_name = object.getString(sym.n_strx);
721
722 if (isLocal(&sym) and self.locals.get(sym_name) != null) {
723 log.debug("symbol '{s}' already exists; skipping", .{sym_name});
724 continue;
725 }
726
727 const sect = seg.sections.items[sym.n_sect - 1];
728 const key: DirectoryKey = .{
729 .segname = sect.segname,
730 .sectname = sect.sectname,
731 };
732 const res = self.directory.get(key) orelse continue;
733
734 const n_strx = try self.makeString(sym_name);
735 const n_value = sym.n_value - sect.addr + next_address.get(key).?.addr;
736
737 log.debug("resolving '{s}' as local symbol at 0x{x}", .{ sym_name, n_value });
738
739 var n_sect = res.sect_index + 1;
740 for (self.load_commands.items) |sseg, i| {
741 if (i == res.seg_index) {
742 break;
743 }
744 n_sect += @intCast(u16, sseg.Segment.sections.items.len);
745 }
746
747 var out_name = try self.allocator.dupe(u8, sym_name);
748 try self.locals.putNoClobber(self.allocator, out_name, .{
749 .n_strx = n_strx,
750 .n_value = n_value,
751 .n_type = macho.N_SECT,
752 .n_desc = sym.n_desc,
753 .n_sect = @intCast(u8, n_sect),
754 });
755 }
756 }
757}
758
759fn doRelocs(self: *Zld) !void {
760 const Space = struct {
761 address: u64,
762 offset: u64,
763 size: u64,
764 };
765 var next_space = std.AutoHashMap(DirectoryKey, Space).init(self.allocator);
766 defer next_space.deinit();
767
768 for (self.objects.items) |object| {
769 log.debug("\n\n", .{});
770 log.debug("relocating object {s}", .{object.name});
771
772 const seg = object.load_commands.items[object.segment_cmd_index.?].Segment;
773
774 for (seg.sections.items) |sect| {
775 const key: DirectoryKey = .{
776 .segname = sect.segname,
777 .sectname = sect.sectname,
778 };
779 const indices = self.directory.get(key) orelse continue;
780 const out_seg = self.load_commands.items[indices.seg_index].Segment;
781 const out_sect = out_seg.sections.items[indices.sect_index];
782
783 const res = try next_space.getOrPut(key);
784 const next = &res.entry.value;
785 if (res.found_existing) {
786 next.offset += next.size;
787 next.address += next.size;
788 } else {
789 next.offset = out_sect.offset;
790 next.address = out_sect.addr;
791 }
792 next.size = sect.size;
793 }
794
795 for (seg.sections.items) |sect| {
796 const segname = parseName(&sect.segname);
797 const sectname = parseName(&sect.sectname);
798
799 const key: DirectoryKey = .{
800 .segname = sect.segname,
801 .sectname = sect.sectname,
802 };
803 const next = next_space.get(key) orelse continue;
804
805 var code = try self.allocator.alloc(u8, sect.size);
806 defer self.allocator.free(code);
807 _ = try object.file.preadAll(code, sect.offset);
808
809 // Parse relocs (if any)
810 var raw_relocs = try self.allocator.alloc(u8, @sizeOf(macho.relocation_info) * sect.nreloc);
811 defer self.allocator.free(raw_relocs);
812 _ = try object.file.preadAll(raw_relocs, sect.reloff);
813 const relocs = mem.bytesAsSlice(macho.relocation_info, raw_relocs);
814
815 var addend: ?u64 = null;
816 var sub: ?i64 = null;
817
818 for (relocs) |rel| {
819 const off = @intCast(u32, rel.r_address);
820 const this_addr = next.address + off;
821
822 switch (self.arch.?) {
823 .aarch64 => {
824 const rel_type = @intToEnum(macho.reloc_type_arm64, rel.r_type);
825 log.debug("{s}", .{rel_type});
826 log.debug(" | source address 0x{x}", .{this_addr});
827 log.debug(" | offset 0x{x}", .{off});
828
829 if (rel_type == .ARM64_RELOC_ADDEND) {
830 addend = rel.r_symbolnum;
831 log.debug(" | calculated addend = 0x{x}", .{addend});
832 // TODO followed by either PAGE21 or PAGEOFF12 only.
833 continue;
834 }
835 },
836 .x86_64 => {
837 const rel_type = @intToEnum(macho.reloc_type_x86_64, rel.r_type);
838 log.debug("{s}", .{rel_type});
839 log.debug(" | source address 0x{x}", .{this_addr});
840 log.debug(" | offset 0x{x}", .{off});
841 },
842 else => {},
843 }
844
845 const target_addr = try self.relocTargetAddr(object, rel, next_space);
846 log.debug(" | target address 0x{x}", .{target_addr});
847 if (rel.r_extern == 1) {
848 const target_symname = object.getString(object.symtab.items[rel.r_symbolnum].n_strx);
849 log.debug(" | target symbol '{s}'", .{target_symname});
850 } else {
851 const target_sectname = seg.sections.items[rel.r_symbolnum - 1].sectname;
852 log.debug(" | target section '{s}'", .{parseName(&target_sectname)});
853 }
854
855 switch (self.arch.?) {
856 .x86_64 => {
857 const rel_type = @intToEnum(macho.reloc_type_x86_64, rel.r_type);
858
859 switch (rel_type) {
860 .X86_64_RELOC_BRANCH,
861 .X86_64_RELOC_GOT_LOAD,
862 .X86_64_RELOC_GOT,
863 => {
864 assert(rel.r_length == 2);
865 const inst = code[off..][0..4];
866 const displacement = @bitCast(u32, @intCast(i32, @intCast(i64, target_addr) - @intCast(i64, this_addr) - 4));
867 mem.writeIntLittle(u32, inst, displacement);
868 },
869 .X86_64_RELOC_TLV => {
870 assert(rel.r_length == 2);
871 // We need to rewrite the opcode from movq to leaq.
872 code[off - 2] = 0x8d;
873 // Add displacement.
874 const inst = code[off..][0..4];
875 const displacement = @bitCast(u32, @intCast(i32, @intCast(i64, target_addr) - @intCast(i64, this_addr) - 4));
876 mem.writeIntLittle(u32, inst, displacement);
877 },
878 .X86_64_RELOC_SIGNED,
879 .X86_64_RELOC_SIGNED_1,
880 .X86_64_RELOC_SIGNED_2,
881 .X86_64_RELOC_SIGNED_4,
882 => {
883 assert(rel.r_length == 2);
884 const inst = code[off..][0..4];
885 const offset: i32 = blk: {
886 if (rel.r_extern == 1) {
887 break :blk mem.readIntLittle(i32, inst);
888 } else {
889 // TODO it might be required here to parse the offset from the instruction placeholder,
890 // compare the displacement with the original displacement in the .o file, and adjust
891 // the displacement in the resultant binary file.
892 const correction: i4 = switch (rel_type) {
893 .X86_64_RELOC_SIGNED => 0,
894 .X86_64_RELOC_SIGNED_1 => 1,
895 .X86_64_RELOC_SIGNED_2 => 2,
896 .X86_64_RELOC_SIGNED_4 => 4,
897 else => unreachable,
898 };
899 break :blk correction;
900 }
901 };
902 log.debug(" | calculated addend 0x{x}", .{offset});
903 const result = @intCast(i64, target_addr) - @intCast(i64, this_addr) - 4 + offset;
904 const displacement = @bitCast(u32, @intCast(i32, result));
905 mem.writeIntLittle(u32, inst, displacement);
906 },
907 .X86_64_RELOC_SUBTRACTOR => {
908 sub = @intCast(i64, target_addr);
909 },
910 .X86_64_RELOC_UNSIGNED => {
911 switch (rel.r_length) {
912 3 => {
913 const inst = code[off..][0..8];
914 const offset = mem.readIntLittle(i64, inst);
915 log.debug(" | calculated addend 0x{x}", .{offset});
916 const result = if (sub) |s|
917 @intCast(i64, target_addr) - s + offset
918 else
919 @intCast(i64, target_addr) + offset;
920 mem.writeIntLittle(u64, inst, @bitCast(u64, result));
921 sub = null;
922
923 // TODO should handle this better.
924 if (mem.eql(u8, segname, "__DATA")) outer: {
925 if (!mem.eql(u8, sectname, "__data") and
926 !mem.eql(u8, sectname, "__const") and
927 !mem.eql(u8, sectname, "__mod_init_func")) break :outer;
928 const this_seg = self.load_commands.items[self.data_segment_cmd_index.?].Segment;
929 const this_offset = next.address + off - this_seg.inner.vmaddr;
930 try self.local_rebases.append(self.allocator, .{
931 .offset = this_offset,
932 .segment_id = @intCast(u16, self.data_segment_cmd_index.?),
933 });
934 }
935 },
936 2 => {
937 const inst = code[off..][0..4];
938 const offset = mem.readIntLittle(i32, inst);
939 log.debug(" | calculated addend 0x{x}", .{offset});
940 const result = if (sub) |s|
941 @intCast(i64, target_addr) - s + offset
942 else
943 @intCast(i64, target_addr) + offset;
944 mem.writeIntLittle(u32, inst, @truncate(u32, @bitCast(u64, result)));
945 sub = null;
946 },
947 else => |len| {
948 log.err("unexpected relocation length 0x{x}", .{len});
949 return error.UnexpectedRelocationLength;
950 },
951 }
952 },
953 }
954 },
955 .aarch64 => {
956 const rel_type = @intToEnum(macho.reloc_type_arm64, rel.r_type);
957
958 switch (rel_type) {
959 .ARM64_RELOC_BRANCH26 => {
960 assert(rel.r_length == 2);
961 const inst = code[off..][0..4];
962 const displacement = @intCast(i28, @intCast(i64, target_addr) - @intCast(i64, this_addr));
963 var parsed = mem.bytesAsValue(meta.TagPayload(Arm64, Arm64.Branch), inst);
964 parsed.disp = @truncate(u26, @bitCast(u28, displacement) >> 2);
965 },
966 .ARM64_RELOC_PAGE21,
967 .ARM64_RELOC_GOT_LOAD_PAGE21,
968 .ARM64_RELOC_TLVP_LOAD_PAGE21,
969 => {
970 assert(rel.r_length == 2);
971 const inst = code[off..][0..4];
972 const ta = if (addend) |a| target_addr + a else target_addr;
973 const this_page = @intCast(i32, this_addr >> 12);
974 const target_page = @intCast(i32, ta >> 12);
975 const pages = @bitCast(u21, @intCast(i21, target_page - this_page));
976 log.debug(" | moving by {} pages", .{pages});
977 var parsed = mem.bytesAsValue(meta.TagPayload(Arm64, Arm64.Address), inst);
978 parsed.immhi = @truncate(u19, pages >> 2);
979 parsed.immlo = @truncate(u2, pages);
980 addend = null;
981 },
982 .ARM64_RELOC_PAGEOFF12,
983 .ARM64_RELOC_GOT_LOAD_PAGEOFF12,
984 => {
985 const inst = code[off..][0..4];
986 if (Arm64.isArithmetic(inst)) {
987 log.debug(" | detected ADD opcode", .{});
988 // add
989 var parsed = mem.bytesAsValue(meta.TagPayload(Arm64, Arm64.Add), inst);
990 const ta = if (addend) |a| target_addr + a else target_addr;
991 const narrowed = @truncate(u12, ta);
992 parsed.offset = narrowed;
993 } else {
994 log.debug(" | detected LDR/STR opcode", .{});
995 // ldr/str
996 var parsed = mem.bytesAsValue(meta.TagPayload(Arm64, Arm64.LoadRegister), inst);
997 const ta = if (addend) |a| target_addr + a else target_addr;
998 const narrowed = @truncate(u12, ta);
999 const offset = if (parsed.size == 1) @divExact(narrowed, 8) else @divExact(narrowed, 4);
1000 parsed.offset = @truncate(u12, offset);
1001 }
1002 addend = null;
1003 },
1004 .ARM64_RELOC_TLVP_LOAD_PAGEOFF12 => {
1005 // TODO why is this necessary?
1006 const RegInfo = struct {
1007 rt: u5,
1008 rn: u5,
1009 size: u1,
1010 };
1011 const inst = code[off..][0..4];
1012 const parsed: RegInfo = blk: {
1013 if (Arm64.isArithmetic(inst)) {
1014 const curr = mem.bytesAsValue(meta.TagPayload(Arm64, Arm64.Add), inst);
1015 break :blk .{ .rt = curr.rt, .rn = curr.rn, .size = curr.size };
1016 } else {
1017 const curr = mem.bytesAsValue(meta.TagPayload(Arm64, Arm64.LoadRegister), inst);
1018 break :blk .{ .rt = curr.rt, .rn = curr.rn, .size = curr.size };
1019 }
1020 };
1021 const ta = if (addend) |a| target_addr + a else target_addr;
1022 const narrowed = @truncate(u12, ta);
1023 log.debug(" | rewriting TLV access to ADD opcode", .{});
1024 // For TLV, we always generate an add instruction.
1025 mem.writeIntLittle(u32, inst, Arm64.add(parsed.rt, parsed.rn, narrowed, parsed.size).toU32());
1026 },
1027 .ARM64_RELOC_SUBTRACTOR => {
1028 sub = @intCast(i64, target_addr);
1029 },
1030 .ARM64_RELOC_UNSIGNED => {
1031 switch (rel.r_length) {
1032 3 => {
1033 const inst = code[off..][0..8];
1034 const offset = mem.readIntLittle(i64, inst);
1035 log.debug(" | calculated addend 0x{x}", .{offset});
1036 const result = if (sub) |s|
1037 @intCast(i64, target_addr) - s + offset
1038 else
1039 @intCast(i64, target_addr) + offset;
1040 mem.writeIntLittle(u64, inst, @bitCast(u64, result));
1041 sub = null;
1042
1043 // TODO should handle this better.
1044 if (mem.eql(u8, segname, "__DATA")) outer: {
1045 if (!mem.eql(u8, sectname, "__data") and
1046 !mem.eql(u8, sectname, "__const") and
1047 !mem.eql(u8, sectname, "__mod_init_func")) break :outer;
1048 const this_seg = self.load_commands.items[self.data_segment_cmd_index.?].Segment;
1049 const this_offset = next.address + off - this_seg.inner.vmaddr;
1050 try self.local_rebases.append(self.allocator, .{
1051 .offset = this_offset,
1052 .segment_id = @intCast(u16, self.data_segment_cmd_index.?),
1053 });
1054 }
1055 },
1056 2 => {
1057 const inst = code[off..][0..4];
1058 const offset = mem.readIntLittle(i32, inst);
1059 log.debug(" | calculated addend 0x{x}", .{offset});
1060 const result = if (sub) |s|
1061 @intCast(i64, target_addr) - s + offset
1062 else
1063 @intCast(i64, target_addr) + offset;
1064 mem.writeIntLittle(u32, inst, @truncate(u32, @bitCast(u64, result)));
1065 sub = null;
1066 },
1067 else => |len| {
1068 log.err("unexpected relocation length 0x{x}", .{len});
1069 return error.UnexpectedRelocationLength;
1070 },
1071 }
1072 },
1073 .ARM64_RELOC_POINTER_TO_GOT => return error.TODOArm64RelocPointerToGot,
1074 else => unreachable,
1075 }
1076 },
1077 else => unreachable,
1078 }
1079 }
1080
1081 log.debug("writing contents of '{s},{s}' section from '{s}' from 0x{x} to 0x{x}", .{
1082 segname,
1083 sectname,
1084 object.name,
1085 next.offset,
1086 next.offset + next.size,
1087 });
1088
1089 if (mem.eql(u8, sectname, "__bss") or
1090 mem.eql(u8, sectname, "__thread_bss") or
1091 mem.eql(u8, sectname, "__thread_vars"))
1092 {
1093 // Zero-out the space
1094 var zeroes = try self.allocator.alloc(u8, next.size);
1095 defer self.allocator.free(zeroes);
1096 mem.set(u8, zeroes, 0);
1097 try self.file.?.pwriteAll(zeroes, next.offset);
1098 } else {
1099 try self.file.?.pwriteAll(code, next.offset);
1100 }
1101 }
1102 }
1103}
1104
1105fn relocTargetAddr(self: *Zld, object: Object, rel: macho.relocation_info, next_space: anytype) !u64 {
1106 const seg = object.load_commands.items[object.segment_cmd_index.?].Segment;
1107 const target_addr = blk: {
1108 if (rel.r_extern == 1) {
1109 const sym = object.symtab.items[rel.r_symbolnum];
1110 if (isLocal(&sym) or isExport(&sym)) {
1111 // Relocate using section offsets only.
1112 const source_sect = seg.sections.items[sym.n_sect - 1];
1113 const target_space = next_space.get(.{
1114 .segname = source_sect.segname,
1115 .sectname = source_sect.sectname,
1116 }).?;
1117 break :blk target_space.address + sym.n_value - source_sect.addr;
1118 } else if (isImport(&sym)) {
1119 // Relocate to either the artifact's local symbol, or an import from
1120 // shared library.
1121 const sym_name = object.getString(sym.n_strx);
1122 if (self.locals.get(sym_name)) |loc| {
1123 break :blk loc.n_value;
1124 } else if (self.lazy_imports.get(sym_name)) |ext| {
1125 const segment = self.load_commands.items[self.text_segment_cmd_index.?].Segment;
1126 const stubs = segment.sections.items[self.stubs_section_index.?];
1127 break :blk stubs.addr + ext.index * stubs.reserved2;
1128 } else if (self.nonlazy_imports.get(sym_name)) |ext| {
1129 const segment = self.load_commands.items[self.data_segment_cmd_index.?].Segment;
1130 const got = segment.sections.items[self.got_section_index.?];
1131 break :blk got.addr + ext.index * @sizeOf(u64);
1132 } else if (self.threadlocal_imports.get(sym_name)) |ext| {
1133 const segment = self.load_commands.items[self.data_segment_cmd_index.?].Segment;
1134 const tlv = segment.sections.items[self.tlv_section_index.?];
1135 break :blk tlv.addr + ext.index * @sizeOf(u64);
1136 } else {
1137 log.err("failed to resolve symbol '{s}' as a relocation target", .{sym_name});
1138 return error.FailedToResolveRelocationTarget;
1139 }
1140 } else {
1141 log.err("unexpected symbol {}, {s}", .{ sym, object.getString(sym.n_strx) });
1142 return error.UnexpectedSymbolWhenRelocating;
1143 }
1144 } else {
1145 // TODO I think we need to reparse the relocation_info as scattered_relocation_info
1146 // here to get the actual section plus offset into that section of the relocated
1147 // symbol. Unless the fine-grained location is encoded within the cell in the code
1148 // buffer?
1149 const source_sectname = seg.sections.items[rel.r_symbolnum - 1];
1150 const target_space = next_space.get(.{
1151 .segname = source_sectname.segname,
1152 .sectname = source_sectname.sectname,
1153 }).?;
1154 break :blk target_space.address;
1155 }
1156 };
1157 return target_addr;
1158}
1159
1160fn populateMetadata(self: *Zld) !void {
1161 if (self.pagezero_segment_cmd_index == null) {
1162 self.pagezero_segment_cmd_index = @intCast(u16, self.load_commands.items.len);
1163 try self.load_commands.append(self.allocator, .{
1164 .Segment = SegmentCommand.empty(.{
1165 .cmd = macho.LC_SEGMENT_64,
1166 .cmdsize = @sizeOf(macho.segment_command_64),
1167 .segname = makeStaticString("__PAGEZERO"),
1168 .vmaddr = 0,
1169 .vmsize = 0x100000000, // size always set to 4GB
1170 .fileoff = 0,
1171 .filesize = 0,
1172 .maxprot = 0,
1173 .initprot = 0,
1174 .nsects = 0,
1175 .flags = 0,
1176 }),
1177 });
1178 try self.addSegmentToDir(0);
1179 }
1180
1181 if (self.text_segment_cmd_index == null) {
1182 self.text_segment_cmd_index = @intCast(u16, self.load_commands.items.len);
1183 try self.load_commands.append(self.allocator, .{
1184 .Segment = SegmentCommand.empty(.{
1185 .cmd = macho.LC_SEGMENT_64,
1186 .cmdsize = @sizeOf(macho.segment_command_64),
1187 .segname = makeStaticString("__TEXT"),
1188 .vmaddr = 0x100000000, // always starts at 4GB
1189 .vmsize = 0,
1190 .fileoff = 0,
1191 .filesize = 0,
1192 .maxprot = macho.VM_PROT_READ | macho.VM_PROT_EXECUTE,
1193 .initprot = macho.VM_PROT_READ | macho.VM_PROT_EXECUTE,
1194 .nsects = 0,
1195 .flags = 0,
1196 }),
1197 });
1198 try self.addSegmentToDir(self.text_segment_cmd_index.?);
1199 }
1200
1201 if (self.text_section_index == null) {
1202 const text_seg = &self.load_commands.items[self.text_segment_cmd_index.?].Segment;
1203 self.text_section_index = @intCast(u16, text_seg.sections.items.len);
1204 const alignment: u2 = switch (self.arch.?) {
1205 .x86_64 => 0,
1206 .aarch64 => 2,
1207 else => unreachable, // unhandled architecture type
1208 };
1209 try text_seg.append(self.allocator, .{
1210 .sectname = makeStaticString("__text"),
1211 .segname = makeStaticString("__TEXT"),
1212 .addr = 0,
1213 .size = 0,
1214 .offset = 0,
1215 .@"align" = alignment,
1216 .reloff = 0,
1217 .nreloc = 0,
1218 .flags = macho.S_REGULAR | macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS,
1219 .reserved1 = 0,
1220 .reserved2 = 0,
1221 .reserved3 = 0,
1222 });
1223 try self.addSectionToDir(.{
1224 .seg_index = self.text_segment_cmd_index.?,
1225 .sect_index = self.text_section_index.?,
1226 });
1227 }
1228
1229 if (self.stubs_section_index == null) {
1230 const text_seg = &self.load_commands.items[self.text_segment_cmd_index.?].Segment;
1231 self.stubs_section_index = @intCast(u16, text_seg.sections.items.len);
1232 const alignment: u2 = switch (self.arch.?) {
1233 .x86_64 => 0,
1234 .aarch64 => 2,
1235 else => unreachable, // unhandled architecture type
1236 };
1237 const stub_size: u4 = switch (self.arch.?) {
1238 .x86_64 => 6,
1239 .aarch64 => 2 * @sizeOf(u32),
1240 else => unreachable, // unhandled architecture type
1241 };
1242 try text_seg.append(self.allocator, .{
1243 .sectname = makeStaticString("__stubs"),
1244 .segname = makeStaticString("__TEXT"),
1245 .addr = 0,
1246 .size = 0,
1247 .offset = 0,
1248 .@"align" = alignment,
1249 .reloff = 0,
1250 .nreloc = 0,
1251 .flags = macho.S_SYMBOL_STUBS | macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS,
1252 .reserved1 = 0,
1253 .reserved2 = stub_size,
1254 .reserved3 = 0,
1255 });
1256 try self.addSectionToDir(.{
1257 .seg_index = self.text_segment_cmd_index.?,
1258 .sect_index = self.stubs_section_index.?,
1259 });
1260 }
1261
1262 if (self.stub_helper_section_index == null) {
1263 const text_seg = &self.load_commands.items[self.text_segment_cmd_index.?].Segment;
1264 self.stub_helper_section_index = @intCast(u16, text_seg.sections.items.len);
1265 const alignment: u2 = switch (self.arch.?) {
1266 .x86_64 => 0,
1267 .aarch64 => 2,
1268 else => unreachable, // unhandled architecture type
1269 };
1270 const stub_helper_size: u5 = switch (self.arch.?) {
1271 .x86_64 => 15,
1272 .aarch64 => 6 * @sizeOf(u32),
1273 else => unreachable,
1274 };
1275 try text_seg.append(self.allocator, .{
1276 .sectname = makeStaticString("__stub_helper"),
1277 .segname = makeStaticString("__TEXT"),
1278 .addr = 0,
1279 .size = stub_helper_size,
1280 .offset = 0,
1281 .@"align" = alignment,
1282 .reloff = 0,
1283 .nreloc = 0,
1284 .flags = macho.S_REGULAR | macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS,
1285 .reserved1 = 0,
1286 .reserved2 = 0,
1287 .reserved3 = 0,
1288 });
1289 try self.addSectionToDir(.{
1290 .seg_index = self.text_segment_cmd_index.?,
1291 .sect_index = self.stub_helper_section_index.?,
1292 });
1293 }
1294
1295 if (self.data_segment_cmd_index == null) {
1296 self.data_segment_cmd_index = @intCast(u16, self.load_commands.items.len);
1297 try self.load_commands.append(self.allocator, .{
1298 .Segment = SegmentCommand.empty(.{
1299 .cmd = macho.LC_SEGMENT_64,
1300 .cmdsize = @sizeOf(macho.segment_command_64),
1301 .segname = makeStaticString("__DATA"),
1302 .vmaddr = 0,
1303 .vmsize = 0,
1304 .fileoff = 0,
1305 .filesize = 0,
1306 .maxprot = macho.VM_PROT_READ | macho.VM_PROT_WRITE,
1307 .initprot = macho.VM_PROT_READ | macho.VM_PROT_WRITE,
1308 .nsects = 0,
1309 .flags = 0,
1310 }),
1311 });
1312 try self.addSegmentToDir(self.data_segment_cmd_index.?);
1313 }
1314
1315 if (self.got_section_index == null) {
1316 const data_seg = &self.load_commands.items[self.data_segment_cmd_index.?].Segment;
1317 self.got_section_index = @intCast(u16, data_seg.sections.items.len);
1318 try data_seg.append(self.allocator, .{
1319 .sectname = makeStaticString("__got"),
1320 .segname = makeStaticString("__DATA"),
1321 .addr = 0,
1322 .size = 0,
1323 .offset = 0,
1324 .@"align" = 3, // 2^3 = @sizeOf(u64)
1325 .reloff = 0,
1326 .nreloc = 0,
1327 .flags = macho.S_NON_LAZY_SYMBOL_POINTERS,
1328 .reserved1 = 0,
1329 .reserved2 = 0,
1330 .reserved3 = 0,
1331 });
1332 try self.addSectionToDir(.{
1333 .seg_index = self.data_segment_cmd_index.?,
1334 .sect_index = self.got_section_index.?,
1335 });
1336 }
1337
1338 if (self.la_symbol_ptr_section_index == null) {
1339 const data_seg = &self.load_commands.items[self.data_segment_cmd_index.?].Segment;
1340 self.la_symbol_ptr_section_index = @intCast(u16, data_seg.sections.items.len);
1341 try data_seg.append(self.allocator, .{
1342 .sectname = makeStaticString("__la_symbol_ptr"),
1343 .segname = makeStaticString("__DATA"),
1344 .addr = 0,
1345 .size = 0,
1346 .offset = 0,
1347 .@"align" = 3, // 2^3 = @sizeOf(u64)
1348 .reloff = 0,
1349 .nreloc = 0,
1350 .flags = macho.S_LAZY_SYMBOL_POINTERS,
1351 .reserved1 = 0,
1352 .reserved2 = 0,
1353 .reserved3 = 0,
1354 });
1355 try self.addSectionToDir(.{
1356 .seg_index = self.data_segment_cmd_index.?,
1357 .sect_index = self.la_symbol_ptr_section_index.?,
1358 });
1359 }
1360
1361 if (self.data_section_index == null) {
1362 const data_seg = &self.load_commands.items[self.data_segment_cmd_index.?].Segment;
1363 self.data_section_index = @intCast(u16, data_seg.sections.items.len);
1364 try data_seg.append(self.allocator, .{
1365 .sectname = makeStaticString("__data"),
1366 .segname = makeStaticString("__DATA"),
1367 .addr = 0,
1368 .size = 0,
1369 .offset = 0,
1370 .@"align" = 3, // 2^3 = @sizeOf(u64)
1371 .reloff = 0,
1372 .nreloc = 0,
1373 .flags = macho.S_REGULAR,
1374 .reserved1 = 0,
1375 .reserved2 = 0,
1376 .reserved3 = 0,
1377 });
1378 try self.addSectionToDir(.{
1379 .seg_index = self.data_segment_cmd_index.?,
1380 .sect_index = self.data_section_index.?,
1381 });
1382 }
1383
1384 if (self.linkedit_segment_cmd_index == null) {
1385 self.linkedit_segment_cmd_index = @intCast(u16, self.load_commands.items.len);
1386 try self.load_commands.append(self.allocator, .{
1387 .Segment = SegmentCommand.empty(.{
1388 .cmd = macho.LC_SEGMENT_64,
1389 .cmdsize = @sizeOf(macho.segment_command_64),
1390 .segname = makeStaticString("__LINKEDIT"),
1391 .vmaddr = 0,
1392 .vmsize = 0,
1393 .fileoff = 0,
1394 .filesize = 0,
1395 .maxprot = macho.VM_PROT_READ,
1396 .initprot = macho.VM_PROT_READ,
1397 .nsects = 0,
1398 .flags = 0,
1399 }),
1400 });
1401 try self.addSegmentToDir(self.linkedit_segment_cmd_index.?);
1402 }
1403
1404 if (self.dyld_info_cmd_index == null) {
1405 self.dyld_info_cmd_index = @intCast(u16, self.load_commands.items.len);
1406 try self.load_commands.append(self.allocator, .{
1407 .DyldInfoOnly = .{
1408 .cmd = macho.LC_DYLD_INFO_ONLY,
1409 .cmdsize = @sizeOf(macho.dyld_info_command),
1410 .rebase_off = 0,
1411 .rebase_size = 0,
1412 .bind_off = 0,
1413 .bind_size = 0,
1414 .weak_bind_off = 0,
1415 .weak_bind_size = 0,
1416 .lazy_bind_off = 0,
1417 .lazy_bind_size = 0,
1418 .export_off = 0,
1419 .export_size = 0,
1420 },
1421 });
1422 }
1423
1424 if (self.symtab_cmd_index == null) {
1425 self.symtab_cmd_index = @intCast(u16, self.load_commands.items.len);
1426 try self.load_commands.append(self.allocator, .{
1427 .Symtab = .{
1428 .cmd = macho.LC_SYMTAB,
1429 .cmdsize = @sizeOf(macho.symtab_command),
1430 .symoff = 0,
1431 .nsyms = 0,
1432 .stroff = 0,
1433 .strsize = 0,
1434 },
1435 });
1436 try self.strtab.append(self.allocator, 0);
1437 }
1438
1439 if (self.dysymtab_cmd_index == null) {
1440 self.dysymtab_cmd_index = @intCast(u16, self.load_commands.items.len);
1441 try self.load_commands.append(self.allocator, .{
1442 .Dysymtab = .{
1443 .cmd = macho.LC_DYSYMTAB,
1444 .cmdsize = @sizeOf(macho.dysymtab_command),
1445 .ilocalsym = 0,
1446 .nlocalsym = 0,
1447 .iextdefsym = 0,
1448 .nextdefsym = 0,
1449 .iundefsym = 0,
1450 .nundefsym = 0,
1451 .tocoff = 0,
1452 .ntoc = 0,
1453 .modtaboff = 0,
1454 .nmodtab = 0,
1455 .extrefsymoff = 0,
1456 .nextrefsyms = 0,
1457 .indirectsymoff = 0,
1458 .nindirectsyms = 0,
1459 .extreloff = 0,
1460 .nextrel = 0,
1461 .locreloff = 0,
1462 .nlocrel = 0,
1463 },
1464 });
1465 }
1466
1467 if (self.dylinker_cmd_index == null) {
1468 self.dylinker_cmd_index = @intCast(u16, self.load_commands.items.len);
1469 const cmdsize = @intCast(u32, mem.alignForwardGeneric(
1470 u64,
1471 @sizeOf(macho.dylinker_command) + mem.lenZ(DEFAULT_DYLD_PATH),
1472 @sizeOf(u64),
1473 ));
1474 var dylinker_cmd = emptyGenericCommandWithData(macho.dylinker_command{
1475 .cmd = macho.LC_LOAD_DYLINKER,
1476 .cmdsize = cmdsize,
1477 .name = @sizeOf(macho.dylinker_command),
1478 });
1479 dylinker_cmd.data = try self.allocator.alloc(u8, cmdsize - dylinker_cmd.inner.name);
1480 mem.set(u8, dylinker_cmd.data, 0);
1481 mem.copy(u8, dylinker_cmd.data, mem.spanZ(DEFAULT_DYLD_PATH));
1482 try self.load_commands.append(self.allocator, .{ .Dylinker = dylinker_cmd });
1483 }
1484
1485 if (self.libsystem_cmd_index == null) {
1486 self.libsystem_cmd_index = @intCast(u16, self.load_commands.items.len);
1487 const cmdsize = @intCast(u32, mem.alignForwardGeneric(
1488 u64,
1489 @sizeOf(macho.dylib_command) + mem.lenZ(LIB_SYSTEM_PATH),
1490 @sizeOf(u64),
1491 ));
1492 // TODO Find a way to work out runtime version from the OS version triple stored in std.Target.
1493 // In the meantime, we're gonna hardcode to the minimum compatibility version of 0.0.0.
1494 const min_version = 0x0;
1495 var dylib_cmd = emptyGenericCommandWithData(macho.dylib_command{
1496 .cmd = macho.LC_LOAD_DYLIB,
1497 .cmdsize = cmdsize,
1498 .dylib = .{
1499 .name = @sizeOf(macho.dylib_command),
1500 .timestamp = 2, // not sure why not simply 0; this is reverse engineered from Mach-O files
1501 .current_version = min_version,
1502 .compatibility_version = min_version,
1503 },
1504 });
1505 dylib_cmd.data = try self.allocator.alloc(u8, cmdsize - dylib_cmd.inner.dylib.name);
1506 mem.set(u8, dylib_cmd.data, 0);
1507 mem.copy(u8, dylib_cmd.data, mem.spanZ(LIB_SYSTEM_PATH));
1508 try self.load_commands.append(self.allocator, .{ .Dylib = dylib_cmd });
1509 }
1510
1511 if (self.main_cmd_index == null) {
1512 self.main_cmd_index = @intCast(u16, self.load_commands.items.len);
1513 try self.load_commands.append(self.allocator, .{
1514 .Main = .{
1515 .cmd = macho.LC_MAIN,
1516 .cmdsize = @sizeOf(macho.entry_point_command),
1517 .entryoff = 0x0,
1518 .stacksize = 0,
1519 },
1520 });
1521 }
1522
1523 if (self.source_version_cmd_index == null) {
1524 self.source_version_cmd_index = @intCast(u16, self.load_commands.items.len);
1525 try self.load_commands.append(self.allocator, .{
1526 .SourceVersion = .{
1527 .cmd = macho.LC_SOURCE_VERSION,
1528 .cmdsize = @sizeOf(macho.source_version_command),
1529 .version = 0x0,
1530 },
1531 });
1532 }
1533
1534 if (self.uuid_cmd_index == null) {
1535 self.uuid_cmd_index = @intCast(u16, self.load_commands.items.len);
1536 var uuid_cmd: macho.uuid_command = .{
1537 .cmd = macho.LC_UUID,
1538 .cmdsize = @sizeOf(macho.uuid_command),
1539 .uuid = undefined,
1540 };
1541 std.crypto.random.bytes(&uuid_cmd.uuid);
1542 try self.load_commands.append(self.allocator, .{ .Uuid = uuid_cmd });
1543 }
1544
1545 if (self.code_signature_cmd_index == null and self.arch.? == .aarch64) {
1546 self.code_signature_cmd_index = @intCast(u16, self.load_commands.items.len);
1547 try self.load_commands.append(self.allocator, .{
1548 .LinkeditData = .{
1549 .cmd = macho.LC_CODE_SIGNATURE,
1550 .cmdsize = @sizeOf(macho.linkedit_data_command),
1551 .dataoff = 0,
1552 .datasize = 0,
1553 },
1554 });
1555 }
1556}
1557
1558fn flush(self: *Zld) !void {
1559 {
1560 const seg = &self.load_commands.items[self.data_segment_cmd_index.?].Segment;
1561 for (seg.sections.items) |*sect| {
1562 const sectname = parseName(&sect.sectname);
1563 if (mem.eql(u8, sectname, "__bss") or mem.eql(u8, sectname, "__thread_bss")) {
1564 sect.offset = 0;
1565 }
1566 }
1567 }
1568 {
1569 const seg = &self.load_commands.items[self.text_segment_cmd_index.?].Segment;
1570 for (seg.sections.items) |*sect| {
1571 if (mem.eql(u8, parseName(&sect.sectname), "__eh_frame")) {
1572 sect.flags = 0;
1573 }
1574 }
1575 }
1576 try self.setEntryPoint();
1577 try self.writeRebaseInfoTable();
1578 try self.writeBindInfoTable();
1579 try self.writeLazyBindInfoTable();
1580 try self.writeExportInfo();
1581
1582 {
1583 const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment;
1584 const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab;
1585 symtab.symoff = @intCast(u32, seg.inner.fileoff + seg.inner.filesize);
1586 }
1587
1588 try self.writeDebugInfo();
1589 try self.writeSymbolTable();
1590 try self.writeDynamicSymbolTable();
1591 try self.writeStringTable();
1592
1593 {
1594 // Seal __LINKEDIT size
1595 const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment;
1596 seg.inner.vmsize = mem.alignForwardGeneric(u64, seg.inner.filesize, self.page_size.?);
1597 }
1598
1599 if (self.arch.? == .aarch64) {
1600 try self.writeCodeSignaturePadding();
1601 }
1602
1603 try self.writeLoadCommands();
1604 try self.writeHeader();
1605
1606 if (self.arch.? == .aarch64) {
1607 try self.writeCodeSignature();
1608 }
1609
1610 if (comptime std.Target.current.isDarwin() and std.Target.current.cpu.arch == .aarch64) {
1611 try fs.cwd().copyFile(self.out_path.?, fs.cwd(), self.out_path.?, .{});
1612 }
1613}
1614
1615fn setEntryPoint(self: *Zld) !void {
1616 // TODO we should respect the -entry flag passed in by the user to set a custom
1617 // entrypoint. For now, assume default of `_main`.
1618 const seg = self.load_commands.items[self.text_segment_cmd_index.?].Segment;
1619 const text = seg.sections.items[self.text_section_index.?];
1620 const entry_sym = self.locals.get("_main") orelse return error.MissingMainEntrypoint;
1621
1622 const name = try self.allocator.dupe(u8, "_main");
1623 try self.exports.putNoClobber(self.allocator, name, .{
1624 .n_strx = entry_sym.n_strx,
1625 .n_value = entry_sym.n_value,
1626 .n_type = macho.N_SECT | macho.N_EXT,
1627 .n_desc = entry_sym.n_desc,
1628 .n_sect = entry_sym.n_sect,
1629 });
1630
1631 const ec = &self.load_commands.items[self.main_cmd_index.?].Main;
1632 ec.entryoff = @intCast(u32, entry_sym.n_value - seg.inner.vmaddr);
1633}
1634
1635fn writeRebaseInfoTable(self: *Zld) !void {
1636 const data_seg = self.load_commands.items[self.data_segment_cmd_index.?].Segment;
1637
1638 var pointers = std.ArrayList(Pointer).init(self.allocator);
1639 defer pointers.deinit();
1640 try pointers.ensureCapacity(self.lazy_imports.items().len);
1641
1642 if (self.la_symbol_ptr_section_index) |idx| {
1643 const sect = data_seg.sections.items[idx];
1644 const base_offset = sect.addr - data_seg.inner.vmaddr;
1645 const segment_id = @intCast(u16, self.data_segment_cmd_index.?);
1646 for (self.lazy_imports.items()) |entry| {
1647 pointers.appendAssumeCapacity(.{
1648 .offset = base_offset + entry.value.index * @sizeOf(u64),
1649 .segment_id = segment_id,
1650 });
1651 }
1652 }
1653
1654 try pointers.ensureCapacity(pointers.items.len + self.local_rebases.items.len);
1655
1656 const nlocals = self.local_rebases.items.len;
1657 var i = nlocals;
1658 while (i > 0) : (i -= 1) {
1659 pointers.appendAssumeCapacity(self.local_rebases.items[i - 1]);
1660 }
1661
1662 const size = try rebaseInfoSize(pointers.items);
1663 var buffer = try self.allocator.alloc(u8, @intCast(usize, size));
1664 defer self.allocator.free(buffer);
1665
1666 var stream = std.io.fixedBufferStream(buffer);
1667 try writeRebaseInfo(pointers.items, stream.writer());
1668
1669 const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment;
1670 const dyld_info = &self.load_commands.items[self.dyld_info_cmd_index.?].DyldInfoOnly;
1671 dyld_info.rebase_off = @intCast(u32, seg.inner.fileoff);
1672 dyld_info.rebase_size = @intCast(u32, mem.alignForwardGeneric(u64, buffer.len, @sizeOf(u64)));
1673 seg.inner.filesize += dyld_info.rebase_size;
1674
1675 log.debug("writing rebase info from 0x{x} to 0x{x}", .{ dyld_info.rebase_off, dyld_info.rebase_off + dyld_info.rebase_size });
1676
1677 try self.file.?.pwriteAll(buffer, dyld_info.rebase_off);
1678}
1679
1680fn writeBindInfoTable(self: *Zld) !void {
1681 const data_seg = self.load_commands.items[self.data_segment_cmd_index.?].Segment;
1682
1683 var pointers = std.ArrayList(Pointer).init(self.allocator);
1684 defer pointers.deinit();
1685 try pointers.ensureCapacity(self.nonlazy_imports.items().len + self.threadlocal_imports.items().len);
1686
1687 if (self.got_section_index) |idx| {
1688 const sect = data_seg.sections.items[idx];
1689 const base_offset = sect.addr - data_seg.inner.vmaddr;
1690 const segment_id = @intCast(u16, self.data_segment_cmd_index.?);
1691 for (self.nonlazy_imports.items()) |entry| {
1692 pointers.appendAssumeCapacity(.{
1693 .offset = base_offset + entry.value.index * @sizeOf(u64),
1694 .segment_id = segment_id,
1695 .dylib_ordinal = entry.value.dylib_ordinal,
1696 .name = entry.key,
1697 });
1698 }
1699 }
1700
1701 if (self.tlv_section_index) |idx| {
1702 const sect = data_seg.sections.items[idx];
1703 const base_offset = sect.addr - data_seg.inner.vmaddr;
1704 const segment_id = @intCast(u16, self.data_segment_cmd_index.?);
1705 for (self.threadlocal_imports.items()) |entry| {
1706 pointers.appendAssumeCapacity(.{
1707 .offset = base_offset + entry.value.index * @sizeOf(u64),
1708 .segment_id = segment_id,
1709 .dylib_ordinal = entry.value.dylib_ordinal,
1710 .name = entry.key,
1711 });
1712 }
1713 }
1714
1715 const size = try bindInfoSize(pointers.items);
1716 var buffer = try self.allocator.alloc(u8, @intCast(usize, size));
1717 defer self.allocator.free(buffer);
1718
1719 var stream = std.io.fixedBufferStream(buffer);
1720 try writeBindInfo(pointers.items, stream.writer());
1721
1722 const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment;
1723 const dyld_info = &self.load_commands.items[self.dyld_info_cmd_index.?].DyldInfoOnly;
1724 dyld_info.bind_off = @intCast(u32, seg.inner.fileoff + seg.inner.filesize);
1725 dyld_info.bind_size = @intCast(u32, mem.alignForwardGeneric(u64, buffer.len, @alignOf(u64)));
1726 seg.inner.filesize += dyld_info.bind_size;
1727
1728 log.debug("writing binding info from 0x{x} to 0x{x}", .{ dyld_info.bind_off, dyld_info.bind_off + dyld_info.bind_size });
1729
1730 try self.file.?.pwriteAll(buffer, dyld_info.bind_off);
1731}
1732
1733fn writeLazyBindInfoTable(self: *Zld) !void {
1734 const data_seg = self.load_commands.items[self.data_segment_cmd_index.?].Segment;
1735
1736 var pointers = std.ArrayList(Pointer).init(self.allocator);
1737 defer pointers.deinit();
1738 try pointers.ensureCapacity(self.lazy_imports.items().len);
1739
1740 if (self.la_symbol_ptr_section_index) |idx| {
1741 const sect = data_seg.sections.items[idx];
1742 const base_offset = sect.addr - data_seg.inner.vmaddr;
1743 const segment_id = @intCast(u16, self.data_segment_cmd_index.?);
1744 for (self.lazy_imports.items()) |entry| {
1745 pointers.appendAssumeCapacity(.{
1746 .offset = base_offset + entry.value.index * @sizeOf(u64),
1747 .segment_id = segment_id,
1748 .dylib_ordinal = entry.value.dylib_ordinal,
1749 .name = entry.key,
1750 });
1751 }
1752 }
1753
1754 const size = try lazyBindInfoSize(pointers.items);
1755 var buffer = try self.allocator.alloc(u8, @intCast(usize, size));
1756 defer self.allocator.free(buffer);
1757
1758 var stream = std.io.fixedBufferStream(buffer);
1759 try writeLazyBindInfo(pointers.items, stream.writer());
1760
1761 const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment;
1762 const dyld_info = &self.load_commands.items[self.dyld_info_cmd_index.?].DyldInfoOnly;
1763 dyld_info.lazy_bind_off = @intCast(u32, seg.inner.fileoff + seg.inner.filesize);
1764 dyld_info.lazy_bind_size = @intCast(u32, mem.alignForwardGeneric(u64, buffer.len, @alignOf(u64)));
1765 seg.inner.filesize += dyld_info.lazy_bind_size;
1766
1767 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 });
1768
1769 try self.file.?.pwriteAll(buffer, dyld_info.lazy_bind_off);
1770 try self.populateLazyBindOffsetsInStubHelper(buffer);
1771}
1772
1773fn populateLazyBindOffsetsInStubHelper(self: *Zld, buffer: []const u8) !void {
1774 var stream = std.io.fixedBufferStream(buffer);
1775 var reader = stream.reader();
1776 var offsets = std.ArrayList(u32).init(self.allocator);
1777 try offsets.append(0);
1778 defer offsets.deinit();
1779 var valid_block = false;
1780
1781 while (true) {
1782 const inst = reader.readByte() catch |err| switch (err) {
1783 error.EndOfStream => break,
1784 else => return err,
1785 };
1786 const imm: u8 = inst & macho.BIND_IMMEDIATE_MASK;
1787 const opcode: u8 = inst & macho.BIND_OPCODE_MASK;
1788
1789 switch (opcode) {
1790 macho.BIND_OPCODE_DO_BIND => {
1791 valid_block = true;
1792 },
1793 macho.BIND_OPCODE_DONE => {
1794 if (valid_block) {
1795 const offset = try stream.getPos();
1796 try offsets.append(@intCast(u32, offset));
1797 }
1798 valid_block = false;
1799 },
1800 macho.BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM => {
1801 var next = try reader.readByte();
1802 while (next != @as(u8, 0)) {
1803 next = try reader.readByte();
1804 }
1805 },
1806 macho.BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB => {
1807 _ = try leb.readULEB128(u64, reader);
1808 },
1809 macho.BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB => {
1810 _ = try leb.readULEB128(u64, reader);
1811 },
1812 macho.BIND_OPCODE_SET_ADDEND_SLEB => {
1813 _ = try leb.readILEB128(i64, reader);
1814 },
1815 else => {},
1816 }
1817 }
1818 assert(self.lazy_imports.items().len <= offsets.items.len);
1819
1820 const stub_size: u4 = switch (self.arch.?) {
1821 .x86_64 => 10,
1822 .aarch64 => 3 * @sizeOf(u32),
1823 else => unreachable,
1824 };
1825 const off: u4 = switch (self.arch.?) {
1826 .x86_64 => 1,
1827 .aarch64 => 2 * @sizeOf(u32),
1828 else => unreachable,
1829 };
1830 var buf: [@sizeOf(u32)]u8 = undefined;
1831 for (self.lazy_imports.items()) |entry| {
1832 const symbol = entry.value;
1833 const placeholder_off = self.stub_helper_stubs_start_off.? + symbol.index * stub_size + off;
1834 mem.writeIntLittle(u32, &buf, offsets.items[symbol.index]);
1835 try self.file.?.pwriteAll(&buf, placeholder_off);
1836 }
1837}
1838
1839fn writeExportInfo(self: *Zld) !void {
1840 var trie = Trie.init(self.allocator);
1841 defer trie.deinit();
1842
1843 const text_segment = self.load_commands.items[self.text_segment_cmd_index.?].Segment;
1844 for (self.exports.items()) |entry| {
1845 const name = entry.key;
1846 const symbol = entry.value;
1847 // TODO figure out if we should put all exports into the export trie
1848 assert(symbol.n_value >= text_segment.inner.vmaddr);
1849 try trie.put(.{
1850 .name = name,
1851 .vmaddr_offset = symbol.n_value - text_segment.inner.vmaddr,
1852 .export_flags = macho.EXPORT_SYMBOL_FLAGS_KIND_REGULAR,
1853 });
1854 }
1855
1856 try trie.finalize();
1857 var buffer = try self.allocator.alloc(u8, @intCast(usize, trie.size));
1858 defer self.allocator.free(buffer);
1859 var stream = std.io.fixedBufferStream(buffer);
1860 const nwritten = try trie.write(stream.writer());
1861 assert(nwritten == trie.size);
1862
1863 const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment;
1864 const dyld_info = &self.load_commands.items[self.dyld_info_cmd_index.?].DyldInfoOnly;
1865 dyld_info.export_off = @intCast(u32, seg.inner.fileoff + seg.inner.filesize);
1866 dyld_info.export_size = @intCast(u32, mem.alignForwardGeneric(u64, buffer.len, @alignOf(u64)));
1867 seg.inner.filesize += dyld_info.export_size;
1868
1869 log.debug("writing export info from 0x{x} to 0x{x}", .{ dyld_info.export_off, dyld_info.export_off + dyld_info.export_size });
1870
1871 try self.file.?.pwriteAll(buffer, dyld_info.export_off);
1872}
1873
1874fn writeDebugInfo(self: *Zld) !void {
1875 var stabs = std.ArrayList(macho.nlist_64).init(self.allocator);
1876 defer stabs.deinit();
1877
1878 for (self.objects.items) |object| {
1879 var debug_info = blk: {
1880 var di = try DebugInfo.parseFromObject(self.allocator, object);
1881 break :blk di orelse continue;
1882 };
1883 defer debug_info.deinit(self.allocator);
1884
1885 const compile_unit = try debug_info.inner.findCompileUnit(0x0); // We assume there is only one CU.
1886 const name = try compile_unit.die.getAttrString(&debug_info.inner, dwarf.AT_name);
1887 const comp_dir = try compile_unit.die.getAttrString(&debug_info.inner, dwarf.AT_comp_dir);
1888
1889 {
1890 const tu_path = try std.fs.path.join(self.allocator, &[_][]const u8{ comp_dir, name });
1891 defer self.allocator.free(tu_path);
1892 const dirname = std.fs.path.dirname(tu_path) orelse "./";
1893 // Current dir
1894 try stabs.append(.{
1895 .n_strx = try self.makeString(tu_path[0 .. dirname.len + 1]),
1896 .n_type = macho.N_SO,
1897 .n_sect = 0,
1898 .n_desc = 0,
1899 .n_value = 0,
1900 });
1901 // Artifact name
1902 try stabs.append(.{
1903 .n_strx = try self.makeString(tu_path[dirname.len + 1 ..]),
1904 .n_type = macho.N_SO,
1905 .n_sect = 0,
1906 .n_desc = 0,
1907 .n_value = 0,
1908 });
1909 // Path to object file with debug info
1910 var buffer: [std.fs.MAX_PATH_BYTES]u8 = undefined;
1911 const path = object.name;
1912 const full_path = try std.os.realpath(path, &buffer);
1913 const stat = try object.file.stat();
1914 const mtime = @intCast(u64, @divFloor(stat.mtime, 1_000_000_000));
1915 try stabs.append(.{
1916 .n_strx = try self.makeString(full_path),
1917 .n_type = macho.N_OSO,
1918 .n_sect = 0,
1919 .n_desc = 1,
1920 .n_value = mtime,
1921 });
1922 }
1923
1924 for (object.symtab.items) |source_sym| {
1925 const symname = object.getString(source_sym.n_strx);
1926 const source_addr = source_sym.n_value;
1927 const target_sym = self.locals.get(symname) orelse continue;
1928
1929 const maybe_size = blk: for (debug_info.inner.func_list.items) |func| {
1930 if (func.pc_range) |range| {
1931 if (source_addr >= range.start and source_addr < range.end) {
1932 break :blk range.end - range.start;
1933 }
1934 }
1935 } else null;
1936
1937 if (maybe_size) |size| {
1938 try stabs.append(.{
1939 .n_strx = 0,
1940 .n_type = macho.N_BNSYM,
1941 .n_sect = target_sym.n_sect,
1942 .n_desc = 0,
1943 .n_value = target_sym.n_value,
1944 });
1945 try stabs.append(.{
1946 .n_strx = target_sym.n_strx,
1947 .n_type = macho.N_FUN,
1948 .n_sect = target_sym.n_sect,
1949 .n_desc = 0,
1950 .n_value = target_sym.n_value,
1951 });
1952 try stabs.append(.{
1953 .n_strx = 0,
1954 .n_type = macho.N_FUN,
1955 .n_sect = 0,
1956 .n_desc = 0,
1957 .n_value = size,
1958 });
1959 try stabs.append(.{
1960 .n_strx = 0,
1961 .n_type = macho.N_ENSYM,
1962 .n_sect = target_sym.n_sect,
1963 .n_desc = 0,
1964 .n_value = size,
1965 });
1966 } else {
1967 // TODO need a way to differentiate symbols: global, static, local, etc.
1968 try stabs.append(.{
1969 .n_strx = target_sym.n_strx,
1970 .n_type = macho.N_STSYM,
1971 .n_sect = target_sym.n_sect,
1972 .n_desc = 0,
1973 .n_value = target_sym.n_value,
1974 });
1975 }
1976 }
1977
1978 // Close the source file!
1979 try stabs.append(.{
1980 .n_strx = 0,
1981 .n_type = macho.N_SO,
1982 .n_sect = 0,
1983 .n_desc = 0,
1984 .n_value = 0,
1985 });
1986 }
1987
1988 if (stabs.items.len == 0) return;
1989
1990 // Write stabs into the symbol table
1991 const linkedit = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment;
1992 const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab;
1993
1994 symtab.nsyms = @intCast(u32, stabs.items.len);
1995
1996 const stabs_off = symtab.symoff;
1997 const stabs_size = symtab.nsyms * @sizeOf(macho.nlist_64);
1998 log.debug("writing symbol stabs from 0x{x} to 0x{x}", .{ stabs_off, stabs_size + stabs_off });
1999 try self.file.?.pwriteAll(mem.sliceAsBytes(stabs.items), stabs_off);
2000
2001 linkedit.inner.filesize += stabs_size;
2002
2003 // Update dynamic symbol table.
2004 const dysymtab = &self.load_commands.items[self.dysymtab_cmd_index.?].Dysymtab;
2005 dysymtab.nlocalsym = symtab.nsyms;
2006}
2007
2008fn writeSymbolTable(self: *Zld) !void {
2009 const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment;
2010 const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab;
2011
2012 const nlocals = self.locals.items().len;
2013 var locals = std.ArrayList(macho.nlist_64).init(self.allocator);
2014 defer locals.deinit();
2015
2016 try locals.ensureCapacity(nlocals);
2017 for (self.locals.items()) |entry| {
2018 locals.appendAssumeCapacity(entry.value);
2019 }
2020
2021 const nexports = self.exports.items().len;
2022 var exports = std.ArrayList(macho.nlist_64).init(self.allocator);
2023 defer exports.deinit();
2024
2025 try exports.ensureCapacity(nexports);
2026 for (self.exports.items()) |entry| {
2027 exports.appendAssumeCapacity(entry.value);
2028 }
2029
2030 const nundefs = self.lazy_imports.items().len + self.nonlazy_imports.items().len + self.threadlocal_imports.items().len;
2031 var undefs = std.ArrayList(macho.nlist_64).init(self.allocator);
2032 defer undefs.deinit();
2033
2034 try undefs.ensureCapacity(nundefs);
2035 for (self.lazy_imports.items()) |entry| {
2036 undefs.appendAssumeCapacity(entry.value.symbol);
2037 }
2038 for (self.nonlazy_imports.items()) |entry| {
2039 undefs.appendAssumeCapacity(entry.value.symbol);
2040 }
2041 for (self.threadlocal_imports.items()) |entry| {
2042 undefs.appendAssumeCapacity(entry.value.symbol);
2043 }
2044
2045 const locals_off = symtab.symoff + symtab.nsyms * @sizeOf(macho.nlist_64);
2046 const locals_size = nlocals * @sizeOf(macho.nlist_64);
2047 log.debug("writing local symbols from 0x{x} to 0x{x}", .{ locals_off, locals_size + locals_off });
2048 try self.file.?.pwriteAll(mem.sliceAsBytes(locals.items), locals_off);
2049
2050 const exports_off = locals_off + locals_size;
2051 const exports_size = nexports * @sizeOf(macho.nlist_64);
2052 log.debug("writing exported symbols from 0x{x} to 0x{x}", .{ exports_off, exports_size + exports_off });
2053 try self.file.?.pwriteAll(mem.sliceAsBytes(exports.items), exports_off);
2054
2055 const undefs_off = exports_off + exports_size;
2056 const undefs_size = nundefs * @sizeOf(macho.nlist_64);
2057 log.debug("writing undefined symbols from 0x{x} to 0x{x}", .{ undefs_off, undefs_size + undefs_off });
2058 try self.file.?.pwriteAll(mem.sliceAsBytes(undefs.items), undefs_off);
2059
2060 symtab.nsyms += @intCast(u32, nlocals + nexports + nundefs);
2061 seg.inner.filesize += locals_size + exports_size + undefs_size;
2062
2063 // Update dynamic symbol table.
2064 const dysymtab = &self.load_commands.items[self.dysymtab_cmd_index.?].Dysymtab;
2065 dysymtab.nlocalsym += @intCast(u32, nlocals);
2066 dysymtab.iextdefsym = dysymtab.nlocalsym;
2067 dysymtab.nextdefsym = @intCast(u32, nexports);
2068 dysymtab.iundefsym = dysymtab.nlocalsym + dysymtab.nextdefsym;
2069 dysymtab.nundefsym = @intCast(u32, nundefs);
2070}
2071
2072fn writeDynamicSymbolTable(self: *Zld) !void {
2073 const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment;
2074 const text_segment = &self.load_commands.items[self.text_segment_cmd_index.?].Segment;
2075 const stubs = &text_segment.sections.items[self.stubs_section_index.?];
2076 const data_segment = &self.load_commands.items[self.data_segment_cmd_index.?].Segment;
2077 const got = &data_segment.sections.items[self.got_section_index.?];
2078 const la_symbol_ptr = &data_segment.sections.items[self.la_symbol_ptr_section_index.?];
2079 const dysymtab = &self.load_commands.items[self.dysymtab_cmd_index.?].Dysymtab;
2080
2081 const lazy = self.lazy_imports.items();
2082 const nonlazy = self.nonlazy_imports.items();
2083 dysymtab.indirectsymoff = @intCast(u32, seg.inner.fileoff + seg.inner.filesize);
2084 dysymtab.nindirectsyms = @intCast(u32, lazy.len * 2 + nonlazy.len);
2085 const needed_size = dysymtab.nindirectsyms * @sizeOf(u32);
2086 seg.inner.filesize += needed_size;
2087
2088 log.debug("writing indirect symbol table from 0x{x} to 0x{x}", .{
2089 dysymtab.indirectsymoff,
2090 dysymtab.indirectsymoff + needed_size,
2091 });
2092
2093 var buf = try self.allocator.alloc(u8, needed_size);
2094 defer self.allocator.free(buf);
2095 var stream = std.io.fixedBufferStream(buf);
2096 var writer = stream.writer();
2097
2098 stubs.reserved1 = 0;
2099 for (self.lazy_imports.items()) |_, i| {
2100 const symtab_idx = @intCast(u32, dysymtab.iundefsym + i);
2101 try writer.writeIntLittle(u32, symtab_idx);
2102 }
2103
2104 const base_id = @intCast(u32, lazy.len);
2105 got.reserved1 = base_id;
2106 for (self.nonlazy_imports.items()) |_, i| {
2107 const symtab_idx = @intCast(u32, dysymtab.iundefsym + i + base_id);
2108 try writer.writeIntLittle(u32, symtab_idx);
2109 }
2110
2111 la_symbol_ptr.reserved1 = got.reserved1 + @intCast(u32, nonlazy.len);
2112 for (self.lazy_imports.items()) |_, i| {
2113 const symtab_idx = @intCast(u32, dysymtab.iundefsym + i);
2114 try writer.writeIntLittle(u32, symtab_idx);
2115 }
2116
2117 try self.file.?.pwriteAll(buf, dysymtab.indirectsymoff);
2118}
2119
2120fn writeStringTable(self: *Zld) !void {
2121 const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment;
2122 const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab;
2123 symtab.stroff = @intCast(u32, seg.inner.fileoff + seg.inner.filesize);
2124 symtab.strsize = @intCast(u32, mem.alignForwardGeneric(u64, self.strtab.items.len, @alignOf(u64)));
2125 seg.inner.filesize += symtab.strsize;
2126
2127 log.debug("writing string table from 0x{x} to 0x{x}", .{ symtab.stroff, symtab.stroff + symtab.strsize });
2128
2129 try self.file.?.pwriteAll(self.strtab.items, symtab.stroff);
2130
2131 if (symtab.strsize > self.strtab.items.len and self.arch.? == .x86_64) {
2132 // This is the last section, so we need to pad it out.
2133 try self.file.?.pwriteAll(&[_]u8{0}, seg.inner.fileoff + seg.inner.filesize - 1);
2134 }
2135}
2136
2137fn writeCodeSignaturePadding(self: *Zld) !void {
2138 const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment;
2139 const code_sig_cmd = &self.load_commands.items[self.code_signature_cmd_index.?].LinkeditData;
2140 const fileoff = seg.inner.fileoff + seg.inner.filesize;
2141 const needed_size = CodeSignature.calcCodeSignaturePaddingSize(
2142 self.out_path.?,
2143 fileoff,
2144 self.page_size.?,
2145 );
2146 code_sig_cmd.dataoff = @intCast(u32, fileoff);
2147 code_sig_cmd.datasize = needed_size;
2148
2149 // Advance size of __LINKEDIT segment
2150 seg.inner.filesize += needed_size;
2151 seg.inner.vmsize = mem.alignForwardGeneric(u64, seg.inner.filesize, self.page_size.?);
2152
2153 log.debug("writing code signature padding from 0x{x} to 0x{x}", .{ fileoff, fileoff + needed_size });
2154
2155 // Pad out the space. We need to do this to calculate valid hashes for everything in the file
2156 // except for code signature data.
2157 try self.file.?.pwriteAll(&[_]u8{0}, fileoff + needed_size - 1);
2158}
2159
2160fn writeCodeSignature(self: *Zld) !void {
2161 const text_seg = self.load_commands.items[self.text_segment_cmd_index.?].Segment;
2162 const code_sig_cmd = self.load_commands.items[self.code_signature_cmd_index.?].LinkeditData;
2163
2164 var code_sig = CodeSignature.init(self.allocator, self.page_size.?);
2165 defer code_sig.deinit();
2166 try code_sig.calcAdhocSignature(
2167 self.file.?,
2168 self.out_path.?,
2169 text_seg.inner,
2170 code_sig_cmd,
2171 .Exe,
2172 );
2173
2174 var buffer = try self.allocator.alloc(u8, code_sig.size());
2175 defer self.allocator.free(buffer);
2176 var stream = std.io.fixedBufferStream(buffer);
2177 try code_sig.write(stream.writer());
2178
2179 log.debug("writing code signature from 0x{x} to 0x{x}", .{ code_sig_cmd.dataoff, code_sig_cmd.dataoff + buffer.len });
2180
2181 try self.file.?.pwriteAll(buffer, code_sig_cmd.dataoff);
2182}
2183
2184fn writeLoadCommands(self: *Zld) !void {
2185 var sizeofcmds: u32 = 0;
2186 for (self.load_commands.items) |lc| {
2187 sizeofcmds += lc.cmdsize();
2188 }
2189
2190 var buffer = try self.allocator.alloc(u8, sizeofcmds);
2191 defer self.allocator.free(buffer);
2192 var writer = std.io.fixedBufferStream(buffer).writer();
2193 for (self.load_commands.items) |lc| {
2194 try lc.write(writer);
2195 }
2196
2197 const off = @sizeOf(macho.mach_header_64);
2198 log.debug("writing {} load commands from 0x{x} to 0x{x}", .{ self.load_commands.items.len, off, off + sizeofcmds });
2199 try self.file.?.pwriteAll(buffer, off);
2200}
2201
2202fn writeHeader(self: *Zld) !void {
2203 var header: macho.mach_header_64 = undefined;
2204 header.magic = macho.MH_MAGIC_64;
2205
2206 const CpuInfo = struct {
2207 cpu_type: macho.cpu_type_t,
2208 cpu_subtype: macho.cpu_subtype_t,
2209 };
2210
2211 const cpu_info: CpuInfo = switch (self.arch.?) {
2212 .aarch64 => .{
2213 .cpu_type = macho.CPU_TYPE_ARM64,
2214 .cpu_subtype = macho.CPU_SUBTYPE_ARM_ALL,
2215 },
2216 .x86_64 => .{
2217 .cpu_type = macho.CPU_TYPE_X86_64,
2218 .cpu_subtype = macho.CPU_SUBTYPE_X86_64_ALL,
2219 },
2220 else => return error.UnsupportedCpuArchitecture,
2221 };
2222 header.cputype = cpu_info.cpu_type;
2223 header.cpusubtype = cpu_info.cpu_subtype;
2224 header.filetype = macho.MH_EXECUTE;
2225 header.flags = macho.MH_NOUNDEFS | macho.MH_DYLDLINK | macho.MH_PIE | macho.MH_TWOLEVEL;
2226 header.reserved = 0;
2227
2228 if (self.tlv_section_index) |_|
2229 header.flags |= macho.MH_HAS_TLV_DESCRIPTORS;
2230
2231 header.ncmds = @intCast(u32, self.load_commands.items.len);
2232 header.sizeofcmds = 0;
2233 for (self.load_commands.items) |cmd| {
2234 header.sizeofcmds += cmd.cmdsize();
2235 }
2236 log.debug("writing Mach-O header {}", .{header});
2237 try self.file.?.pwriteAll(mem.asBytes(&header), 0);
2238}
2239
2240pub fn makeStaticString(bytes: []const u8) [16]u8 {
2241 var buf = [_]u8{0} ** 16;
2242 assert(bytes.len <= buf.len);
2243 mem.copy(u8, &buf, bytes);
2244 return buf;
2245}
2246
2247fn makeString(self: *Zld, bytes: []const u8) !u32 {
2248 try self.strtab.ensureCapacity(self.allocator, self.strtab.items.len + bytes.len + 1);
2249 const offset = @intCast(u32, self.strtab.items.len);
2250 log.debug("writing new string '{s}' into string table at offset 0x{x}", .{ bytes, offset });
2251 self.strtab.appendSliceAssumeCapacity(bytes);
2252 self.strtab.appendAssumeCapacity(0);
2253 return offset;
2254}
2255
2256fn getString(self: *const Zld, str_off: u32) []const u8 {
2257 assert(str_off < self.strtab.items.len);
2258 return mem.spanZ(@ptrCast([*:0]const u8, self.strtab.items.ptr + str_off));
2259}
2260
2261pub fn parseName(name: *const [16]u8) []const u8 {
2262 const len = mem.indexOfScalar(u8, name, @as(u8, 0)) orelse name.len;
2263 return name[0..len];
2264}
2265
2266fn addSegmentToDir(self: *Zld, idx: u16) !void {
2267 const segment_cmd = self.load_commands.items[idx].Segment;
2268 return self.segments_directory.putNoClobber(self.allocator, segment_cmd.inner.segname, idx);
2269}
2270
2271fn addSectionToDir(self: *Zld, value: DirectoryEntry) !void {
2272 const seg = self.load_commands.items[value.seg_index].Segment;
2273 const sect = seg.sections.items[value.sect_index];
2274 return self.directory.putNoClobber(self.allocator, .{
2275 .segname = sect.segname,
2276 .sectname = sect.sectname,
2277 }, value);
2278}
2279
2280fn isLocal(sym: *const macho.nlist_64) callconv(.Inline) bool {
2281 if (isExtern(sym)) return false;
2282 const tt = macho.N_TYPE & sym.n_type;
2283 return tt == macho.N_SECT;
2284}
2285
2286fn isExport(sym: *const macho.nlist_64) callconv(.Inline) bool {
2287 if (!isExtern(sym)) return false;
2288 const tt = macho.N_TYPE & sym.n_type;
2289 return tt == macho.N_SECT;
2290}
2291
2292fn isImport(sym: *const macho.nlist_64) callconv(.Inline) bool {
2293 if (!isExtern(sym)) return false;
2294 const tt = macho.N_TYPE & sym.n_type;
2295 return tt == macho.N_UNDF;
2296}
2297
2298fn isExtern(sym: *const macho.nlist_64) callconv(.Inline) bool {
2299 if ((sym.n_type & macho.N_EXT) == 0) return false;
2300 return (sym.n_type & macho.N_PEXT) == 0;
2301}
src/link/MachO/commands.zig+5
...@@ -166,6 +166,11 @@ pub const SegmentCommand = struct {...@@ -166,6 +166,11 @@ pub const SegmentCommand = struct {
166 return .{ .inner = inner };166 return .{ .inner = inner };
167 }167 }
168168
169 // TODO remove me, I'm just a temp!
170 pub fn append(self: *SegmentCommand, alloc: *Allocator, section: macho.section_64) !void {
171 return self.addSection(alloc, section);
172 }
173
169 pub fn addSection(self: *SegmentCommand, alloc: *Allocator, section: macho.section_64) !void {174 pub fn addSection(self: *SegmentCommand, alloc: *Allocator, section: macho.section_64) !void {
170 try self.sections.append(alloc, section);175 try self.sections.append(alloc, section);
171 self.inner.cmdsize += @sizeOf(macho.section_64);176 self.inner.cmdsize += @sizeOf(macho.section_64);
src/link/MachO/reloc.zig created+159
...@@ -0,0 +1,159 @@
1const std = @import("std");
2const log = std.log.scoped(.reloc);
3
4pub const Arm64 = union(enum) {
5 Branch: packed struct {
6 disp: u26,
7 fixed: u5 = 0b00101,
8 link: u1,
9 },
10 BranchRegister: packed struct {
11 _1: u5 = 0b0000_0,
12 reg: u5,
13 _2: u11 = 0b1111_1000_000,
14 link: u1,
15 _3: u10 = 0b1101_0110_00,
16 },
17 Address: packed struct {
18 reg: u5,
19 immhi: u19,
20 _1: u5 = 0b10000,
21 immlo: u2,
22 page: u1,
23 },
24 LoadRegister: packed struct {
25 rt: u5,
26 rn: u5,
27 offset: u12,
28 _1: u8 = 0b111_0_01_01,
29 size: u1,
30 _2: u1 = 0b1,
31 },
32 LoadLiteral: packed struct {
33 reg: u5,
34 literal: u19,
35 _1: u6 = 0b011_0_00,
36 size: u1,
37 _2: u1 = 0b0,
38 },
39 Add: packed struct {
40 rt: u5,
41 rn: u5,
42 offset: u12,
43 _1: u9 = 0b0_0_100010_0,
44 size: u1,
45 },
46
47 pub fn toU32(self: Arm64) u32 {
48 const as_u32 = switch (self) {
49 .Branch => |x| @bitCast(u32, x),
50 .BranchRegister => |x| @bitCast(u32, x),
51 .Address => |x| @bitCast(u32, x),
52 .LoadRegister => |x| @bitCast(u32, x),
53 .LoadLiteral => |x| @bitCast(u32, x),
54 .Add => |x| @bitCast(u32, x),
55 };
56 return as_u32;
57 }
58
59 pub fn b(disp: i28) Arm64 {
60 return Arm64{
61 .Branch = .{
62 .disp = @truncate(u26, @bitCast(u28, disp) >> 2),
63 .link = 0,
64 },
65 };
66 }
67
68 pub fn bl(disp: i28) Arm64 {
69 return Arm64{
70 .Branch = .{
71 .disp = @truncate(u26, @bitCast(u28, disp) >> 2),
72 .link = 1,
73 },
74 };
75 }
76
77 pub fn br(reg: u5) Arm64 {
78 return Arm64{
79 .BranchRegister = .{
80 .reg = reg,
81 .link = 0,
82 },
83 };
84 }
85
86 pub fn blr(reg: u5) Arm64 {
87 return Arm64{
88 .BranchRegister = .{
89 .reg = reg,
90 .link = 1,
91 },
92 };
93 }
94
95 pub fn adr(reg: u5, disp: u21) Arm64 {
96 return Arm64{
97 .Address = .{
98 .reg = reg,
99 .immhi = @truncate(u19, disp >> 2),
100 .immlo = @truncate(u2, disp),
101 .page = 0,
102 },
103 };
104 }
105
106 pub fn adrp(reg: u5, disp: u21) Arm64 {
107 return Arm64{
108 .Address = .{
109 .reg = reg,
110 .immhi = @truncate(u19, disp >> 2),
111 .immlo = @truncate(u2, disp),
112 .page = 1,
113 },
114 };
115 }
116
117 pub fn ldr(reg: u5, literal: u19, size: u1) Arm64 {
118 return Arm64{
119 .LoadLiteral = .{
120 .reg = reg,
121 .literal = literal,
122 .size = size,
123 },
124 };
125 }
126
127 pub fn add(rt: u5, rn: u5, offset: u12, size: u1) Arm64 {
128 return Arm64{
129 .Add = .{
130 .rt = rt,
131 .rn = rn,
132 .offset = offset,
133 .size = size,
134 },
135 };
136 }
137
138 pub fn ldrr(rt: u5, rn: u5, offset: u12, size: u1) Arm64 {
139 return Arm64{
140 .LoadRegister = .{
141 .rt = rt,
142 .rn = rn,
143 .offset = offset,
144 .size = size,
145 },
146 };
147 }
148
149 pub fn isArithmetic(inst: *const [4]u8) bool {
150 const group_decode = @truncate(u5, inst[3]);
151 log.debug("{b}", .{group_decode});
152 return ((group_decode >> 2) == 4);
153 // if ((group_decode >> 2) == 4) {
154 // log.debug("Arithmetic imm", .{});
155 // } else if (((group_decode & 0b01010) >> 3) == 1) {
156 // log.debug("Load/store", .{});
157 // }
158 }
159};
src/main.zig+4
...@@ -547,6 +547,7 @@ fn buildOutputType(...@@ -547,6 +547,7 @@ fn buildOutputType(
547 var image_base_override: ?u64 = null;547 var image_base_override: ?u64 = null;
548 var use_llvm: ?bool = null;548 var use_llvm: ?bool = null;
549 var use_lld: ?bool = null;549 var use_lld: ?bool = null;
550 var use_zld: ?bool = null;
550 var use_clang: ?bool = null;551 var use_clang: ?bool = null;
551 var link_eh_frame_hdr = false;552 var link_eh_frame_hdr = false;
552 var link_emit_relocs = false;553 var link_emit_relocs = false;
...@@ -906,6 +907,8 @@ fn buildOutputType(...@@ -906,6 +907,8 @@ fn buildOutputType(
906 use_lld = true;907 use_lld = true;
907 } else if (mem.eql(u8, arg, "-fno-LLD")) {908 } else if (mem.eql(u8, arg, "-fno-LLD")) {
908 use_lld = false;909 use_lld = false;
910 } else if (mem.eql(u8, arg, "-fZLD")) {
911 use_zld = true;
909 } else if (mem.eql(u8, arg, "-fClang")) {912 } else if (mem.eql(u8, arg, "-fClang")) {
910 use_clang = true;913 use_clang = true;
911 } else if (mem.eql(u8, arg, "-fno-Clang")) {914 } else if (mem.eql(u8, arg, "-fno-Clang")) {
...@@ -1864,6 +1867,7 @@ fn buildOutputType(...@@ -1864,6 +1867,7 @@ fn buildOutputType(
1864 .want_compiler_rt = want_compiler_rt,1867 .want_compiler_rt = want_compiler_rt,
1865 .use_llvm = use_llvm,1868 .use_llvm = use_llvm,
1866 .use_lld = use_lld,1869 .use_lld = use_lld,
1870 .use_zld = use_zld,
1867 .use_clang = use_clang,1871 .use_clang = use_clang,
1868 .rdynamic = rdynamic,1872 .rdynamic = rdynamic,
1869 .linker_script = linker_script,1873 .linker_script = linker_script,