| 1 | const std = @import("std"); |
| 2 | const Allocator = std.mem.Allocator; |
| 3 | const Target = std.Target; |
| 4 | const Object = @import("../Object.zig"); |
| 5 | |
| 6 | const Section = struct { |
| 7 | data: std.ArrayList(u8) = .empty, |
| 8 | relocations: std.ArrayList(Relocation) = .empty, |
| 9 | flags: u64, |
| 10 | type: u32, |
| 11 | index: u16 = undefined, |
| 12 | }; |
| 13 | |
| 14 | const Symbol = struct { |
| 15 | section: ?*Section, |
| 16 | size: u64, |
| 17 | offset: u64, |
| 18 | index: u16 = undefined, |
| 19 | info: u8, |
| 20 | }; |
| 21 | |
| 22 | const Relocation = struct { |
| 23 | symbol: *Symbol, |
| 24 | addend: i64, |
| 25 | offset: u48, |
| 26 | type: u8, |
| 27 | }; |
| 28 | |
| 29 | const additional_sections = 3; // null section, strtab, symtab |
| 30 | const strtab_index = 1; |
| 31 | const symtab_index = 2; |
| 32 | const strtab_default = "\x00.strtab\x00.symtab\x00"; |
| 33 | const strtab_name = 1; |
| 34 | const symtab_name = "\x00.strtab\x00".len; |
| 35 | |
| 36 | const Elf = @This(); |
| 37 | |
| 38 | obj: Object, |
| 39 | /// The keys are owned by the Codegen.tree |
| 40 | sections: std.StringHashMapUnmanaged(*Section) = .empty, |
| 41 | local_symbols: std.StringHashMapUnmanaged(*Symbol) = .empty, |
| 42 | global_symbols: std.StringHashMapUnmanaged(*Symbol) = .empty, |
| 43 | unnamed_symbol_mangle: u32 = 0, |
| 44 | strtab_len: u64 = strtab_default.len, |
| 45 | arena: std.heap.ArenaAllocator, |
| 46 | |
| 47 | pub fn create(gpa: Allocator, target: Target) !*Object { |
| 48 | const elf = try gpa.create(Elf); |
| 49 | elf.* = .{ |
| 50 | .obj = .{ .format = .elf, .target = target }, |
| 51 | .arena = std.heap.ArenaAllocator.init(gpa), |
| 52 | }; |
| 53 | return &elf.obj; |
| 54 | } |
| 55 | |
| 56 | pub fn deinit(elf: *Elf) void { |
| 57 | const gpa = elf.arena.child_allocator; |
| 58 | { |
| 59 | var it = elf.sections.valueIterator(); |
| 60 | while (it.next()) |sect| { |
| 61 | sect.*.data.deinit(gpa); |
| 62 | sect.*.relocations.deinit(gpa); |
| 63 | } |
| 64 | } |
| 65 | elf.sections.deinit(gpa); |
| 66 | elf.local_symbols.deinit(gpa); |
| 67 | elf.global_symbols.deinit(gpa); |
| 68 | elf.arena.deinit(); |
| 69 | gpa.destroy(elf); |
| 70 | } |
| 71 | |
| 72 | fn sectionString(sec: Object.Section) []const u8 { |
| 73 | return switch (sec) { |
| 74 | .undefined => unreachable, |
| 75 | .data => "data", |
| 76 | .read_only_data => "rodata", |
| 77 | .func => "text", |
| 78 | .strings => "rodata.str", |
| 79 | .custom => |name| name, |
| 80 | }; |
| 81 | } |
| 82 | |
| 83 | pub fn getSection(elf: *Elf, section_kind: Object.Section) !*std.ArrayList(u8) { |
| 84 | const section_name = sectionString(section_kind); |
| 85 | const section = elf.sections.get(section_name) orelse blk: { |
| 86 | const section = try elf.arena.allocator().create(Section); |
| 87 | section.* = .{ |
| 88 | .data = std.ArrayList(u8).init(elf.arena.child_allocator), |
| 89 | .type = std.elf.SHT_PROGBITS, |
| 90 | .flags = switch (section_kind) { |
| 91 | .func, .custom => std.elf.SHF_ALLOC + std.elf.SHF_EXECINSTR, |
| 92 | .strings => std.elf.SHF_ALLOC + std.elf.SHF_MERGE + std.elf.SHF_STRINGS, |
| 93 | .read_only_data => std.elf.SHF_ALLOC, |
| 94 | .data => std.elf.SHF_ALLOC + std.elf.SHF_WRITE, |
| 95 | .undefined => unreachable, |
| 96 | }, |
| 97 | }; |
| 98 | try elf.sections.putNoClobber(elf.arena.child_allocator, section_name, section); |
| 99 | elf.strtab_len += section_name.len + ".\x00".len; |
| 100 | break :blk section; |
| 101 | }; |
| 102 | return &section.data; |
| 103 | } |
| 104 | |
| 105 | pub fn declareSymbol( |
| 106 | elf: *Elf, |
| 107 | section_kind: Object.Section, |
| 108 | maybe_name: ?[]const u8, |
| 109 | linkage: std.builtin.GlobalLinkage, |
| 110 | @"type": Object.SymbolType, |
| 111 | offset: u64, |
| 112 | size: u64, |
| 113 | ) ![]const u8 { |
| 114 | const section = blk: { |
| 115 | if (section_kind == .undefined) break :blk null; |
| 116 | const section_name = sectionString(section_kind); |
| 117 | break :blk elf.sections.get(section_name); |
| 118 | }; |
| 119 | const binding: u8 = switch (linkage) { |
| 120 | .Internal => std.elf.STB_LOCAL, |
| 121 | .Strong => std.elf.STB_GLOBAL, |
| 122 | .Weak => std.elf.STB_WEAK, |
| 123 | .LinkOnce => unreachable, |
| 124 | }; |
| 125 | const sym_type: u8 = switch (@"type") { |
| 126 | .func => std.elf.STT_FUNC, |
| 127 | .variable => std.elf.STT_OBJECT, |
| 128 | .external => std.elf.STT_NOTYPE, |
| 129 | }; |
| 130 | const name = if (maybe_name) |some| some else blk: { |
| 131 | defer elf.unnamed_symbol_mangle += 1; |
| 132 | break :blk try std.fmt.allocPrint(elf.arena.allocator(), ".L.{d}", .{elf.unnamed_symbol_mangle}); |
| 133 | }; |
| 134 | |
| 135 | const gop = if (linkage == .Internal) |
| 136 | try elf.local_symbols.getOrPut(elf.arena.child_allocator, name) |
| 137 | else |
| 138 | try elf.global_symbols.getOrPut(elf.arena.child_allocator, name); |
| 139 | |
| 140 | if (!gop.found_existing) { |
| 141 | gop.value_ptr.* = try elf.arena.allocator().create(Symbol); |
| 142 | elf.strtab_len += name.len + 1; // +1 for null byte |
| 143 | } |
| 144 | gop.value_ptr.*.* = .{ |
| 145 | .section = section, |
| 146 | .size = size, |
| 147 | .offset = offset, |
| 148 | .info = (binding << 4) + sym_type, |
| 149 | }; |
| 150 | return name; |
| 151 | } |
| 152 | |
| 153 | pub fn addRelocation(elf: *Elf, name: []const u8, section_kind: Object.Section, address: u64, addend: i64) !void { |
| 154 | const section_name = sectionString(section_kind); |
| 155 | const symbol = elf.local_symbols.get(name) orelse elf.global_symbols.get(name).?; // reference to undeclared symbol |
| 156 | const section = elf.sections.get(section_name).?; |
| 157 | if (section.relocations.items.len == 0) elf.strtab_len += ".rela".len; |
| 158 | |
| 159 | try section.relocations.append(elf.arena.child_allocator, .{ |
| 160 | .symbol = symbol, |
| 161 | .offset = @intCast(address), |
| 162 | .addend = addend, |
| 163 | .type = if (symbol.section == null) 4 else 2, // TODO |
| 164 | }); |
| 165 | } |
| 166 | |
| 167 | /// elf header |
| 168 | /// sections contents |
| 169 | /// symbols |
| 170 | /// relocations |
| 171 | /// strtab |
| 172 | /// section headers |
| 173 | pub fn finish(elf: *Elf, w: *std.Io.Writer) !void { |
| 174 | var num_sections: std.elf.Half = additional_sections; |
| 175 | var relocations_len: std.elf.Elf64_Off = 0; |
| 176 | var sections_len: std.elf.Elf64_Off = 0; |
| 177 | { |
| 178 | var it = elf.sections.valueIterator(); |
| 179 | while (it.next()) |sect| { |
| 180 | sections_len += sect.*.data.items.len; |
| 181 | relocations_len += sect.*.relocations.items.len * @sizeOf(std.elf.Elf64_Rela); |
| 182 | sect.*.index = num_sections; |
| 183 | num_sections += 1; |
| 184 | num_sections += @intFromBool(sect.*.relocations.items.len != 0); |
| 185 | } |
| 186 | } |
| 187 | const symtab_len = (elf.local_symbols.count() + elf.global_symbols.count() + 1) * @sizeOf(std.elf.Elf64_Sym); |
| 188 | |
| 189 | const symtab_offset = @sizeOf(std.elf.Elf64_Ehdr) + sections_len; |
| 190 | const symtab_offset_aligned = std.mem.alignForward(u64, symtab_offset, 8); |
| 191 | const rela_offset = symtab_offset_aligned + symtab_len; |
| 192 | const strtab_offset = rela_offset + relocations_len; |
| 193 | const sh_offset = strtab_offset + elf.strtab_len; |
| 194 | const sh_offset_aligned = std.mem.alignForward(u64, sh_offset, 16); |
| 195 | const endian = elf.obj.target.cpu.arch.endian(); |
| 196 | |
| 197 | const elf_header: std.elf.Elf64_Ehdr = .{ |
| 198 | .e_ident = .{ 0x7F, 'E', 'L', 'F', 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, |
| 199 | .e_type = std.elf.ET.REL, // we only produce relocatables |
| 200 | .e_machine = elf.obj.target.toElfMachine(), |
| 201 | .e_version = 1, |
| 202 | .e_entry = 0, // linker will handle this |
| 203 | .e_phoff = 0, // no program header |
| 204 | .e_shoff = sh_offset_aligned, // section headers offset |
| 205 | .e_flags = 0, // no flags |
| 206 | .e_ehsize = @sizeOf(std.elf.Elf64_Ehdr), |
| 207 | .e_phentsize = 0, // no program header |
| 208 | .e_phnum = 0, // no program header |
| 209 | .e_shentsize = @sizeOf(std.elf.Elf64_Shdr), |
| 210 | .e_shnum = num_sections, |
| 211 | .e_shstrndx = strtab_index, |
| 212 | }; |
| 213 | try w.writeStruct(elf_header, endian); |
| 214 | |
| 215 | // write contents of sections |
| 216 | { |
| 217 | var it = elf.sections.valueIterator(); |
| 218 | while (it.next()) |sect| try w.writeAll(sect.*.data.items); |
| 219 | } |
| 220 | |
| 221 | // pad to 8 bytes |
| 222 | try w.splatByteAll(0, @intCast(symtab_offset_aligned - symtab_offset)); |
| 223 | |
| 224 | var name_offset: u32 = strtab_default.len; |
| 225 | // write symbols |
| 226 | { |
| 227 | // first symbol must be null |
| 228 | try w.writeStruct(std.mem.zeroes(std.elf.Elf64_Sym), endian); |
| 229 | |
| 230 | var sym_index: u16 = 1; |
| 231 | var it = elf.local_symbols.iterator(); |
| 232 | while (it.next()) |entry| { |
| 233 | const sym = entry.value_ptr.*; |
| 234 | try w.writeStruct(std.elf.Elf64_Sym{ |
| 235 | .st_name = name_offset, |
| 236 | .st_info = sym.info, |
| 237 | .st_other = 0, |
| 238 | .st_shndx = if (sym.section) |some| some.index else 0, |
| 239 | .st_value = sym.offset, |
| 240 | .st_size = sym.size, |
| 241 | }, endian); |
| 242 | sym.index = sym_index; |
| 243 | sym_index += 1; |
| 244 | name_offset += @intCast(entry.key_ptr.len + 1); // +1 for null byte |
| 245 | } |
| 246 | it = elf.global_symbols.iterator(); |
| 247 | while (it.next()) |entry| { |
| 248 | const sym = entry.value_ptr.*; |
| 249 | try w.writeStruct(std.elf.Elf64_Sym{ |
| 250 | .st_name = name_offset, |
| 251 | .st_info = sym.info, |
| 252 | .st_other = 0, |
| 253 | .st_shndx = if (sym.section) |some| some.index else 0, |
| 254 | .st_value = sym.offset, |
| 255 | .st_size = sym.size, |
| 256 | }, endian); |
| 257 | sym.index = sym_index; |
| 258 | sym_index += 1; |
| 259 | name_offset += @intCast(entry.key_ptr.len + 1); // +1 for null byte |
| 260 | } |
| 261 | } |
| 262 | |
| 263 | // write relocations |
| 264 | { |
| 265 | var it = elf.sections.valueIterator(); |
| 266 | while (it.next()) |sect| { |
| 267 | for (sect.*.relocations.items) |rela| { |
| 268 | try w.writeStruct(std.elf.Elf64_Rela{ |
| 269 | .r_offset = rela.offset, |
| 270 | .r_addend = rela.addend, |
| 271 | .r_info = (@as(u64, rela.symbol.index) << 32) | rela.type, |
| 272 | }, endian); |
| 273 | } |
| 274 | } |
| 275 | } |
| 276 | |
| 277 | // write strtab |
| 278 | try w.writeAll(strtab_default); |
| 279 | { |
| 280 | var it = elf.local_symbols.keyIterator(); |
| 281 | while (it.next()) |key| try w.print("{s}\x00", .{key.*}); |
| 282 | it = elf.global_symbols.keyIterator(); |
| 283 | while (it.next()) |key| try w.print("{s}\x00", .{key.*}); |
| 284 | } |
| 285 | { |
| 286 | var it = elf.sections.iterator(); |
| 287 | while (it.next()) |entry| { |
| 288 | if (entry.value_ptr.*.relocations.items.len != 0) try w.writeAll(".rela"); |
| 289 | try w.print(".{s}\x00", .{entry.key_ptr.*}); |
| 290 | } |
| 291 | } |
| 292 | |
| 293 | // pad to 16 bytes |
| 294 | try w.splatByteAll(0, @intCast(sh_offset_aligned - sh_offset)); |
| 295 | // mandatory null header |
| 296 | try w.writeStruct(std.mem.zeroes(std.elf.Elf64_Shdr), endian); |
| 297 | |
| 298 | // write strtab section header |
| 299 | { |
| 300 | const sect_header: std.elf.Elf64_Shdr = .{ |
| 301 | .sh_name = strtab_name, |
| 302 | .sh_type = std.elf.SHT_STRTAB, |
| 303 | .sh_flags = 0, |
| 304 | .sh_addr = 0, |
| 305 | .sh_offset = strtab_offset, |
| 306 | .sh_size = elf.strtab_len, |
| 307 | .sh_link = 0, |
| 308 | .sh_info = 0, |
| 309 | .sh_addralign = 1, |
| 310 | .sh_entsize = 0, |
| 311 | }; |
| 312 | try w.writeStruct(sect_header, endian); |
| 313 | } |
| 314 | |
| 315 | // write symtab section header |
| 316 | { |
| 317 | const sect_header: std.elf.Elf64_Shdr = .{ |
| 318 | .sh_name = symtab_name, |
| 319 | .sh_type = std.elf.SHT_SYMTAB, |
| 320 | .sh_flags = 0, |
| 321 | .sh_addr = 0, |
| 322 | .sh_offset = symtab_offset_aligned, |
| 323 | .sh_size = symtab_len, |
| 324 | .sh_link = strtab_index, |
| 325 | .sh_info = elf.local_symbols.size + 1, |
| 326 | .sh_addralign = 8, |
| 327 | .sh_entsize = @sizeOf(std.elf.Elf64_Sym), |
| 328 | }; |
| 329 | try w.writeStruct(sect_header, endian); |
| 330 | } |
| 331 | |
| 332 | // remaining section headers |
| 333 | { |
| 334 | var sect_offset: u64 = @sizeOf(std.elf.Elf64_Ehdr); |
| 335 | var rela_sect_offset: u64 = rela_offset; |
| 336 | var it = elf.sections.iterator(); |
| 337 | while (it.next()) |entry| { |
| 338 | const sect = entry.value_ptr.*; |
| 339 | const rela_count = sect.relocations.items.len; |
| 340 | const rela_name_offset: u32 = if (rela_count != 0) @truncate(".rela".len) else 0; |
| 341 | try w.writeStruct(std.elf.Elf64_Shdr{ |
| 342 | .sh_name = rela_name_offset + name_offset, |
| 343 | .sh_type = sect.type, |
| 344 | .sh_flags = sect.flags, |
| 345 | .sh_addr = 0, |
| 346 | .sh_offset = sect_offset, |
| 347 | .sh_size = sect.data.items.len, |
| 348 | .sh_link = 0, |
| 349 | .sh_info = 0, |
| 350 | .sh_addralign = if (sect.flags & std.elf.SHF_EXECINSTR != 0) 16 else 1, |
| 351 | .sh_entsize = 0, |
| 352 | }, endian); |
| 353 | |
| 354 | if (rela_count != 0) { |
| 355 | const size = rela_count * @sizeOf(std.elf.Elf64_Rela); |
| 356 | try w.writeStruct(std.elf.Elf64_Shdr{ |
| 357 | .sh_name = name_offset, |
| 358 | .sh_type = std.elf.SHT_RELA, |
| 359 | .sh_flags = 0, |
| 360 | .sh_addr = 0, |
| 361 | .sh_offset = rela_sect_offset, |
| 362 | .sh_size = rela_count * @sizeOf(std.elf.Elf64_Rela), |
| 363 | .sh_link = symtab_index, |
| 364 | .sh_info = sect.index, |
| 365 | .sh_addralign = 8, |
| 366 | .sh_entsize = @sizeOf(std.elf.Elf64_Rela), |
| 367 | }, endian); |
| 368 | rela_sect_offset += size; |
| 369 | } |
| 370 | |
| 371 | sect_offset += sect.data.items.len; |
| 372 | name_offset += @as(u32, @intCast(entry.key_ptr.len + ".\x00".len)) + rela_name_offset; |
| 373 | } |
| 374 | } |
| 375 | try w.flush(); |
| 376 | } |