| 1 | // Copyright (c) 2026 Alexander Medvednikov. All rights reserved. |
| 2 | // Use of this source code is governed by an MIT license |
| 3 | // that can be found in the LICENSE file. |
| 4 | |
| 5 | module x64 |
| 6 | |
| 7 | import os |
| 8 | |
| 9 | // ELF64 Constants |
| 10 | const ei_mag0 = 0x7f |
| 11 | const ei_mag1 = 0x45 // 'E' |
| 12 | const ei_mag2 = 0x4c // 'L' |
| 13 | const ei_mag3 = 0x46 // 'F' |
| 14 | |
| 15 | const elfclass64 = 2 |
| 16 | const elfdata2lsb = 1 |
| 17 | const ev_current = 1 |
| 18 | const et_rel = 1 // Relocatable file |
| 19 | const et_exec = 2 // Executable file |
| 20 | const em_x86_64 = 62 |
| 21 | |
| 22 | const pt_load = 1 |
| 23 | |
| 24 | const pf_x = 0x1 |
| 25 | const pf_w = 0x2 |
| 26 | const pf_r = 0x4 |
| 27 | |
| 28 | const sht_progbits = 1 |
| 29 | const sht_symtab = 2 |
| 30 | const sht_strtab = 3 |
| 31 | const sht_rela = 4 |
| 32 | const sht_nobits = 8 |
| 33 | |
| 34 | const shf_write = 0x1 |
| 35 | const shf_alloc = 0x2 |
| 36 | const shf_execinstr = 0x4 |
| 37 | |
| 38 | // Relocation Types for x86_64 |
| 39 | const r_x86_64_64 = 1 |
| 40 | const r_x86_64_pc32 = 2 |
| 41 | const r_x86_64_plt32 = 4 |
| 42 | |
| 43 | pub struct ElfObject { |
| 44 | pub mut: |
| 45 | text_data []u8 |
| 46 | data_data []u8 |
| 47 | rodata []u8 |
| 48 | |
| 49 | // Symbol Table |
| 50 | symbols []ElfSymbol |
| 51 | str_table []u8 |
| 52 | |
| 53 | // Relocations |
| 54 | text_relocs []ElfRela |
| 55 | |
| 56 | // Section Names |
| 57 | shstr_table []u8 |
| 58 | } |
| 59 | |
| 60 | struct ElfSymbol { |
| 61 | pub mut: |
| 62 | name string |
| 63 | name_idx int |
| 64 | info u8 |
| 65 | shndx u16 |
| 66 | value u64 |
| 67 | size u64 |
| 68 | } |
| 69 | |
| 70 | struct ElfRela { |
| 71 | offset u64 |
| 72 | info u64 |
| 73 | addend i64 |
| 74 | } |
| 75 | |
| 76 | pub fn ElfObject.new() &ElfObject { |
| 77 | mut e := &ElfObject{ |
| 78 | str_table: [u8(0)] // Starts with null byte |
| 79 | shstr_table: [u8(0)] |
| 80 | } |
| 81 | // Add null symbol |
| 82 | e.symbols << ElfSymbol{ |
| 83 | name: '' |
| 84 | shndx: 0 |
| 85 | } |
| 86 | return e |
| 87 | } |
| 88 | |
| 89 | pub fn (mut e ElfObject) add_symbol(name string, value u64, is_func bool, shndx u16) int { |
| 90 | // STB_GLOBAL (1) << 4 | STT_FUNC (2) or STT_OBJECT (1) or STT_NOTYPE (0) |
| 91 | type_ := if is_func { u8(2) } else { u8(1) } // Func : Object |
| 92 | bind := u8(1) // Global |
| 93 | info := (bind << 4) | type_ |
| 94 | |
| 95 | for i := 1; i < e.symbols.len; i++ { |
| 96 | if e.symbols[i].name != name { |
| 97 | continue |
| 98 | } |
| 99 | mut s := &e.symbols[i] |
| 100 | s.info = info |
| 101 | s.shndx = shndx |
| 102 | s.value = value |
| 103 | return i |
| 104 | } |
| 105 | |
| 106 | idx := e.symbols.len |
| 107 | name_off := e.add_string(name) |
| 108 | |
| 109 | e.symbols << ElfSymbol{ |
| 110 | name: name |
| 111 | name_idx: name_off |
| 112 | info: info |
| 113 | shndx: shndx |
| 114 | value: value |
| 115 | } |
| 116 | return idx |
| 117 | } |
| 118 | |
| 119 | pub fn (mut e ElfObject) add_undefined(name string) int { |
| 120 | for i := 1; i < e.symbols.len; i++ { |
| 121 | if e.symbols[i].name == name { |
| 122 | return i |
| 123 | } |
| 124 | } |
| 125 | |
| 126 | idx := e.symbols.len |
| 127 | name_off := e.add_string(name) |
| 128 | // Bind Global (1), Type NoType (0) |
| 129 | info := u8(0x10) |
| 130 | |
| 131 | e.symbols << ElfSymbol{ |
| 132 | name: name |
| 133 | name_idx: name_off |
| 134 | info: info |
| 135 | shndx: 0 // Undefined |
| 136 | value: 0 |
| 137 | } |
| 138 | return idx |
| 139 | } |
| 140 | |
| 141 | pub fn (mut e ElfObject) add_text_reloc(offset u64, sym_idx int, type_ int, addend i64) { |
| 142 | // info = (sym_idx << 32) | type |
| 143 | info := (u64(sym_idx) << 32) | u64(type_) |
| 144 | e.text_relocs << ElfRela{ |
| 145 | offset: offset |
| 146 | info: info |
| 147 | addend: addend |
| 148 | } |
| 149 | } |
| 150 | |
| 151 | fn (mut e ElfObject) add_string(s string) int { |
| 152 | off := e.str_table.len |
| 153 | e.str_table << s.bytes() |
| 154 | e.str_table << 0 |
| 155 | return off |
| 156 | } |
| 157 | |
| 158 | fn (mut e ElfObject) add_sh_string(s string) int { |
| 159 | off := e.shstr_table.len |
| 160 | e.shstr_table << s.bytes() |
| 161 | e.shstr_table << 0 |
| 162 | return off |
| 163 | } |
| 164 | |
| 165 | pub fn (mut e ElfObject) write(path string) { |
| 166 | mut buf := []u8{} |
| 167 | |
| 168 | // --- 1. Prepare Sections --- |
| 169 | // Indices: |
| 170 | // 0: Null |
| 171 | // 1: .text |
| 172 | // 2: .data |
| 173 | // 3: .rodata (strings) |
| 174 | // 4: .symtab |
| 175 | // 5: .strtab |
| 176 | // 6: .rela.text |
| 177 | // 7: .shstrtab |
| 178 | |
| 179 | // Prepare section names in .shstrtab |
| 180 | off_text_name := e.add_sh_string('.text') |
| 181 | off_data_name := e.add_sh_string('.data') |
| 182 | off_rodata_name := e.add_sh_string('.rodata') |
| 183 | off_symtab_name := e.add_sh_string('.symtab') |
| 184 | off_strtab_name := e.add_sh_string('.strtab') |
| 185 | off_rela_text_name := e.add_sh_string('.rela.text') |
| 186 | off_shstrtab_name := e.add_sh_string('.shstrtab') |
| 187 | |
| 188 | // Calculate Offsets |
| 189 | ehdr_size := 64 |
| 190 | shdr_entry_size := 64 |
| 191 | num_sections := 8 |
| 192 | |
| 193 | mut current_offset := ehdr_size |
| 194 | |
| 195 | // .text (Align 16) |
| 196 | if current_offset % 16 != 0 { |
| 197 | current_offset += (16 - (current_offset % 16)) |
| 198 | } |
| 199 | off_text := current_offset |
| 200 | current_offset += e.text_data.len |
| 201 | |
| 202 | // .data (Align 8) |
| 203 | if current_offset % 8 != 0 { |
| 204 | current_offset += (8 - (current_offset % 8)) |
| 205 | } |
| 206 | off_data := current_offset |
| 207 | current_offset += e.data_data.len |
| 208 | |
| 209 | // .rodata (Align 4) |
| 210 | if current_offset % 4 != 0 { |
| 211 | current_offset += (4 - (current_offset % 4)) |
| 212 | } |
| 213 | off_rodata := current_offset |
| 214 | current_offset += e.rodata.len |
| 215 | |
| 216 | // .symtab (Align 8) |
| 217 | if current_offset % 8 != 0 { |
| 218 | current_offset += (8 - (current_offset % 8)) |
| 219 | } |
| 220 | off_symtab := current_offset |
| 221 | size_symtab := e.symbols.len * 24 |
| 222 | current_offset += size_symtab |
| 223 | |
| 224 | // .strtab (Align 1) |
| 225 | off_strtab := current_offset |
| 226 | current_offset += e.str_table.len |
| 227 | |
| 228 | // .rela.text (Align 8) |
| 229 | if current_offset % 8 != 0 { |
| 230 | current_offset += (8 - (current_offset % 8)) |
| 231 | } |
| 232 | off_rela_text := current_offset |
| 233 | size_rela_text := e.text_relocs.len * 24 |
| 234 | current_offset += size_rela_text |
| 235 | |
| 236 | // .shstrtab (Align 1) |
| 237 | off_shstrtab := current_offset |
| 238 | current_offset += e.shstr_table.len |
| 239 | |
| 240 | // Section Headers (Align 8) |
| 241 | if current_offset % 8 != 0 { |
| 242 | current_offset += (8 - (current_offset % 8)) |
| 243 | } |
| 244 | off_shdrs := current_offset |
| 245 | |
| 246 | // --- 2. Write ELF Header --- |
| 247 | buf << u8(ei_mag0) |
| 248 | buf << u8(ei_mag1) |
| 249 | buf << u8(ei_mag2) |
| 250 | buf << u8(ei_mag3) |
| 251 | buf << u8(elfclass64) |
| 252 | buf << u8(elfdata2lsb) |
| 253 | buf << u8(ev_current) |
| 254 | buf << u8(0) // ABI System V |
| 255 | buf << u8(0) // ABI Version |
| 256 | for _ in 0 .. 7 { |
| 257 | buf << 0 |
| 258 | } // Pad |
| 259 | |
| 260 | write_u16_le(mut buf, et_rel) |
| 261 | write_u16_le(mut buf, em_x86_64) |
| 262 | write_u32_le(mut buf, ev_current) |
| 263 | write_u64_le(mut buf, 0) // Entry |
| 264 | write_u64_le(mut buf, 0) // Phdr off |
| 265 | write_u64_le(mut buf, u64(off_shdrs)) |
| 266 | write_u32_le(mut buf, 0) // Flags |
| 267 | write_u16_le(mut buf, u16(ehdr_size)) |
| 268 | write_u16_le(mut buf, 0) // Phdr entry size |
| 269 | write_u16_le(mut buf, 0) // Phdr num |
| 270 | write_u16_le(mut buf, u16(shdr_entry_size)) |
| 271 | write_u16_le(mut buf, u16(num_sections)) |
| 272 | write_u16_le(mut buf, 7) // Shstrndx |
| 273 | |
| 274 | // --- 3. Write Data with Padding --- |
| 275 | |
| 276 | // Pad to Text |
| 277 | for buf.len < off_text { |
| 278 | buf << 0 |
| 279 | } |
| 280 | buf << e.text_data |
| 281 | |
| 282 | // Pad to Data |
| 283 | for buf.len < off_data { |
| 284 | buf << 0 |
| 285 | } |
| 286 | buf << e.data_data |
| 287 | |
| 288 | // Pad to Rodata |
| 289 | for buf.len < off_rodata { |
| 290 | buf << 0 |
| 291 | } |
| 292 | buf << e.rodata |
| 293 | |
| 294 | // Pad to Symtab |
| 295 | for buf.len < off_symtab { |
| 296 | buf << 0 |
| 297 | } |
| 298 | for sym in e.symbols { |
| 299 | write_u32_le(mut buf, u32(sym.name_idx)) |
| 300 | buf << sym.info |
| 301 | buf << 0 // other |
| 302 | write_u16_le(mut buf, sym.shndx) |
| 303 | write_u64_le(mut buf, sym.value) |
| 304 | write_u64_le(mut buf, sym.size) |
| 305 | } |
| 306 | |
| 307 | // Strtab |
| 308 | for buf.len < off_strtab { |
| 309 | buf << 0 |
| 310 | } |
| 311 | buf << e.str_table |
| 312 | |
| 313 | // Rela Text |
| 314 | for buf.len < off_rela_text { |
| 315 | buf << 0 |
| 316 | } |
| 317 | for r in e.text_relocs { |
| 318 | write_u64_le(mut buf, r.offset) |
| 319 | write_u64_le(mut buf, r.info) |
| 320 | write_u64_le(mut buf, u64(r.addend)) |
| 321 | } |
| 322 | |
| 323 | // Shstrtab |
| 324 | for buf.len < off_shstrtab { |
| 325 | buf << 0 |
| 326 | } |
| 327 | buf << e.shstr_table |
| 328 | |
| 329 | // --- 4. Write Section Headers --- |
| 330 | for buf.len < off_shdrs { |
| 331 | buf << 0 |
| 332 | } |
| 333 | |
| 334 | // 0: Null |
| 335 | write_shdr(mut buf, 0, 0, 0, 0, 0, 0, 0, 0, 0) |
| 336 | |
| 337 | // 1: .text |
| 338 | write_shdr(mut buf, u32(off_text_name), sht_progbits, shf_alloc | shf_execinstr, u64(off_text), |
| 339 | u64(e.text_data.len), 0, 0, 16, 0) |
| 340 | |
| 341 | // 2: .data |
| 342 | write_shdr(mut buf, u32(off_data_name), sht_progbits, shf_alloc | shf_write, u64(off_data), |
| 343 | u64(e.data_data.len), 0, 0, 8, 0) |
| 344 | |
| 345 | // 3: .rodata |
| 346 | write_shdr(mut buf, u32(off_rodata_name), sht_progbits, shf_alloc, u64(off_rodata), |
| 347 | u64(e.rodata.len), 0, 0, 4, 0) |
| 348 | |
| 349 | // 4: .symtab (EntSize = 24) |
| 350 | mut first_global := 1 |
| 351 | for i, s in e.symbols { |
| 352 | if (s.info >> 4) == 1 { // STB_GLOBAL |
| 353 | first_global = i |
| 354 | break |
| 355 | } |
| 356 | } |
| 357 | write_shdr(mut buf, u32(off_symtab_name), sht_symtab, 0, u64(off_symtab), u64(size_symtab), 5, |
| 358 | u32(first_global), 8, 24) |
| 359 | |
| 360 | // 5: .strtab |
| 361 | write_shdr(mut buf, u32(off_strtab_name), sht_strtab, 0, u64(off_strtab), u64(e.str_table.len), |
| 362 | 0, 0, 1, 0) |
| 363 | |
| 364 | // 6: .rela.text (EntSize = 24) |
| 365 | write_shdr(mut buf, u32(off_rela_text_name), sht_rela, 0, u64(off_rela_text), |
| 366 | u64(size_rela_text), 4, 1, 8, 24) |
| 367 | |
| 368 | // 7: .shstrtab |
| 369 | write_shdr(mut buf, u32(off_shstrtab_name), sht_strtab, 0, u64(off_shstrtab), |
| 370 | u64(e.shstr_table.len), 0, 0, 1, 0) |
| 371 | |
| 372 | os.write_file_array(path, buf) or { panic(err) } |
| 373 | } |
| 374 | |
| 375 | fn write_shdr(mut b []u8, name u32, type_ u32, flags u64, off u64, size u64, link u32, info u32, align u64, entsize u64) { |
| 376 | write_u32_le(mut b, name) |
| 377 | write_u32_le(mut b, type_) |
| 378 | write_u64_le(mut b, flags) |
| 379 | write_u64_le(mut b, 0) // Addr |
| 380 | write_u64_le(mut b, off) |
| 381 | write_u64_le(mut b, size) |
| 382 | write_u32_le(mut b, link) |
| 383 | write_u32_le(mut b, info) |
| 384 | write_u64_le(mut b, align) |
| 385 | write_u64_le(mut b, entsize) |
| 386 | } |
| 387 | |
| 388 | fn write_u32_le(mut b []u8, v u32) { |
| 389 | b << u8(v) |
| 390 | b << u8(v >> 8) |
| 391 | b << u8(v >> 16) |
| 392 | b << u8(v >> 24) |
| 393 | } |
| 394 | |
| 395 | fn write_u64_le(mut b []u8, v u64) { |
| 396 | b << u8(v) |
| 397 | b << u8(v >> 8) |
| 398 | b << u8(v >> 16) |
| 399 | b << u8(v >> 24) |
| 400 | b << u8(v >> 32) |
| 401 | b << u8(v >> 40) |
| 402 | b << u8(v >> 48) |
| 403 | b << u8(v >> 56) |
| 404 | } |
| 405 | |
| 406 | fn write_u16_le(mut b []u8, v u16) { |
| 407 | b << u8(v) |
| 408 | b << u8(v >> 8) |
| 409 | } |
| 410 | |