authorgravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2020-11-30 19:42:08+01:00
committergravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2020-12-01 10:49:31+01:00
logde66b65677f8207b2bc10997031b90c06b738dc8
tree7760341c9887fab97633d2f8191ddc456774fc5c
parent0ef3071db6827427e834475d8d111370cc25a924

lld: start unifying load command logic


3 files changed, 460 insertions(+), 2 deletions(-)

src/link/MachO.zig+9-2
...@@ -813,7 +813,7 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void {...@@ -813,7 +813,7 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void {
813 // Pad out space for code signature813 // Pad out space for code signature
814 const text_cmd = parser.load_commands.items[parser.text_cmd_index.?].Segment.inner;814 const text_cmd = parser.load_commands.items[parser.text_cmd_index.?].Segment.inner;
815 const dataoff = @intCast(u32, mem.alignForward(parser.end_pos.?, @sizeOf(u64)));815 const dataoff = @intCast(u32, mem.alignForward(parser.end_pos.?, @sizeOf(u64)));
816 const datasize = 0x1000;816 const datasize = 0x400000;
817 const code_sig = macho.linkedit_data_command{817 const code_sig = macho.linkedit_data_command{
818 .cmd = macho.LC_CODE_SIGNATURE,818 .cmd = macho.LC_CODE_SIGNATURE,
819 .cmdsize = @sizeOf(macho.linkedit_data_command),819 .cmdsize = @sizeOf(macho.linkedit_data_command),
...@@ -1600,7 +1600,7 @@ fn allocateTextBlock(self: *MachO, text_block: *TextBlock, new_block_size: u64,...@@ -1600,7 +1600,7 @@ fn allocateTextBlock(self: *MachO, text_block: *TextBlock, new_block_size: u64,
1600 return vaddr;1600 return vaddr;
1601}1601}
16021602
1603fn makeStaticString(comptime bytes: []const u8) [16]u8 {1603pub fn makeStaticString(comptime bytes: []const u8) [16]u8 {
1604 var buf = [_]u8{0} ** 16;1604 var buf = [_]u8{0} ** 16;
1605 if (bytes.len > buf.len) @compileError("string too long; max 16 bytes");1605 if (bytes.len > buf.len) @compileError("string too long; max 16 bytes");
1606 mem.copy(u8, buf[0..], bytes);1606 mem.copy(u8, buf[0..], bytes);
...@@ -1994,3 +1994,10 @@ fn satMul(a: anytype, b: anytype) @TypeOf(a, b) {...@@ -1994,3 +1994,10 @@ fn satMul(a: anytype, b: anytype) @TypeOf(a, b) {
1994 const T = @TypeOf(a, b);1994 const T = @TypeOf(a, b);
1995 return std.math.mul(T, a, b) catch std.math.maxInt(T);1995 return std.math.mul(T, a, b) catch std.math.maxInt(T);
1996}1996}
1997
1998test "" {
1999 // TODO surprisingly this causes a linking error:
2000 // _linkWithLLD symbol missing for arch
2001 // _ = std.testing.refAllDecls(@This());
2002 _ = std.testing.refAllDecls(@import("MachO/commands.zig"));
2003}
src/link/MachO/Parser.zig created+80
...@@ -0,0 +1,80 @@
1const Parser = @This();
2
3const std = @import("std");
4const fs = std.fs;
5const io = std.io;
6const mem = std.mem;
7const macho = std.macho;
8
9const Allocator = std.mem.Allocator;
10
11const LoadCommand = @import("commands.zig").LoadCommand;
12
13allocator: *Allocator,
14
15/// Mach-O header
16header: ?macho.mach_header_64 = null,
17
18/// Load commands
19load_commands: std.ArrayListUnmanaged(LoadCommand) = .{},
20
21text_cmd_index: ?usize = null,
22
23linkedit_cmd_index: ?usize = null,
24linkedit_cmd_offset: ?u64 = null,
25
26code_sig_cmd_offset: ?u64 = null,
27
28end_pos: ?u64 = null,
29
30pub fn init(allocator: *Allocator) Parser {
31 return .{
32 .allocator = allocator,
33 };
34}
35
36pub fn parse(self: *Parser, reader: anytype) !void {
37 self.header = try reader.readStruct(macho.mach_header_64);
38
39 const ncmds = self.header.?.ncmds;
40 try self.load_commands.ensureCapacity(self.allocator, ncmds);
41
42 var off: u64 = @sizeOf(macho.mach_header_64);
43 var i: u16 = 0;
44 while (i < ncmds) : (i += 1) {
45 const cmd = try LoadCommand.read(self.allocator, reader);
46 switch (cmd.cmd()) {
47 macho.LC_SEGMENT_64 => {
48 const x = cmd.Segment;
49 if (mem.eql(u8, mem.trimRight(u8, x.inner.segname[0..], &[_]u8{0}), "__LINKEDIT")) {
50 self.linkedit_cmd_index = i;
51 self.linkedit_cmd_offset = off;
52 } else if (mem.eql(u8, mem.trimRight(u8, x.inner.segname[0..], &[_]u8{0}), "__TEXT")) {
53 self.text_cmd_index = i;
54 }
55 },
56 macho.LC_SYMTAB => {
57 const x = cmd.Symtab;
58 self.end_pos = x.stroff + x.strsize;
59 },
60 else => {},
61 }
62 off += cmd.cmdsize();
63 self.load_commands.appendAssumeCapacity(cmd);
64 }
65
66 self.code_sig_cmd_offset = off;
67
68 // TODO parse memory mapped segments
69}
70
71pub fn parseFile(self: *Parser, file: fs.File) !void {
72 return self.parse(file.reader());
73}
74
75pub fn deinit(self: *Parser) void {
76 for (self.load_commands.items) |*cmd| {
77 cmd.deinit(self.allocator);
78 }
79 self.load_commands.deinit(self.allocator);
80}
src/link/MachO/commands.zig created+371
...@@ -0,0 +1,371 @@
1const std = @import("std");
2const fs = std.fs;
3const io = std.io;
4const mem = std.mem;
5const macho = std.macho;
6const testing = std.testing;
7
8const Allocator = std.mem.Allocator;
9const makeName = @import("../MachO.zig").makeStaticString;
10
11pub const LoadCommand = union(enum) {
12 Segment: SegmentCommand,
13 DyldInfoOnly: macho.dyld_info_command,
14 Symtab: macho.symtab_command,
15 Dysymtab: macho.dysymtab_command,
16 Dylinker: GenericCommandWithData(macho.dylinker_command),
17 Dylib: GenericCommandWithData(macho.dylib_command),
18 Main: macho.entry_point_command,
19 VersionMin: macho.version_min_command,
20 SourceVersion: macho.source_version_command,
21 LinkeditData: macho.linkedit_data_command,
22 Unknown: GenericCommandWithData(macho.load_command),
23
24 pub fn read(allocator: *Allocator, reader: anytype) !LoadCommand {
25 const header = try reader.readStruct(macho.load_command);
26 var buffer = try allocator.alloc(u8, header.cmdsize);
27 defer allocator.free(buffer);
28 const slice = [1]macho.load_command{header};
29 mem.copy(u8, buffer[0..], mem.sliceAsBytes(slice[0..1]));
30 try reader.readNoEof(buffer[@sizeOf(macho.load_command)..]);
31 var stream = io.fixedBufferStream(buffer[0..]);
32
33 return switch (header.cmd) {
34 macho.LC_SEGMENT_64 => LoadCommand{
35 .Segment = try SegmentCommand.read(allocator, stream.reader()),
36 },
37 macho.LC_DYLD_INFO, macho.LC_DYLD_INFO_ONLY => LoadCommand{
38 .DyldInfoOnly = try stream.reader().readStruct(macho.dyld_info_command),
39 },
40 macho.LC_SYMTAB => LoadCommand{
41 .Symtab = try stream.reader().readStruct(macho.symtab_command),
42 },
43 macho.LC_DYSYMTAB => LoadCommand{
44 .Dysymtab = try stream.reader().readStruct(macho.dysymtab_command),
45 },
46 macho.LC_ID_DYLINKER, macho.LC_LOAD_DYLINKER, macho.LC_DYLD_ENVIRONMENT => LoadCommand{
47 .Dylinker = try GenericCommandWithData(macho.dylinker_command).read(allocator, stream.reader()),
48 },
49 macho.LC_ID_DYLIB, macho.LC_LOAD_WEAK_DYLIB, macho.LC_LOAD_DYLIB, macho.LC_REEXPORT_DYLIB => LoadCommand{
50 .Dylib = try GenericCommandWithData(macho.dylib_command).read(allocator, stream.reader()),
51 },
52 macho.LC_MAIN => LoadCommand{
53 .Main = try stream.reader().readStruct(macho.entry_point_command),
54 },
55 macho.LC_VERSION_MIN_MACOSX, macho.LC_VERSION_MIN_IPHONEOS, macho.LC_VERSION_MIN_WATCHOS, macho.LC_VERSION_MIN_TVOS => LoadCommand{
56 .VersionMin = try stream.reader().readStruct(macho.version_min_command),
57 },
58 macho.LC_SOURCE_VERSION => LoadCommand{
59 .SourceVersion = try stream.reader().readStruct(macho.source_version_command),
60 },
61 macho.LC_FUNCTION_STARTS, macho.LC_DATA_IN_CODE, macho.LC_CODE_SIGNATURE => LoadCommand{
62 .LinkeditData = try stream.reader().readStruct(macho.linkedit_data_command),
63 },
64 else => LoadCommand{
65 .Unknown = try GenericCommandWithData(macho.load_command).read(allocator, stream.reader()),
66 },
67 };
68 }
69
70 pub fn write(self: LoadCommand, writer: anytype) !void {
71 return switch (self) {
72 .DyldInfoOnly => |x| writeStruct(x, writer),
73 .Symtab => |x| writeStruct(x, writer),
74 .Dysymtab => |x| writeStruct(x, writer),
75 .Main => |x| writeStruct(x, writer),
76 .VersionMin => |x| writeStruct(x, writer),
77 .SourceVersion => |x| writeStruct(x, writer),
78 .LinkeditData => |x| writeStruct(x, writer),
79 .Segment => |x| x.write(writer),
80 .Dylinker => |x| x.write(writer),
81 .Dylib => |x| x.write(writer),
82 .Unknown => |x| x.write(writer),
83 };
84 }
85
86 pub fn cmd(self: LoadCommand) u32 {
87 return switch (self) {
88 .DyldInfoOnly => |x| x.cmd,
89 .Symtab => |x| x.cmd,
90 .Dysymtab => |x| x.cmd,
91 .Main => |x| x.cmd,
92 .VersionMin => |x| x.cmd,
93 .SourceVersion => |x| x.cmd,
94 .LinkeditData => |x| x.cmd,
95 .Segment => |x| x.inner.cmd,
96 .Dylinker => |x| x.inner.cmd,
97 .Dylib => |x| x.inner.cmd,
98 .Unknown => |x| x.inner.cmd,
99 };
100 }
101
102 pub fn cmdsize(self: LoadCommand) u32 {
103 return switch (self) {
104 .DyldInfoOnly => |x| x.cmdsize,
105 .Symtab => |x| x.cmdsize,
106 .Dysymtab => |x| x.cmdsize,
107 .Main => |x| x.cmdsize,
108 .VersionMin => |x| x.cmdsize,
109 .SourceVersion => |x| x.cmdsize,
110 .LinkeditData => |x| x.cmdsize,
111 .Segment => |x| x.inner.cmdsize,
112 .Dylinker => |x| x.inner.cmdsize,
113 .Dylib => |x| x.inner.cmdsize,
114 .Unknown => |x| x.inner.cmdsize,
115 };
116 }
117
118 pub fn deinit(self: *LoadCommand, allocator: *Allocator) void {
119 return switch (self.*) {
120 .Segment => |*x| x.deinit(allocator),
121 .Dylinker => |*x| x.deinit(allocator),
122 .Dylib => |*x| x.deinit(allocator),
123 .Unknown => |*x| x.deinit(allocator),
124 else => {},
125 };
126 }
127
128 fn writeStruct(command: anytype, writer: anytype) !void {
129 const slice = [1]@TypeOf(command){command};
130 return writer.writeAll(mem.sliceAsBytes(slice[0..1]));
131 }
132
133 fn eql(self: LoadCommand, other: LoadCommand) bool {
134 if (@as(@TagType(LoadCommand), self) != @as(@TagType(LoadCommand), other)) return false;
135 return switch (self) {
136 .DyldInfoOnly => |x| eqlStruct(x, other.DyldInfoOnly),
137 .Symtab => |x| eqlStruct(x, other.Symtab),
138 .Dysymtab => |x| eqlStruct(x, other.Dysymtab),
139 .Main => |x| eqlStruct(x, other.Main),
140 .VersionMin => |x| eqlStruct(x, other.VersionMin),
141 .SourceVersion => |x| eqlStruct(x, other.SourceVersion),
142 .LinkeditData => |x| eqlStruct(x, other.LinkeditData),
143 .Segment => |x| x.eql(other.Segment),
144 .Dylinker => |x| x.eql(other.Dylinker),
145 .Dylib => |x| x.eql(other.Dylib),
146 .Unknown => |x| x.eql(other.Unknown),
147 };
148 }
149
150 fn eqlStruct(lhs: anytype, rhs: anytype) bool {
151 return mem.eql(u8, mem.asBytes(&lhs), mem.asBytes(&rhs));
152 }
153};
154
155pub const SegmentCommand = struct {
156 inner: macho.segment_command_64,
157 sections: std.StringArrayHashMapUnmanaged(macho.section_64) = .{},
158
159 pub fn read(alloc: *Allocator, reader: anytype) !SegmentCommand {
160 const inner = try reader.readStruct(macho.segment_command_64);
161 var segment = SegmentCommand{
162 .inner = inner,
163 };
164 try segment.sections.ensureCapacity(alloc, inner.nsects);
165
166 var i: usize = 0;
167 while (i < inner.nsects) : (i += 1) {
168 const section = try reader.readStruct(macho.section_64);
169 segment.sections.putAssumeCapacityNoClobber(mem.trimRight(u8, section.sectname[0..], &[_]u8{0}), section);
170 }
171
172 return segment;
173 }
174
175 pub fn write(self: SegmentCommand, writer: anytype) !void {
176 const cmd = [1]macho.segment_command_64{self.inner};
177 try writer.writeAll(mem.sliceAsBytes(cmd[0..1]));
178
179 for (self.sections.items()) |entry| {
180 const section = [1]macho.section_64{entry.value};
181 try writer.writeAll(mem.sliceAsBytes(section[0..1]));
182 }
183 }
184
185 pub fn deinit(self: *SegmentCommand, alloc: *Allocator) void {
186 self.sections.deinit(alloc);
187 }
188
189 fn eql(self: SegmentCommand, other: SegmentCommand) bool {
190 if (!mem.eql(u8, mem.asBytes(&self.inner), mem.asBytes(&other.inner))) return false;
191 const lhs = self.sections.items();
192 const rhs = other.sections.items();
193 var i: usize = 0;
194 while (i < self.inner.nsects) : (i += 1) {
195 if (!mem.eql(u8, lhs[i].key, rhs[i].key)) return false;
196 if (!mem.eql(u8, mem.asBytes(&lhs[i].value), mem.asBytes(&rhs[i].value))) return false;
197 }
198 return true;
199 }
200};
201
202pub fn GenericCommandWithData(comptime Cmd: type) type {
203 return struct {
204 inner: Cmd,
205 /// This field remains undefined until `read` is called.
206 data: []u8 = undefined,
207
208 const Self = @This();
209
210 pub fn read(allocator: *Allocator, reader: anytype) !Self {
211 const inner = try reader.readStruct(Cmd);
212 var data = try allocator.alloc(u8, inner.cmdsize - @sizeOf(Cmd));
213 errdefer allocator.free(data);
214 try reader.readNoEof(data[0..]);
215 return Self{
216 .inner = inner,
217 .data = data,
218 };
219 }
220
221 pub fn write(self: Self, writer: anytype) !void {
222 const cmd = [1]Cmd{self.inner};
223 try writer.writeAll(mem.sliceAsBytes(cmd[0..1]));
224 try writer.writeAll(self.data);
225 }
226
227 pub fn deinit(self: *Self, allocator: *Allocator) void {
228 allocator.free(self.data);
229 }
230
231 pub fn eql(self: Self, other: Self) bool {
232 if (!mem.eql(u8, mem.asBytes(&self.inner), mem.asBytes(&other.inner))) return false;
233 return mem.eql(u8, self.data, other.data);
234 }
235 };
236}
237
238fn testRead(allocator: *Allocator, buffer: []const u8, expected: anytype) !void {
239 var stream = io.fixedBufferStream(buffer);
240 var given = try LoadCommand.read(allocator, stream.reader());
241 defer given.deinit(allocator);
242 testing.expect(expected.eql(given));
243}
244
245fn testWrite(buffer: []u8, cmd: LoadCommand, expected: []const u8) !void {
246 var stream = io.fixedBufferStream(buffer);
247 try cmd.write(stream.writer());
248 testing.expect(mem.eql(u8, expected, buffer[0..expected.len]));
249}
250
251test "read-write segment command" {
252 var gpa = testing.allocator;
253 const in_buffer = &[_]u8{
254 0x19, 0x00, 0x00, 0x00, // cmd
255 0x98, 0x00, 0x00, 0x00, // cmdsize
256 0x5f, 0x5f, 0x54, 0x45, 0x58, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // segname
257 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, // vmaddr
258 0x00, 0x80, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, // vmsize
259 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // fileoff
260 0x00, 0x80, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, // filesize
261 0x07, 0x00, 0x00, 0x00, // maxprot
262 0x05, 0x00, 0x00, 0x00, // initprot
263 0x01, 0x00, 0x00, 0x00, // nsects
264 0x00, 0x00, 0x00, 0x00, // flags
265 0x5f, 0x5f, 0x74, 0x65, 0x78, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // sectname
266 0x5f, 0x5f, 0x54, 0x45, 0x58, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // segname
267 0x00, 0x40, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, // address
268 0xc0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // size
269 0x00, 0x40, 0x00, 0x00, // offset
270 0x02, 0x00, 0x00, 0x00, // alignment
271 0x00, 0x00, 0x00, 0x00, // reloff
272 0x00, 0x00, 0x00, 0x00, // nreloc
273 0x00, 0x04, 0x00, 0x80, // flags
274 0x00, 0x00, 0x00, 0x00, // reserved1
275 0x00, 0x00, 0x00, 0x00, // reserved2
276 0x00, 0x00, 0x00, 0x00, // reserved3
277 };
278 var cmd = SegmentCommand{
279 .inner = .{
280 .cmd = macho.LC_SEGMENT_64,
281 .cmdsize = 152,
282 .segname = makeName("__TEXT"),
283 .vmaddr = 4294967296,
284 .vmsize = 294912,
285 .fileoff = 0,
286 .filesize = 294912,
287 .maxprot = macho.VM_PROT_READ | macho.VM_PROT_WRITE | macho.VM_PROT_EXECUTE,
288 .initprot = macho.VM_PROT_EXECUTE | macho.VM_PROT_READ,
289 .nsects = 1,
290 .flags = 0,
291 },
292 };
293 try cmd.sections.putNoClobber(gpa, "__text", .{
294 .sectname = makeName("__text"),
295 .segname = makeName("__TEXT"),
296 .addr = 4294983680,
297 .size = 448,
298 .offset = 16384,
299 .@"align" = 2,
300 .reloff = 0,
301 .nreloc = 0,
302 .flags = macho.S_REGULAR | macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS,
303 .reserved1 = 0,
304 .reserved2 = 0,
305 .reserved3 = 0,
306 });
307 defer cmd.deinit(gpa);
308 try testRead(gpa, in_buffer[0..], LoadCommand{ .Segment = cmd });
309
310 var out_buffer: [in_buffer.len]u8 = undefined;
311 try testWrite(out_buffer[0..], LoadCommand{ .Segment = cmd }, in_buffer[0..]);
312}
313
314test "read-write generic command with data" {
315 var gpa = testing.allocator;
316 const in_buffer = &[_]u8{
317 0x0c, 0x00, 0x00, 0x00, // cmd
318 0x20, 0x00, 0x00, 0x00, // cmdsize
319 0x18, 0x00, 0x00, 0x00, // name
320 0x02, 0x00, 0x00, 0x00, // timestamp
321 0x00, 0x00, 0x00, 0x00, // current_version
322 0x00, 0x00, 0x00, 0x00, // compatibility_version
323 0x2f, 0x75, 0x73, 0x72, 0x00, 0x00, 0x00, 0x00, // data
324 };
325 var cmd = GenericCommandWithData(macho.dylib_command){
326 .inner = .{
327 .cmd = macho.LC_LOAD_DYLIB,
328 .cmdsize = 32,
329 .dylib = .{
330 .name = 24,
331 .timestamp = 2,
332 .current_version = 0,
333 .compatibility_version = 0,
334 },
335 },
336 };
337 cmd.data = try gpa.alloc(u8, 8);
338 defer gpa.free(cmd.data);
339 cmd.data[0] = 0x2f;
340 cmd.data[1] = 0x75;
341 cmd.data[2] = 0x73;
342 cmd.data[3] = 0x72;
343 cmd.data[4] = 0x0;
344 cmd.data[5] = 0x0;
345 cmd.data[6] = 0x0;
346 cmd.data[7] = 0x0;
347 try testRead(gpa, in_buffer[0..], LoadCommand{ .Dylib = cmd });
348
349 var out_buffer: [in_buffer.len]u8 = undefined;
350 try testWrite(out_buffer[0..], LoadCommand{ .Dylib = cmd }, in_buffer[0..]);
351}
352
353test "read-write C struct command" {
354 var gpa = testing.allocator;
355 const in_buffer = &[_]u8{
356 0x28, 0x00, 0x00, 0x80, // cmd
357 0x18, 0x00, 0x00, 0x00, // cmdsize
358 0x04, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // entryoff
359 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // stacksize
360 };
361 const cmd = .{
362 .cmd = macho.LC_MAIN,
363 .cmdsize = 24,
364 .entryoff = 16644,
365 .stacksize = 0,
366 };
367 try testRead(gpa, in_buffer[0..], LoadCommand{ .Main = cmd });
368
369 var out_buffer: [in_buffer.len]u8 = undefined;
370 try testWrite(out_buffer[0..], LoadCommand{ .Main = cmd }, in_buffer[0..]);
371}