lib/tldr/src/load.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const builtin = @import("builtin");
3 const sys = @import("sys");
4
5 const formats = @import("formats/root.zig");
6 const linker = @import("link.zig");
7 const model = @import("model.zig");
8
9 const elf_object = formats.elf.object;
10 const format = formats.elf.format;
11 const memory = sys.memory;
12
13 const ehdr_size = format.ehdr_size;
14 const phdr_size = format.phdr_size;
15 const readU16 = format.readU16;
16 const readU32 = format.readU32;
17 const readU64 = format.readU64;
18 const requireRange = format.requireRange;
19
20 pub const LoadError = model.Error || memory.MapError || memory.ProtectError;
21
22 pub const LoadedImage = struct {
23 memory: []align(std.heap.page_size_min) u8,
24 entry_address: usize,
25
26 pub fn deinit(self: *LoadedImage) void {
27 memory.unmap(self.memory);
28 self.* = undefined;
29 }
30
31 pub fn entry(self: LoadedImage, comptime Function: type) Function {
32 return @ptrFromInt(self.entry_address);
33 }
34 };
35
36 const ImageSpan = struct {
37 start: u64,
38 end: u64,
39
40 fn byteCount(self: ImageSpan) model.Error!usize {
41 if (self.end <= self.start) return error.InvalidRange;
42 return std.math.cast(usize, self.end - self.start) orelse error.InvalidRange;
43 }
44 };
45
46 const LoadHeader = struct {
47 flags: u32,
48 offset: u64,
49 virtual_address: u64,
50 file_size: u64,
51 memory_size: u64,
52 };
53
54 pub fn loadExecutable(
55 allocator: std.mem.Allocator,
56 inputs: []const model.Input,
57 options: model.LinkOptions,
58 ) LoadError!LoadedImage {
59 if (builtin.cpu.arch != .x86_64) return error.UnsupportedArchitecture;
60
61 var probe_options = loadOptions(options);
62 probe_options.image_base = 0;
63 var probe = try linker.link(allocator, inputs, probe_options);
64 defer probe.deinit(allocator);
65
66 const probe_span = try imageSpan(probe.bytes);
67 const memory_len = try probe_span.byteCount();
68 const mapping = try memory.reserveAddressSpace(memory_len);
69 errdefer memory.unmap(mapping);
70
71 var mapped_options = loadOptions(options);
72 const mapping_address: u64 = @intCast(@intFromPtr(mapping.ptr));
73 if (mapping_address < probe_span.start) return error.InvalidRange;
74 mapped_options.image_base = mapping_address - probe_span.start;
75
76 var linked = try linker.link(allocator, inputs, mapped_options);
77 defer linked.deinit(allocator);
78
79 const mapped_span = try imageSpan(linked.bytes);
80 if (mapped_span.start != mapping_address) return error.InvalidRange;
81 if (try mapped_span.byteCount() > mapping.len) return error.InvalidRange;
82
83 try memory.protect(mapping, .{ .read = true, .write = true });
84 @memset(mapping, 0);
85 try copyLoadSegments(mapping, linked.bytes, mapped_span);
86 try protectLoadSegments(mapping, linked.bytes, mapped_span);
87
88 const entry_address = try entryAddress(linked.bytes, mapped_span);
89 return .{
90 .memory = mapping,
91 .entry_address = entry_address,
92 };
93 }
94
95 fn loadOptions(options: model.LinkOptions) model.LinkOptions {
96 var result = options;
97 result.output_kind = .executable;
98 result.page_size = memory.pageSize();
99 result.mapped_output_file = null;
100 return result;
101 }
102
103 fn imageSpan(image: []const u8) model.Error!ImageSpan {
104 var result = ImageSpan{
105 .start = std.math.maxInt(u64),
106 .end = 0,
107 };
108 var found = false;
109 var iterator = try LoadIterator.init(image);
110 while (iterator.next()) |header| {
111 if (header.memory_size < header.file_size) return error.InvalidElfHeader;
112 const start = alignDown(header.virtual_address, iterator.page_size);
113 const end = try alignForwardU64(try checkedAddU64(header.virtual_address, header.memory_size), iterator.page_size);
114 result.start = @min(result.start, start);
115 result.end = @max(result.end, end);
116 found = true;
117 }
118 if (!found) return error.MissingSection;
119 return result;
120 }
121
122 fn copyLoadSegments(mapping: []u8, image: []const u8, span: ImageSpan) model.Error!void {
123 var iterator = try LoadIterator.init(image);
124 while (iterator.next()) |header| {
125 try requireRange(image, header.offset, header.file_size);
126 const dest_start = try relativeOffset(span, header.virtual_address);
127 const file_size = std.math.cast(usize, header.file_size) orelse return error.InvalidRange;
128 if (file_size > mapping.len - dest_start) return error.InvalidRange;
129 const source_start = std.math.cast(usize, header.offset) orelse return error.InvalidRange;
130 @memcpy(mapping[dest_start..][0..file_size], image[source_start..][0..file_size]);
131 }
132 }
133
134 fn protectLoadSegments(
135 mapping: []align(std.heap.page_size_min) u8,
136 image: []const u8,
137 span: ImageSpan,
138 ) LoadError!void {
139 var iterator = try LoadIterator.init(image);
140 while (iterator.next()) |header| {
141 const page_start = alignDown(header.virtual_address, iterator.page_size);
142 const page_end = try alignForwardU64(try checkedAddU64(header.virtual_address, header.memory_size), iterator.page_size);
143 const start = try relativeOffset(span, page_start);
144 const size = std.math.cast(usize, page_end - page_start) orelse return error.InvalidRange;
145 if (size > mapping.len - start) return error.InvalidRange;
146 const segment: []align(std.heap.page_size_min) u8 = @alignCast(mapping[start..][0..size]);
147 try memory.protect(segment, protectionForFlags(header.flags));
148 }
149 }
150
151 fn entryAddress(image: []const u8, span: ImageSpan) model.Error!usize {
152 if (image.len < ehdr_size) return error.InvalidElfHeader;
153 const entry = readU64(image, 24);
154 if (entry < span.start or entry >= span.end) return error.MissingEntrySymbol;
155 return std.math.cast(usize, entry) orelse error.InvalidRange;
156 }
157
158 const LoadIterator = struct {
159 image: []const u8,
160 phoff: u64,
161 phnum: u16,
162 index: u16 = 0,
163 page_size: u64,
164
165 fn init(image: []const u8) model.Error!LoadIterator {
166 if (image.len < ehdr_size) return error.InvalidElfHeader;
167 if (!std.mem.eql(u8, image[0..4], std.elf.MAGIC)) return error.InvalidElfHeader;
168 if (readU16(image, 16) != @backingInt(std.elf.ET.EXEC)) return error.UnsupportedOutputKind;
169 const phoff = readU64(image, 32);
170 const phentsize = readU16(image, 54);
171 const phnum = readU16(image, 56);
172 if (phentsize != phdr_size) return error.InvalidElfHeader;
173 try requireRange(image, phoff, @as(u64, phnum) * phdr_size);
174 return .{
175 .image = image,
176 .phoff = phoff,
177 .phnum = phnum,
178 .page_size = memory.pageSize(),
179 };
180 }
181
182 fn next(self: *LoadIterator) ?LoadHeader {
183 while (self.index < self.phnum) {
184 const offset: usize = @intCast(self.phoff + @as(u64, self.index) * phdr_size);
185 self.index += 1;
186 if (readU32(self.image, offset) != std.elf.PT_LOAD) continue;
187 return .{
188 .flags = readU32(self.image, offset + 4),
189 .offset = readU64(self.image, offset + 8),
190 .virtual_address = readU64(self.image, offset + 16),
191 .file_size = readU64(self.image, offset + 32),
192 .memory_size = readU64(self.image, offset + 40),
193 };
194 }
195 return null;
196 }
197 };
198
199 fn protectionForFlags(flags: u32) memory.Protection {
200 return .{
201 .read = (flags & std.elf.PF_R) != 0,
202 .write = (flags & std.elf.PF_W) != 0,
203 .execute = (flags & std.elf.PF_X) != 0,
204 };
205 }
206
207 fn relativeOffset(span: ImageSpan, address: u64) model.Error!usize {
208 if (address < span.start or address > span.end) return error.InvalidRange;
209 return std.math.cast(usize, address - span.start) orelse error.InvalidRange;
210 }
211
212 fn alignDown(value: u64, alignment: u64) u64 {
213 return value & ~(alignment - 1);
214 }
215
216 fn alignForwardU64(value: u64, alignment: u64) model.Error!u64 {
217 const mask = alignment - 1;
218 if (value > std.math.maxInt(u64) - mask) return error.InvalidRange;
219 return (value + mask) & ~mask;
220 }
221
222 fn checkedAddU64(a: u64, b: u64) model.Error!u64 {
223 return std.math.add(u64, a, b) catch error.InvalidRange;
224 }
225
226 test "load executable maps entry function" {
227 const allocator = std.testing.allocator;
228 const text = [_]u8{ 0xb8, 41, 0, 0, 0, 0xc3 };
229 const object = try elf_object.build(allocator, .{
230 .sections = &.{elf_object.Section.progbits(".text", &text, std.elf.SHF_EXECINSTR, 16)},
231 .symbols = &.{
232 elf_object.Symbol.section(1),
233 elf_object.Symbol.function("_start", 1, 0, text.len),
234 },
235 });
236 defer allocator.free(object);
237
238 const loaded_result = loadExecutable(allocator, &.{.{ .name = "entry.o", .bytes = object }}, .{ .incremental_mode = .off });
239 if (builtin.cpu.arch != .x86_64) {
240 try std.testing.expectError(error.UnsupportedArchitecture, loaded_result);
241 return;
242 }
243 var loaded = try loaded_result;
244 defer loaded.deinit();
245
246 const Entry = *const fn () callconv(.c) i32;
247 const entry = loaded.entry(Entry);
248 try std.testing.expectEqual(@as(i32, 41), entry());
249 }
250
251 test "load executable runs linked call relocation" {
252 const allocator = std.testing.allocator;
253
254 const caller_text = [_]u8{ 0xe8, 0, 0, 0, 0, 0xc3 };
255 const caller_object = try elf_object.build(allocator, .{
256 .sections = &.{
257 elf_object.Section.progbits(".text", &caller_text, std.elf.SHF_EXECINSTR, 16),
258 },
259 .symbols = &.{
260 elf_object.Symbol.section(1),
261 elf_object.Symbol.function("_start", 1, 0, caller_text.len),
262 elf_object.Symbol.undefinedFunction("callee"),
263 },
264 .relocations = &.{elf_object.Relocation.x86_64(1, 1, 3, .PLT32, -4)},
265 });
266 defer allocator.free(caller_object);
267
268 const callee_text = [_]u8{ 0xb8, 42, 0, 0, 0, 0xc3 };
269 const callee_object = try elf_object.build(allocator, .{
270 .sections = &.{
271 elf_object.Section.progbits(".text", &callee_text, std.elf.SHF_EXECINSTR, 16),
272 },
273 .symbols = &.{
274 elf_object.Symbol.section(1),
275 elf_object.Symbol.function("callee", 1, 0, callee_text.len),
276 },
277 });
278 defer allocator.free(callee_object);
279
280 const loaded_result = loadExecutable(allocator, &.{
281 .{ .name = "caller.o", .bytes = caller_object },
282 .{ .name = "callee.o", .bytes = callee_object },
283 }, .{ .incremental_mode = .off });
284 if (builtin.cpu.arch != .x86_64) {
285 try std.testing.expectError(error.UnsupportedArchitecture, loaded_result);
286 return;
287 }
288 var loaded = try loaded_result;
289 defer loaded.deinit();
290
291 const Entry = *const fn () callconv(.c) i32;
292 const entry = loaded.entry(Entry);
293 try std.testing.expectEqual(@as(i32, 42), entry());
294 }