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 }