lib/tldr/src/formats/elf/metadata.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const root = @import("../../root.zig");
3 const format = @import("format.zig");
4 const build_id_note = @import("note.zig");
5
6 const incremental = root.incremental;
7 const model = root.model;
8 const ehdr_size = format.ehdr_size;
9 const phdr_size = format.phdr_size;
10 const shdr_size = format.shdr_size;
11 const SectionHeader = format.SectionHeader;
12 const requireRange = format.requireRange;
13 const sectionBytes = format.sectionBytes;
14 const stringFromTable = format.stringFromTable;
15 const readSectionHeader = format.readSectionHeader;
16 const readU16 = format.readU16;
17 const readU32 = format.readU32;
18 const readU64 = format.readU64;
19
20 pub const ProgramHeader = struct {
21 flags: u32,
22 file_offset: u64,
23 virtual_address: u64,
24 file_size: u64,
25 memory_size: u64,
26 };
27
28 const Layout = struct {
29 start: u64,
30 section_header_offset: u64,
31 section_count: u16,
32 string_section_index: u16,
33 };
34
35 pub fn patchIncrementalMetadata(image: []u8, candidate_image: []const u8) model.Error!void {
36 if (image.len != candidate_image.len) return error.InvalidRange;
37 const image_metadata = try metadataLayout(image);
38 const candidate_metadata = try metadataLayout(candidate_image);
39 if (image_metadata.start != candidate_metadata.start) return error.InvalidElfHeader;
40 if (image_metadata.section_header_offset != candidate_metadata.section_header_offset) return error.InvalidElfHeader;
41 if (image_metadata.section_count != candidate_metadata.section_count) return error.InvalidElfHeader;
42 if (image_metadata.string_section_index != candidate_metadata.string_section_index) return error.InvalidElfHeader;
43 const start: usize = @intCast(image_metadata.start);
44 @memcpy(image[start..], candidate_image[start..]);
45 try patchGeneratedBuildIdNote(image, candidate_image);
46 }
47
48 pub fn finishIncrementalMetadata(image: []u8, options: model.LinkOptions) model.Error!void {
49 try finishGeneratedBuildIdNote(image, options.build_id);
50 }
51
52 pub const MetadataRange = struct {
53 offset: usize,
54 len: usize,
55 };
56
57 pub fn buildIdNoteRange(image: []const u8) model.Error!?MetadataRange {
58 const note = linkedSectionByName(image, build_id_note.section_name) catch |err| switch (err) {
59 error.MissingSection => return null,
60 else => return err,
61 };
62 return .{
63 .offset = std.math.cast(usize, note.offset) orelse return error.InvalidRange,
64 .len = std.math.cast(usize, note.size) orelse return error.InvalidRange,
65 };
66 }
67
68 pub fn patchIncrementalMetadataRange(image: []const u8) model.Error!MetadataRange {
69 const image_metadata = try metadataLayout(image);
70 const start = std.math.cast(usize, image_metadata.start) orelse return error.InvalidRange;
71 return .{ .offset = start, .len = image.len - start };
72 }
73
74 fn patchGeneratedBuildIdNote(image: []u8, candidate_image: []const u8) model.Error!void {
75 const note = linkedSectionByName(image, build_id_note.section_name) catch |err| switch (err) {
76 error.MissingSection => return,
77 else => return err,
78 };
79 const candidate_note = try linkedSectionByName(candidate_image, build_id_note.section_name);
80 try build_id_note.patchGenerated(image, candidate_image, note, candidate_note);
81 }
82
83 fn finishGeneratedBuildIdNote(image: []u8, mode: model.BuildIdMode) model.Error!void {
84 if (mode == .none) return;
85 const note = try linkedSectionByName(image, build_id_note.section_name);
86 try build_id_note.finishBuildIdSection(image, note, mode);
87 }
88
89 pub fn manifestSection(manifest: incremental.Manifest, name: []const u8) ?incremental.SectionRecord {
90 for (manifest.sections) |section| {
91 if (std.mem.eql(u8, manifest.string(section.name_id), name)) return section;
92 }
93 return null;
94 }
95
96 pub fn manifestContribution(
97 manifest: incremental.Manifest,
98 input_name: []const u8,
99 kind: incremental.ContributionKind,
100 name: []const u8,
101 ordinal: u32,
102 ) ?incremental.ContributionRecord {
103 for (manifest.contributions) |contribution| {
104 if (!std.mem.eql(u8, manifest.string(contribution.input_name_id), input_name)) continue;
105 if (contribution.kind != kind) continue;
106 if (contribution.ordinal != ordinal) continue;
107 if (std.mem.eql(u8, manifest.string(contribution.name_id), name)) return contribution;
108 }
109 return null;
110 }
111
112 pub fn manifestDiscardedContribution(
113 manifest: incremental.Manifest,
114 input_name: []const u8,
115 name: []const u8,
116 ordinal: u32,
117 ) ?incremental.DiscardedContributionRecord {
118 for (manifest.discarded_contributions) |contribution| {
119 if (!std.mem.eql(u8, manifest.string(contribution.input_name_id), input_name)) continue;
120 if (contribution.ordinal != ordinal) continue;
121 if (std.mem.eql(u8, manifest.string(contribution.name_id), name)) return contribution;
122 }
123 return null;
124 }
125
126 fn metadataLayout(image: []const u8) model.Error!Layout {
127 if (image.len < ehdr_size) return error.InvalidElfHeader;
128 if (!std.mem.eql(u8, image[0..4], std.elf.MAGIC)) return error.InvalidElfHeader;
129 const shoff = readU64(image, 40);
130 const shentsize = readU16(image, 58);
131 const shnum = readU16(image, 60);
132 const shstrndx = readU16(image, 62);
133 if (shentsize != shdr_size or shnum == 0 or shstrndx >= shnum) return error.InvalidElfHeader;
134 try requireRange(image, shoff, @as(u64, shnum) * shdr_size);
135
136 const shstrtab_header = readSectionHeader(image, shoff + @as(u64, shstrndx) * shdr_size);
137 const shstrtab = try sectionBytes(image, shstrtab_header);
138 var start = shoff;
139 var found = false;
140 for (0..shnum) |index| {
141 const section = readSectionHeader(image, shoff + @as(u64, @intCast(index)) * shdr_size);
142 const section_name = try stringFromTable(shstrtab, section.name_offset);
143 if (!std.mem.eql(u8, section_name, ".symtab") and
144 !std.mem.eql(u8, section_name, ".strtab") and
145 !std.mem.eql(u8, section_name, ".shstrtab"))
146 {
147 continue;
148 }
149 start = @min(start, section.offset);
150 found = true;
151 }
152 if (!found) return error.MissingSection;
153 return .{
154 .start = start,
155 .section_header_offset = shoff,
156 .section_count = shnum,
157 .string_section_index = shstrndx,
158 };
159 }
160
161 pub fn linkedSectionByName(image: []const u8, name: []const u8) model.Error!SectionHeader {
162 const shoff = readU64(image, 40);
163 const shnum = readU16(image, 60);
164 const shstrndx = readU16(image, 62);
165 if (shstrndx >= shnum) return error.InvalidElfHeader;
166 try requireRange(image, shoff, @as(u64, shnum) * shdr_size);
167
168 const shstrtab_header = readSectionHeader(image, shoff + @as(u64, shstrndx) * shdr_size);
169 const shstrtab = try sectionBytes(image, shstrtab_header);
170 for (0..shnum) |index| {
171 const section = readSectionHeader(image, shoff + @as(u64, @intCast(index)) * shdr_size);
172 const section_name = try stringFromTable(shstrtab, section.name_offset);
173 if (std.mem.eql(u8, section_name, name)) return section;
174 }
175 return error.MissingSection;
176 }
177
178 pub fn loadProgramHeaderByAddress(image: []const u8, virtual_address: u64) model.Error!ProgramHeader {
179 return loadProgramHeaderByAddressAndType(image, virtual_address, std.elf.PT_LOAD);
180 }
181
182 pub fn programHeaderByType(image: []const u8, program_type: u32) model.Error!ProgramHeader {
183 return loadProgramHeaderByAddressAndType(image, null, program_type);
184 }
185
186 fn loadProgramHeaderByAddressAndType(image: []const u8, virtual_address: ?u64, program_type: u32) model.Error!ProgramHeader {
187 const phoff = readU64(image, 32);
188 const phentsize = readU16(image, 54);
189 const phnum = readU16(image, 56);
190 if (phentsize != phdr_size) return error.InvalidElfHeader;
191 try requireRange(image, phoff, @as(u64, phnum) * phdr_size);
192
193 for (0..phnum) |index| {
194 const offset = phoff + @as(u64, @intCast(index)) * phdr_size;
195 if (readU32(image, @intCast(offset)) != program_type) continue;
196 const header_virtual_address = readU64(image, @intCast(offset + 16));
197 const memory_size = readU64(image, @intCast(offset + 40));
198 if (virtual_address) |address| {
199 if (address < header_virtual_address) continue;
200 if (address - header_virtual_address >= memory_size) continue;
201 }
202 return .{
203 .flags = readU32(image, @intCast(offset + 4)),
204 .file_offset = readU64(image, @intCast(offset + 8)),
205 .virtual_address = header_virtual_address,
206 .file_size = readU64(image, @intCast(offset + 32)),
207 .memory_size = memory_size,
208 };
209 }
210 return error.MissingSection;
211 }