lib/tldr/src/formats/elf/view.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const root = @import("../../root.zig");
3 const format = @import("format.zig");
4
5 const model = root.model;
6 const ehdr_size = format.ehdr_size;
7 const shdr_size = format.shdr_size;
8 const sym_size = format.sym_size;
9 const Header = format.Header;
10 const SectionHeader = format.SectionHeader;
11 const SymbolRecord = format.SymbolRecord;
12
13 /// A section header found by name, with the file index of that header, returned
14 /// by `find` so the caller gets the header and the index that names it.
15 pub const Found = struct {
16 index: u16,
17 header: SectionHeader,
18 };
19
20 /// A view (a checked read of one ELF64 little-endian file) held in borrowed
21 /// bytes, so tests read back objects and executables through it by section name
22 /// or index. `parse` validates the file header, where the section table lies,
23 /// and the section name table up front. Each read after that returns a slice of
24 /// `bytes` checked against its bounds and allocates nothing. The caller keeps
25 /// `bytes` alive and unchanged while it uses the view.
26 pub const View = struct {
27 bytes: []const u8,
28 header: Header,
29 names: []const u8,
30
31 /// Validates the file header and section table of `bytes` and returns a
32 /// view (a checked read of one ELF64 little-endian file) that borrows them.
33 /// Because every read goes through that table, a file with no section table
34 /// returns `error.MissingSection` here, so no later read checks for it
35 /// again. The call returns `error.InvalidElfHeader` when the section name
36 /// table index is past the table, and `error.InvalidRange` when the table
37 /// or the name table lies outside `bytes`.
38 pub fn parse(bytes: []const u8) model.Error!View {
39 const header = try format.readHeader(bytes);
40 if (header.shnum == 0) return error.MissingSection;
41 if (header.shstrndx >= header.shnum) return error.InvalidElfHeader;
42 try format.requireRange(bytes, header.shoff, @as(u64, header.shnum) * shdr_size);
43
44 const name_table_offset = header.shoff + @as(u64, header.shstrndx) * shdr_size;
45 const name_table = format.readSectionHeader(bytes, name_table_offset);
46 return .{
47 .bytes = bytes,
48 .header = header,
49 .names = try format.sectionBytes(bytes, name_table),
50 };
51 }
52
53 pub fn sectionCount(self: View) u16 {
54 return self.header.shnum;
55 }
56
57 /// Reads the section header at `index`, for example the string table a
58 /// symbol table links to. `parse` already proved the whole table lies
59 /// within the file, so the one requirement left is `index` below the
60 /// section count, which a debug assertion checks.
61 pub fn section(self: View, index: u16) SectionHeader {
62 std.debug.assert(index < self.header.shnum);
63 const offset = self.header.shoff + @as(u64, index) * shdr_size;
64 return format.readSectionHeader(self.bytes, offset);
65 }
66
67 pub fn sectionName(self: View, header: SectionHeader) model.Error![]const u8 {
68 return format.stringFromTable(self.names, header.name_offset);
69 }
70
71 /// Returns the first section named `name` with its index, or null, so tests
72 /// can look sections up by name. The scan runs in file order, so when two
73 /// sections share a name the lower index wins. The call returns
74 /// `error.InvalidStringTable` when a section's name starts outside the
75 /// section name table or runs to its end with no terminating zero byte.
76 pub fn find(self: View, name: []const u8) model.Error!?Found {
77 var index: u16 = 0;
78 while (index < self.header.shnum) : (index += 1) {
79 const header = self.section(index);
80 if (std.mem.eql(u8, try self.sectionName(header), name)) {
81 return .{ .index = index, .header = header };
82 }
83 }
84 return null;
85 }
86
87 /// Returns the file bytes of a section as a slice of `bytes`, so tests can
88 /// compare a section's contents with what they wrote. The call returns an
89 /// empty slice for a NOBITS section, because it has no bytes in the file.
90 /// The call returns `error.InvalidRange` when the section extends past the
91 /// file.
92 pub fn sectionBytes(self: View, header: SectionHeader) model.Error![]const u8 {
93 return format.sectionBytes(self.bytes, header);
94 }
95
96 pub fn symbolCount(self: View, symtab: SectionHeader) model.Error!usize {
97 return (try self.sectionBytes(symtab)).len / sym_size;
98 }
99
100 pub fn symbol(self: View, symtab: SectionHeader, index: usize) model.Error!SymbolRecord {
101 const table = try self.sectionBytes(symtab);
102 if (index >= table.len / sym_size) return error.InvalidRange;
103 return format.readSymbolRecord(table[index * sym_size ..][0..sym_size]);
104 }
105
106 pub fn string(self: View, strtab: SectionHeader, offset: u32) model.Error![]const u8 {
107 return format.stringFromTable(try self.sectionBytes(strtab), offset);
108 }
109 };
110
111 const witness_text = "\x55\x48\x89\xe5";
112 const witness_strtab = "\x00main\x00";
113 const witness_shstrtab = "\x00.text\x00.symtab\x00.strtab\x00.shstrtab\x00";
114 const witness_text_offset = ehdr_size;
115 const witness_symtab_offset = witness_text_offset + 8;
116 const witness_symbol_count = 2;
117 const witness_symtab_size = witness_symbol_count * sym_size;
118 const witness_strtab_offset = witness_symtab_offset + witness_symtab_size;
119 const witness_shstrtab_offset = witness_strtab_offset + witness_strtab.len;
120 const witness_shoff = 160;
121 const witness_shnum = 5;
122 const witness_size = witness_shoff + witness_shnum * shdr_size;
123
124 /// Writes a minimal relocatable object using the package's own header, section
125 /// and symbol record writers, so each test of a view (a borrowed, checked read
126 /// of an ELF64 file) starts from the object it writes. The object comes from
127 /// the same writers whose output the view reads, so a failing view test means
128 /// the writers and the view disagree about the format.
129 fn writeWitness(buffer: *[witness_size]u8) void {
130 @memset(buffer, 0);
131 (Header{
132 .shoff = witness_shoff,
133 .shnum = witness_shnum,
134 .shstrndx = 4,
135 }).write(buffer);
136 @memcpy(buffer[witness_text_offset..][0..witness_text.len], witness_text);
137 (SymbolRecord{}).write(buffer, witness_symtab_offset);
138 (SymbolRecord{
139 .name_offset = 1,
140 .info = format.elfSymbolInfo(std.elf.STB_GLOBAL, std.elf.STT_FUNC),
141 .section_index = 1,
142 .size = witness_text.len,
143 }).write(buffer, witness_symtab_offset + sym_size);
144 @memcpy(buffer[witness_strtab_offset..][0..witness_strtab.len], witness_strtab);
145 @memcpy(buffer[witness_shstrtab_offset..][0..witness_shstrtab.len], witness_shstrtab);
146
147 (SectionHeader{}).write(buffer, witness_shoff);
148 (SectionHeader{
149 .name_offset = 1,
150 .section_type = std.elf.SHT_PROGBITS,
151 .flags = std.elf.SHF_ALLOC | std.elf.SHF_EXECINSTR,
152 .offset = witness_text_offset,
153 .size = witness_text.len,
154 .alignment = 1,
155 }).write(buffer, witness_shoff + shdr_size);
156 (SectionHeader{
157 .name_offset = 7,
158 .section_type = std.elf.SHT_SYMTAB,
159 .offset = witness_symtab_offset,
160 .size = witness_symtab_size,
161 .link = 3,
162 .info = 1,
163 .alignment = 8,
164 .entry_size = sym_size,
165 }).write(buffer, witness_shoff + 2 * shdr_size);
166 (SectionHeader{
167 .name_offset = 15,
168 .section_type = std.elf.SHT_STRTAB,
169 .offset = witness_strtab_offset,
170 .size = witness_strtab.len,
171 .alignment = 1,
172 }).write(buffer, witness_shoff + 3 * shdr_size);
173 (SectionHeader{
174 .name_offset = 23,
175 .section_type = std.elf.SHT_STRTAB,
176 .offset = witness_shstrtab_offset,
177 .size = witness_shstrtab.len,
178 .alignment = 1,
179 }).write(buffer, witness_shoff + 4 * shdr_size);
180 }
181
182 test "ELF view reads back the records written into an object" {
183 var buffer: [witness_size]u8 = undefined;
184 writeWitness(&buffer);
185 const view = try View.parse(&buffer);
186
187 try std.testing.expectEqual(@as(u16, witness_shnum), view.sectionCount());
188 try std.testing.expectEqual(std.elf.ET.REL, view.header.type);
189 try std.testing.expectEqualStrings("", try view.sectionName(view.section(0)));
190
191 const text = (try view.find(".text")).?;
192 try std.testing.expectEqual(@as(u16, 1), text.index);
193 try std.testing.expectEqual(@as(u32, std.elf.SHT_PROGBITS), text.header.section_type);
194 try std.testing.expectEqualStrings(witness_text, try view.sectionBytes(text.header));
195 try std.testing.expectEqualStrings(".text", try view.sectionName(text.header));
196
197 const symtab = (try view.find(".symtab")).?;
198 const strtab = view.section(@intCast(symtab.header.link));
199 const counted = try view.symbolCount(symtab.header);
200 try std.testing.expectEqual(@as(usize, witness_symbol_count), counted);
201
202 const null_symbol = try view.symbol(symtab.header, 0);
203 try std.testing.expectEqual(SymbolRecord{}, null_symbol);
204 try std.testing.expectEqualStrings("", try view.string(strtab, null_symbol.name_offset));
205
206 const main_symbol = try view.symbol(symtab.header, 1);
207 try std.testing.expectEqual(@as(u16, 1), main_symbol.section_index);
208 try std.testing.expectEqual(@as(u64, witness_text.len), main_symbol.size);
209 try std.testing.expectEqualStrings("main", try view.string(strtab, main_symbol.name_offset));
210
211 try std.testing.expect((try view.find(".rodata")) == null);
212 }
213
214 test "ELF view borrows nothing from a NOBITS section" {
215 var buffer: [witness_size]u8 = undefined;
216 writeWitness(&buffer);
217 const view = try View.parse(&buffer);
218
219 const reserved = SectionHeader{
220 .section_type = std.elf.SHT_NOBITS,
221 .offset = witness_size * 4,
222 .size = 4096,
223 };
224 try std.testing.expectEqual(@as(usize, 0), (try view.sectionBytes(reserved)).len);
225 }
226
227 test "ELF view refuses reads the file cannot satisfy" {
228 var buffer: [witness_size]u8 = undefined;
229 writeWitness(&buffer);
230
231 try std.testing.expectError(error.InvalidRange, View.parse(buffer[0 .. witness_size - 1]));
232
233 const view = try View.parse(&buffer);
234 const symtab = (try view.find(".symtab")).?.header;
235 const strtab = view.section(3);
236 try std.testing.expectError(error.InvalidRange, view.symbol(symtab, witness_symbol_count));
237 try std.testing.expectError(error.InvalidStringTable, view.string(strtab, witness_strtab.len));
238
239 writeWitness(&buffer);
240 (Header{
241 .shoff = witness_shoff,
242 .shnum = witness_shnum,
243 .shstrndx = witness_shnum,
244 }).write(&buffer);
245 try std.testing.expectError(error.InvalidElfHeader, View.parse(&buffer));
246
247 writeWitness(&buffer);
248 (Header{ .shoff = witness_shoff }).write(&buffer);
249 try std.testing.expectError(error.MissingSection, View.parse(&buffer));
250 }