draugr.git


draugr

~ single ELF file analysis tool
git clone https://github.com/djnnvx/draugr.git
log | files | refs | archive | README

elf_test.odin (41776B)

      1 package elf
      2 
      3 import "core:mem"
      4 import "core:testing"
      5 
      6 make_elf64_header :: proc(data: []u8, etype: u16, machine: u16) {
      7 	data[0] = 0x7f
      8 	data[1] = 0x45
      9 	data[2] = 0x4c
     10 	data[3] = 0x46
     11 	data[4] = ELF_CLASS_64
     12 	data[5] = ELF_DATA_LSB
     13 	data[6] = 1
     14 	data[7] = 0
     15 	
     16 	data[16] = u8(etype)
     17 	data[17] = u8(etype >> 8)
     18 	data[18] = u8(machine)
     19 	data[19] = u8(machine >> 8)
     20 	data[24] = 0x00
     21 	data[25] = 0x00
     22 	data[26] = 0x40
     23 	data[27] = 0x00
     24 	data[32] = 64
     25 	data[33] = 0
     26 	data[52] = 64
     27 	data[53] = 0
     28 	data[54] = 56
     29 	data[55] = 0
     30 	data[58] = 64
     31 	data[59] = 0
     32 }
     33 
     34 @(test)
     35 test_probe_elf64 :: proc(t: ^testing.T) {
     36 	data := make([]u8, 64)
     37 	defer delete(data)
     38 	make_elf64_header(data, ET_EXEC, EM_X86_64)
     39 	
     40 	result := probe(data)
     41 	testing.expect(t, result == true, "should recognize ELF64 header")
     42 }
     43 
     44 @(test)
     45 test_probe_elf32 :: proc(t: ^testing.T) {
     46 	data := make([]u8, 52)
     47 	defer delete(data)
     48 	
     49 	data[0] = 0x7f
     50 	data[1] = 0x45
     51 	data[2] = 0x4c
     52 	data[3] = 0x46
     53 	data[4] = ELF_CLASS_32
     54 	data[5] = ELF_DATA_LSB
     55 	
     56 	result := probe(data)
     57 	testing.expect(t, result == true, "should recognize ELF32 header")
     58 }
     59 
     60 @(test)
     61 test_probe_invalid_magic :: proc(t: ^testing.T) {
     62 	data := []u8{0x00, 0x01, 0x02, 0x03}
     63 	
     64 	result := probe(data)
     65 	testing.expect(t, result == false, "should reject invalid magic")
     66 }
     67 
     68 @(test)
     69 test_probe_too_small :: proc(t: ^testing.T) {
     70 	data := []u8{0x7f, 0x45, 0x4c}
     71 	
     72 	result := probe(data)
     73 	testing.expect(t, result == false, "should reject data smaller than 16 bytes")
     74 }
     75 
     76 @(test)
     77 test_parse_header_class_64 :: proc(t: ^testing.T) {
     78 	data := make([]u8, 64)
     79 	defer delete(data)
     80 	make_elf64_header(data, ET_EXEC, EM_X86_64)
     81 	
     82 	header, err := parse_header(data)
     83 	testing.expect(t, err == "", "parse should succeed")
     84 	testing.expect(t, header.class == ELF_CLASS_64, "class should be 64")
     85 }
     86 
     87 @(test)
     88 test_parse_header_class_32 :: proc(t: ^testing.T) {
     89 	data := make([]u8, 52)
     90 	defer delete(data)
     91 	
     92 	data[0] = 0x7f
     93 	data[1] = 0x45
     94 	data[2] = 0x4c
     95 	data[3] = 0x46
     96 	data[4] = ELF_CLASS_32
     97 	data[5] = ELF_DATA_LSB
     98 	data[16] = u8(ET_EXEC)
     99 	data[17] = 0
    100 	data[18] = u8(EM_386)
    101 	data[19] = 0
    102 	
    103 	header, err := parse_header(data)
    104 	testing.expect(t, err == "", "parse should succeed")
    105 	testing.expect(t, header.class == ELF_CLASS_32, "class should be 32")
    106 }
    107 
    108 @(test)
    109 test_parse_header_type_exec :: proc(t: ^testing.T) {
    110 	data := make([]u8, 64)
    111 	defer delete(data)
    112 	make_elf64_header(data, ET_EXEC, EM_X86_64)
    113 	
    114 	header, err := parse_header(data)
    115 	
    116 	testing.expect(t, err == "")
    117 	testing.expect(t, header.type == ET_EXEC, "type should be ET_EXEC")
    118 }
    119 
    120 @(test)
    121 test_parse_header_type_dyn :: proc(t: ^testing.T) {
    122 	data := make([]u8, 64)
    123 	defer delete(data)
    124 	make_elf64_header(data, ET_DYN, EM_X86_64)
    125 	
    126 	header, err := parse_header(data)
    127 	
    128 	testing.expect(t, err == "")
    129 	testing.expect(t, header.type == ET_DYN, "type should be ET_DYN")
    130 }
    131 
    132 @(test)
    133 test_parse_header_type_rel :: proc(t: ^testing.T) {
    134 	data := make([]u8, 64)
    135 	defer delete(data)
    136 	make_elf64_header(data, ET_REL, EM_X86_64)
    137 	
    138 	header, err := parse_header(data)
    139 	
    140 	testing.expect(t, err == "")
    141 	testing.expect(t, header.type == ET_REL, "type should be ET_REL")
    142 }
    143 
    144 @(test)
    145 test_parse_header_machine_x86_64 :: proc(t: ^testing.T) {
    146 	data := make([]u8, 64)
    147 	defer delete(data)
    148 	make_elf64_header(data, ET_EXEC, EM_X86_64)
    149 	
    150 	header, err := parse_header(data)
    151 	
    152 	testing.expect(t, err == "")
    153 	testing.expect(t, header.machine == EM_X86_64, "machine should be x86-64")
    154 }
    155 
    156 @(test)
    157 test_parse_header_machine_arm :: proc(t: ^testing.T) {
    158 	data := make([]u8, 64)
    159 	defer delete(data)
    160 	make_elf64_header(data, ET_EXEC, EM_ARM)
    161 	
    162 	header, err := parse_header(data)
    163 	
    164 	testing.expect(t, err == "")
    165 	testing.expect(t, header.machine == EM_ARM, "machine should be ARM")
    166 }
    167 
    168 @(test)
    169 test_parse_header_machine_aarch64 :: proc(t: ^testing.T) {
    170 	data := make([]u8, 64)
    171 	defer delete(data)
    172 	make_elf64_header(data, ET_EXEC, EM_AARCH64)
    173 	
    174 	header, err := parse_header(data)
    175 	
    176 	testing.expect(t, err == "")
    177 	testing.expect(t, header.machine == EM_AARCH64, "machine should be AArch64")
    178 }
    179 
    180 @(test)
    181 test_parse_header_machine_riscv :: proc(t: ^testing.T) {
    182 	data := make([]u8, 64)
    183 	defer delete(data)
    184 	make_elf64_header(data, ET_EXEC, EM_RISCV)
    185 
    186 	header, err := parse_header(data)
    187 
    188 	testing.expect(t, err == "")
    189 	testing.expect(t, header.machine == EM_RISCV, "machine should be RISC-V")
    190 }
    191 
    192 @(test)
    193 test_machine_str_covers_firmware_arches :: proc(t: ^testing.T) {
    194 	testing.expect_value(t, machine_to_str(EM_MIPS), "MIPS")
    195 	testing.expect_value(t, machine_to_str(EM_PPC), "PowerPC")
    196 	testing.expect_value(t, machine_to_str(EM_PPC64), "PowerPC64")
    197 	testing.expect_value(t, machine_to_str(EM_ARM), "ARM")
    198 	testing.expect_value(t, machine_to_str(EM_AARCH64), "AArch64")
    199 }
    200 
    201 @(test)
    202 test_read_u16_le_basic :: proc(t: ^testing.T) {
    203 	data := []u8{0x01, 0x02}
    204 	
    205 	val := read_u16(data, 0, .Little)
    206 	testing.expect_value(t, val, u16(0x0201))
    207 }
    208 
    209 @(test)
    210 test_read_u16_le_offset :: proc(t: ^testing.T) {
    211 	data := []u8{0x00, 0x00, 0x01, 0x02}
    212 	
    213 	val := read_u16(data, 2, .Little)
    214 	testing.expect_value(t, val, u16(0x0201))
    215 }
    216 
    217 @(test)
    218 test_read_u32_le_basic :: proc(t: ^testing.T) {
    219 	data := []u8{0x01, 0x02, 0x03, 0x04}
    220 	
    221 	val := read_u32(data, 0, .Little)
    222 	testing.expect_value(t, val, u32(0x04030201))
    223 }
    224 
    225 @(test)
    226 test_read_u64_le_basic :: proc(t: ^testing.T) {
    227 	data := []u8{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}
    228 	
    229 	val := read_u64(data, 0, .Little)
    230 	testing.expect_value(t, val, u64(0x0807060504030201))
    231 }
    232 
    233 @(test)
    234 test_read_u16_be_basic :: proc(t: ^testing.T) {
    235 	data := []u8{0x01, 0x02}
    236 	
    237 	val := read_u16(data, 0, .Big)
    238 	testing.expect_value(t, val, u16(0x0102))
    239 }
    240 
    241 @(test)
    242 test_read_u32_be_basic :: proc(t: ^testing.T) {
    243 	data := []u8{0x01, 0x02, 0x03, 0x04}
    244 	
    245 	val := read_u32(data, 0, .Big)
    246 	testing.expect_value(t, val, u32(0x01020304))
    247 }
    248 
    249 @(test)
    250 test_read_u64_be_basic :: proc(t: ^testing.T) {
    251 	data := []u8{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}
    252 	
    253 	val := read_u64(data, 0, .Big)
    254 	testing.expect_value(t, val, u64(0x0102030405060708))
    255 }
    256 
    257 @(test)
    258 test_load_elf64_basic :: proc(t: ^testing.T) {
    259 	data := make([]u8, 64)
    260 	defer delete(data)
    261 	make_elf64_header(data, ET_EXEC, EM_X86_64)
    262 	
    263 	info, err := load(data)
    264 	defer destroy(&info)
    265 	testing.expect(t, err == "", "load should succeed")
    266 	testing.expect(t, info.is_64bit == true, "should be 64-bit")
    267 	testing.expect(t, info.is_pie == false, "ET_EXEC should not be PIE")
    268 }
    269 
    270 @(test)
    271 test_load_elf64_pie :: proc(t: ^testing.T) {
    272 	data := make([]u8, 64)
    273 	defer delete(data)
    274 	make_elf64_header(data, ET_DYN, EM_X86_64)
    275 	
    276 	info, err := load(data)
    277 	defer destroy(&info)
    278 	testing.expect(t, err == "", "load should succeed")
    279 	testing.expect(t, info.is_pie == true, "ET_DYN should be PIE")
    280 }
    281 
    282 @(test)
    283 test_load_elf32_basic :: proc(t: ^testing.T) {
    284 	data := make([]u8, 52)
    285 	defer delete(data)
    286 	
    287 	data[0] = 0x7f
    288 	data[1] = 0x45
    289 	data[2] = 0x4c
    290 	data[3] = 0x46
    291 	data[4] = ELF_CLASS_32
    292 	data[5] = ELF_DATA_LSB
    293 	data[16] = u8(ET_EXEC)
    294 	data[17] = 0
    295 	data[18] = u8(EM_386)
    296 	data[19] = 0
    297 	
    298 	info, err := load(data)
    299 	defer destroy(&info)
    300 	testing.expect(t, err == "", "load should succeed")
    301 	testing.expect(t, info.is_64bit == false, "should be 32-bit")
    302 }
    303 
    304 TEXT_BLOB     :: "\x55\x89\xe5\x83\xec\x08\xc9\xc3"
    305 INTERP_BLOB   :: "/lib/ld-linux.so.2\x00"
    306 SHSTRTAB_BLOB :: "\x00.text\x00.interp\x00.symtab\x00.strtab\x00.dynsym\x00.dynstr\x00.shstrtab\x00"
    307 STRTAB_BLOB   :: "\x00main\x00counter\x00printf\x00"
    308 DYNSTR_BLOB   :: "\x00puts\x00"
    309 
    310 NAME_TEXT     :: 1
    311 NAME_INTERP   :: 7
    312 NAME_SYMTAB   :: 15
    313 NAME_STRTAB   :: 23
    314 NAME_DYNSYM   :: 31
    315 NAME_DYNSTR   :: 39
    316 NAME_SHSTRTAB :: 47
    317 
    318 STR_MAIN    :: 1
    319 STR_COUNTER :: 6
    320 STR_PRINTF  :: 14
    321 DYN_PUTS    :: 1
    322 
    323 SEC_NULL     :: 0
    324 SEC_TEXT     :: 1
    325 SEC_INTERP   :: 2
    326 SEC_SYMTAB   :: 3
    327 SEC_STRTAB   :: 4
    328 SEC_DYNSYM   :: 5
    329 SEC_DYNSTR   :: 6
    330 SEC_SHSTRTAB :: 7
    331 
    332 Raw_Sym :: struct {
    333 	name  : u32,
    334 	info  : u8,
    335 	other : u8,
    336 	shndx : u16,
    337 	value : u64,
    338 	size  : u64,
    339 }
    340 
    341 Fixture :: struct {
    342 	data      : []u8,
    343 	class     : u8,
    344 	endian    : u8,
    345 	type      : u16,
    346 	machine   : u16,
    347 	entry     : u64,
    348 	flags     : u32,
    349 	phoff     : u64,
    350 	shoff     : u64,
    351 	phentsize : u16,
    352 	shentsize : u16,
    353 	phnum     : u16,
    354 	shnum     : u16,
    355 	shstrndx  : u16,
    356 	symsize   : int,
    357 	interp_off: u64,
    358 	segments  : [4]Program_Header,
    359 	sections  : [8]Section_Header,
    360 	symbols   : [4]Raw_Sym,
    361 	dynsyms   : [2]Raw_Sym,
    362 }
    363 
    364 put_u16 :: proc(data: []u8, off: int, v: u16, little: bool) {
    365 	if little {
    366 		data[off+0] = u8(v)
    367 		data[off+1] = u8(v >> 8)
    368 	} else {
    369 		data[off+0] = u8(v >> 8)
    370 		data[off+1] = u8(v)
    371 	}
    372 }
    373 
    374 put_u32 :: proc(data: []u8, off: int, v: u32, little: bool) {
    375 	for i in 0 ..< 4 {
    376 		shift := uint(8 * i) if little else uint(8 * (3 - i))
    377 		data[off+i] = u8(v >> shift)
    378 	}
    379 }
    380 
    381 put_u64 :: proc(data: []u8, off: int, v: u64, little: bool) {
    382 	for i in 0 ..< 8 {
    383 		shift := uint(8 * i) if little else uint(8 * (7 - i))
    384 		data[off+i] = u8(v >> shift)
    385 	}
    386 }
    387 
    388 align_up :: proc(v: int, a: int) -> int {
    389 	return (v + a - 1) / a * a
    390 }
    391 
    392 write_phdr :: proc(data: []u8, off: int, is64: bool, little: bool, p: Program_Header) {
    393 	if is64 {
    394 		put_u32(data, off+0,  p.type, little)
    395 		put_u32(data, off+4,  u32(p.flags), little)
    396 		put_u64(data, off+8,  p.offset, little)
    397 		put_u64(data, off+16, p.vaddr, little)
    398 		put_u64(data, off+24, p.paddr, little)
    399 		put_u64(data, off+32, p.filesz, little)
    400 		put_u64(data, off+40, p.memsz, little)
    401 		put_u64(data, off+48, p.align, little)
    402 	} else {
    403 		put_u32(data, off+0,  p.type, little)
    404 		put_u32(data, off+4,  u32(p.offset), little)
    405 		put_u32(data, off+8,  u32(p.vaddr), little)
    406 		put_u32(data, off+12, u32(p.paddr), little)
    407 		put_u32(data, off+16, u32(p.filesz), little)
    408 		put_u32(data, off+20, u32(p.memsz), little)
    409 		put_u32(data, off+24, u32(p.flags), little)
    410 		put_u32(data, off+28, u32(p.align), little)
    411 	}
    412 }
    413 
    414 write_shdr :: proc(data: []u8, off: int, is64: bool, little: bool, s: Section_Header) {
    415 	if is64 {
    416 		put_u32(data, off+0,  s.name, little)
    417 		put_u32(data, off+4,  s.type, little)
    418 		put_u64(data, off+8,  s.flags, little)
    419 		put_u64(data, off+16, s.addr, little)
    420 		put_u64(data, off+24, s.offset, little)
    421 		put_u64(data, off+32, s.size, little)
    422 		put_u32(data, off+40, s.link, little)
    423 		put_u32(data, off+44, s.info, little)
    424 		put_u64(data, off+48, s.addralign, little)
    425 		put_u64(data, off+56, s.entsize, little)
    426 	} else {
    427 		put_u32(data, off+0,  s.name, little)
    428 		put_u32(data, off+4,  s.type, little)
    429 		put_u32(data, off+8,  u32(s.flags), little)
    430 		put_u32(data, off+12, u32(s.addr), little)
    431 		put_u32(data, off+16, u32(s.offset), little)
    432 		put_u32(data, off+20, u32(s.size), little)
    433 		put_u32(data, off+24, s.link, little)
    434 		put_u32(data, off+28, s.info, little)
    435 		put_u32(data, off+32, u32(s.addralign), little)
    436 		put_u32(data, off+36, u32(s.entsize), little)
    437 	}
    438 }
    439 
    440 write_sym :: proc(data: []u8, off: int, is64: bool, little: bool, s: Raw_Sym) {
    441 	if is64 {
    442 		put_u32(data, off+0,  s.name, little)
    443 		data[off+4] = s.info
    444 		data[off+5] = s.other
    445 		put_u16(data, off+6,  s.shndx, little)
    446 		put_u64(data, off+8,  s.value, little)
    447 		put_u64(data, off+16, s.size, little)
    448 	} else {
    449 		put_u32(data, off+0,  s.name, little)
    450 		put_u32(data, off+4,  u32(s.value), little)
    451 		put_u32(data, off+8,  u32(s.size), little)
    452 		data[off+12] = s.info
    453 		data[off+13] = s.other
    454 		put_u16(data, off+14, s.shndx, little)
    455 	}
    456 }
    457 
    458 build_elf :: proc(class: u8, endian: u8) -> Fixture {
    459 	f: Fixture
    460 	f.class = class
    461 	f.endian = endian
    462 
    463 	is64 := class == ELF_CLASS_64
    464 	little := endian == ELF_DATA_LSB
    465 
    466 	ehsize := 64 if is64 else 52
    467 	f.phentsize = 56 if is64 else 32
    468 	f.shentsize = 64 if is64 else 40
    469 	f.symsize   = 24 if is64 else 16
    470 
    471 	f.phnum    = 4
    472 	f.shnum    = 8
    473 	f.shstrndx = SEC_SHSTRTAB
    474 	f.type     = ET_EXEC
    475 	f.machine  = EM_X86_64 if is64 else EM_386
    476 	f.entry    = 0x0000000000401120 if is64 else 0x08049000
    477 	f.flags    = 0x05000200
    478 
    479 	off_ph     := ehsize
    480 	off_interp := off_ph + int(f.phnum) * int(f.phentsize)
    481 	off_text   := off_interp + len(INTERP_BLOB)
    482 	off_shstr  := off_text + len(TEXT_BLOB)
    483 	off_strtab := off_shstr + len(SHSTRTAB_BLOB)
    484 	off_dynstr := off_strtab + len(STRTAB_BLOB)
    485 	off_symtab := align_up(off_dynstr + len(DYNSTR_BLOB), 8)
    486 	off_dynsym := off_symtab + 4 * f.symsize
    487 	off_sh     := align_up(off_dynsym + 2 * f.symsize, 8)
    488 	total      := off_sh + int(f.shnum) * int(f.shentsize)
    489 
    490 	f.phoff = u64(off_ph)
    491 	f.shoff = u64(off_sh)
    492 	f.interp_off = u64(off_interp)
    493 
    494 	f.segments = [4]Program_Header{
    495 		{
    496 			type = PT_LOAD, flags = PF_R | PF_X,
    497 			offset = 0, vaddr = 0x08048000, paddr = 0x08048000,
    498 			filesz = 0x0640, memsz = 0x0640, align = 0x1000,
    499 		},
    500 		{
    501 			type = PT_LOAD, flags = PF_R | PF_W,
    502 			offset = 0x0f00, vaddr = 0x0804af00, paddr = 0x0804af10,
    503 			filesz = 0x0118, memsz = 0x0220, align = 0x1000,
    504 		},
    505 		{
    506 			type = PT_INTERP, flags = PF_R,
    507 			offset = u64(off_interp), vaddr = 0x08048154, paddr = 0x08048154,
    508 			filesz = u64(len(INTERP_BLOB)), memsz = u64(len(INTERP_BLOB)), align = 1,
    509 		},
    510 		{
    511 			type = PT_DYNAMIC, flags = PF_R | PF_W,
    512 			offset = 0x0f14, vaddr = 0x0804af14, paddr = 0x0804af18,
    513 			filesz = 0x00e8, memsz = 0x00e8, align = 4,
    514 		},
    515 	}
    516 
    517 	f.sections = [8]Section_Header{
    518 		{},
    519 		{
    520 			name = NAME_TEXT, type = SHT_PROGBITS, flags = 0x6, addr = 0x08049000,
    521 			offset = u64(off_text), size = u64(len(TEXT_BLOB)), addralign = 16,
    522 		},
    523 		{
    524 			name = NAME_INTERP, type = SHT_PROGBITS, flags = 0x2, addr = 0x08048154,
    525 			offset = u64(off_interp), size = u64(len(INTERP_BLOB)), addralign = 1,
    526 		},
    527 		{
    528 			name = NAME_SYMTAB, type = SHT_SYMTAB,
    529 			offset = u64(off_symtab), size = u64(4 * f.symsize),
    530 			link = SEC_STRTAB, info = 2, addralign = 8, entsize = u64(f.symsize),
    531 		},
    532 		{
    533 			name = NAME_STRTAB, type = SHT_STRTAB,
    534 			offset = u64(off_strtab), size = u64(len(STRTAB_BLOB)), addralign = 1,
    535 		},
    536 		{
    537 			name = NAME_DYNSYM, type = SHT_DYNSYM, flags = 0x2, addr = 0x080481b0,
    538 			offset = u64(off_dynsym), size = u64(2 * f.symsize),
    539 			link = SEC_DYNSTR, info = 1, addralign = 4, entsize = u64(f.symsize),
    540 		},
    541 		{
    542 			name = NAME_DYNSTR, type = SHT_STRTAB, flags = 0x2, addr = 0x080481f0,
    543 			offset = u64(off_dynstr), size = u64(len(DYNSTR_BLOB)), addralign = 1,
    544 		},
    545 		{
    546 			name = NAME_SHSTRTAB, type = SHT_STRTAB,
    547 			offset = u64(off_shstr), size = u64(len(SHSTRTAB_BLOB)), addralign = 1,
    548 		},
    549 	}
    550 
    551 	f.symbols = [4]Raw_Sym{
    552 		{},
    553 		{name = STR_COUNTER, info = (STB_LOCAL  << 4) | STT_OBJECT, value = 0x0804af00, size = 4,    shndx = SEC_TEXT},
    554 		{name = STR_MAIN,    info = (STB_GLOBAL << 4) | STT_FUNC,   value = 0x08049000, size = 0x40, shndx = SEC_TEXT},
    555 		{name = STR_PRINTF,  info = (STB_GLOBAL << 4) | STT_FUNC,   value = 0,          size = 0,    shndx = 0},
    556 	}
    557 
    558 	f.dynsyms = [2]Raw_Sym{
    559 		{},
    560 		{name = DYN_PUTS, info = (STB_GLOBAL << 4) | STT_FUNC, value = 0, size = 0, shndx = 0},
    561 	}
    562 
    563 	data := make([]u8, total)
    564 	f.data = data
    565 
    566 	data[0] = 0x7f
    567 	data[1] = 'E'
    568 	data[2] = 'L'
    569 	data[3] = 'F'
    570 	data[4] = class
    571 	data[5] = endian
    572 	data[6] = 1
    573 	data[7] = 0
    574 
    575 	put_u16(data, 16, f.type, little)
    576 	put_u16(data, 18, f.machine, little)
    577 	put_u32(data, 20, 1, little)
    578 
    579 	if is64 {
    580 		put_u64(data, 24, f.entry, little)
    581 		put_u64(data, 32, f.phoff, little)
    582 		put_u64(data, 40, f.shoff, little)
    583 		put_u32(data, 48, f.flags, little)
    584 		put_u16(data, 52, u16(ehsize), little)
    585 		put_u16(data, 54, f.phentsize, little)
    586 		put_u16(data, 56, f.phnum, little)
    587 		put_u16(data, 58, f.shentsize, little)
    588 		put_u16(data, 60, f.shnum, little)
    589 		put_u16(data, 62, f.shstrndx, little)
    590 	} else {
    591 		put_u32(data, 24, u32(f.entry), little)
    592 		put_u32(data, 28, u32(f.phoff), little)
    593 		put_u32(data, 32, u32(f.shoff), little)
    594 		put_u32(data, 36, f.flags, little)
    595 		put_u16(data, 40, u16(ehsize), little)
    596 		put_u16(data, 42, f.phentsize, little)
    597 		put_u16(data, 44, f.phnum, little)
    598 		put_u16(data, 46, f.shentsize, little)
    599 		put_u16(data, 48, f.shnum, little)
    600 		put_u16(data, 50, f.shstrndx, little)
    601 	}
    602 
    603 	for seg, i in f.segments {
    604 		write_phdr(data, off_ph + i * int(f.phentsize), is64, little, seg)
    605 	}
    606 
    607 	copy(data[off_interp:], INTERP_BLOB)
    608 	copy(data[off_text:],   TEXT_BLOB)
    609 	copy(data[off_shstr:],  SHSTRTAB_BLOB)
    610 	copy(data[off_strtab:], STRTAB_BLOB)
    611 	copy(data[off_dynstr:], DYNSTR_BLOB)
    612 
    613 	for sym, i in f.symbols {
    614 		write_sym(data, off_symtab + i * f.symsize, is64, little, sym)
    615 	}
    616 	for sym, i in f.dynsyms {
    617 		write_sym(data, off_dynsym + i * f.symsize, is64, little, sym)
    618 	}
    619 	for sec, i in f.sections {
    620 		write_shdr(data, off_sh + i * int(f.shentsize), is64, little, sec)
    621 	}
    622 
    623 	return f
    624 }
    625 
    626 SECTION_NAMES :: [8]string{"", ".text", ".interp", ".symtab", ".strtab", ".dynsym", ".dynstr", ".shstrtab"}
    627 SYMBOL_NAMES  :: [4]string{"", "counter", "main", "printf"}
    628 DYNSYM_NAMES  :: [2]string{"", "puts"}
    629 
    630 check_full_image :: proc(t: ^testing.T, class: u8, endian: u8) {
    631 	f := build_elf(class, endian)
    632 	defer delete(f.data)
    633 
    634 	info, err := load(f.data)
    635 	testing.expect(t, err == "", "load should succeed")
    636 	defer destroy(&info)
    637 
    638 	testing.expect_value(t, info.header.class, class)
    639 	testing.expect_value(t, info.header.data, endian)
    640 	testing.expect_value(t, info.is_64bit, class == ELF_CLASS_64)
    641 	testing.expect_value(t, info.is_pie, false)
    642 
    643 	testing.expect_value(t, info.header.type, f.type)
    644 	testing.expect_value(t, info.header.machine, f.machine)
    645 	testing.expect_value(t, info.header.entry, f.entry)
    646 	testing.expect_value(t, info.header.phoff, f.phoff)
    647 	testing.expect_value(t, info.header.shoff, f.shoff)
    648 	testing.expect_value(t, info.header.flags, f.flags)
    649 	testing.expect_value(t, info.header.phentsize, f.phentsize)
    650 	testing.expect_value(t, info.header.shentsize, f.shentsize)
    651 	testing.expect_value(t, info.header.phnum, f.phnum)
    652 	testing.expect_value(t, info.header.shnum, f.shnum)
    653 	testing.expect_value(t, info.header.shstrndx, f.shstrndx)
    654 
    655 	testing.expect_value(t, len(info.program_hdrs), int(f.phnum))
    656 	if len(info.program_hdrs) == int(f.phnum) {
    657 		for want, i in f.segments {
    658 			testing.expect_value(t, info.program_hdrs[i], want)
    659 		}
    660 	}
    661 
    662 	testing.expect_value(t, info.is_dynamic, true)
    663 	testing.expect_value(t, info.interp_offset, f.interp_off)
    664 
    665 	testing.expect_value(t, len(info.section_hdrs), int(f.shnum))
    666 	if len(info.section_hdrs) == int(f.shnum) {
    667 		names := SECTION_NAMES
    668 		for want, i in f.sections {
    669 			testing.expect_value(t, info.section_hdrs[i], want)
    670 			testing.expect_value(t, get_section_name(&info, info.section_hdrs[i]), names[i])
    671 		}
    672 	}
    673 
    674 	testing.expect_value(t, string(info.shstrtab), SHSTRTAB_BLOB)
    675 	testing.expect_value(t, string(info.strtab), STRTAB_BLOB)
    676 	testing.expect_value(t, string(info.dynstr), DYNSTR_BLOB)
    677 
    678 	testing.expect_value(t, len(info.symbols), len(f.symbols))
    679 	if len(info.symbols) == len(f.symbols) {
    680 		names := SYMBOL_NAMES
    681 		for want, i in f.symbols {
    682 			got := info.symbols[i]
    683 			testing.expect_value(t, got.name, names[i])
    684 			testing.expect_value(t, got.value, want.value)
    685 			testing.expect_value(t, got.size, want.size)
    686 			testing.expect_value(t, got.bind, want.info >> 4)
    687 			testing.expect_value(t, got.type, want.info & 0xf)
    688 			testing.expect_value(t, got.shndx, want.shndx)
    689 		}
    690 		testing.expect_value(t, info.symbols[1].bind, u8(STB_LOCAL))
    691 		testing.expect_value(t, info.symbols[1].type, u8(STT_OBJECT))
    692 		testing.expect_value(t, info.symbols[2].bind, u8(STB_GLOBAL))
    693 		testing.expect_value(t, info.symbols[2].type, u8(STT_FUNC))
    694 		testing.expect_value(t, info.symbols[3].shndx, u16(0))
    695 	}
    696 
    697 	testing.expect_value(t, len(info.dyn_symbols), len(f.dynsyms))
    698 	if len(info.dyn_symbols) == len(f.dynsyms) {
    699 		names := DYNSYM_NAMES
    700 		for want, i in f.dynsyms {
    701 			got := info.dyn_symbols[i]
    702 			testing.expect_value(t, got.name, names[i])
    703 			testing.expect_value(t, got.bind, want.info >> 4)
    704 			testing.expect_value(t, got.type, want.info & 0xf)
    705 			testing.expect_value(t, got.shndx, want.shndx)
    706 		}
    707 	}
    708 }
    709 
    710 @(test)
    711 test_full_image_elf32_le :: proc(t: ^testing.T) {
    712 	check_full_image(t, ELF_CLASS_32, ELF_DATA_LSB)
    713 }
    714 
    715 @(test)
    716 test_full_image_elf32_be :: proc(t: ^testing.T) {
    717 	check_full_image(t, ELF_CLASS_32, ELF_DATA_MSB)
    718 }
    719 
    720 @(test)
    721 test_full_image_elf64_le :: proc(t: ^testing.T) {
    722 	check_full_image(t, ELF_CLASS_64, ELF_DATA_LSB)
    723 }
    724 
    725 @(test)
    726 test_full_image_elf64_be :: proc(t: ^testing.T) {
    727 	check_full_image(t, ELF_CLASS_64, ELF_DATA_MSB)
    728 }
    729 
    730 // Regression: ELF32 header fields must use the 32-bit layout
    731 
    732 build_elf32_header_only :: proc(little: bool, entry: u32, phoff: u32, shoff: u32, flags: u32) -> []u8 {
    733 	data := make([]u8, 52)
    734 	data[0] = 0x7f
    735 	data[1] = 'E'
    736 	data[2] = 'L'
    737 	data[3] = 'F'
    738 	data[4] = ELF_CLASS_32
    739 	data[5] = ELF_DATA_LSB if little else ELF_DATA_MSB
    740 	data[6] = 1
    741 
    742 	put_u16(data, 16, ET_EXEC, little)
    743 	put_u16(data, 18, EM_386, little)
    744 	put_u32(data, 20, 1, little)
    745 	put_u32(data, 24, entry, little)
    746 	put_u32(data, 28, phoff, little)
    747 	put_u32(data, 32, shoff, little)
    748 	put_u32(data, 36, flags, little)
    749 	put_u16(data, 40, 52, little)
    750 	put_u16(data, 42, 32, little)
    751 	put_u16(data, 44, 9, little)
    752 	put_u16(data, 46, 40, little)
    753 	put_u16(data, 48, 30, little)
    754 	put_u16(data, 50, 29, little)
    755 	return data
    756 }
    757 
    758 @(test)
    759 test_regression_elf32_header_uses_32bit_layout_le :: proc(t: ^testing.T) {
    760 	data := build_elf32_header_only(true, 0x08049000, 52, 12620, 0)
    761 	defer delete(data)
    762 
    763 	h, err := parse_header(data)
    764 	testing.expect(t, err == "", "parse should succeed")
    765 
    766 	testing.expect_value(t, h.entry, u64(0x08049000))
    767 	testing.expect_value(t, h.phoff, u64(52))
    768 	testing.expect_value(t, h.shoff, u64(12620))
    769 	testing.expect_value(t, h.flags, u32(0))
    770 	testing.expect_value(t, h.phentsize, u16(32))
    771 	testing.expect_value(t, h.phnum, u16(9))
    772 	testing.expect_value(t, h.shentsize, u16(40))
    773 	testing.expect_value(t, h.shnum, u16(30))
    774 	testing.expect_value(t, h.shstrndx, u16(29))
    775 }
    776 
    777 @(test)
    778 test_regression_elf32_header_uses_32bit_layout_be :: proc(t: ^testing.T) {
    779 	data := build_elf32_header_only(false, 0x08049000, 52, 12620, 0)
    780 	defer delete(data)
    781 
    782 	h, err := parse_header(data)
    783 	testing.expect(t, err == "", "parse should succeed")
    784 
    785 	testing.expect_value(t, h.entry, u64(0x08049000))
    786 	testing.expect_value(t, h.phoff, u64(52))
    787 	testing.expect_value(t, h.shoff, u64(12620))
    788 	testing.expect_value(t, h.flags, u32(0))
    789 	testing.expect_value(t, h.phentsize, u16(32))
    790 	testing.expect_value(t, h.phnum, u16(9))
    791 	testing.expect_value(t, h.shentsize, u16(40))
    792 	testing.expect_value(t, h.shnum, u16(30))
    793 	testing.expect_value(t, h.shstrndx, u16(29))
    794 }
    795 
    796 @(test)
    797 test_regression_elf32_flags_not_contaminated :: proc(t: ^testing.T) {
    798 	for little in ([2]bool{true, false}) {
    799 		data := build_elf32_header_only(little, 0xdeadbeef, 0x11223344, 0x55667788, 0x05000402)
    800 		defer delete(data)
    801 
    802 		h, err := parse_header(data)
    803 		testing.expect(t, err == "", "parse should succeed")
    804 
    805 		testing.expect_value(t, h.entry, u64(0xdeadbeef))
    806 		testing.expect_value(t, h.phoff, u64(0x11223344))
    807 		testing.expect_value(t, h.shoff, u64(0x55667788))
    808 		testing.expect_value(t, h.flags, u32(0x05000402))
    809 	}
    810 }
    811 
    812 @(test)
    813 test_regression_elf32_program_headers_parse :: proc(t: ^testing.T) {
    814 	f := build_elf(ELF_CLASS_32, ELF_DATA_LSB)
    815 	defer delete(f.data)
    816 
    817 	info, err := load(f.data)
    818 	testing.expect(t, err == "", "load should succeed")
    819 	defer destroy(&info)
    820 
    821 	testing.expect_value(t, info.header.phoff, u64(52))
    822 	testing.expect_value(t, len(info.program_hdrs), 4)
    823 	if len(info.program_hdrs) == 4 {
    824 		testing.expect_value(t, info.program_hdrs[0].type, u32(PT_LOAD))
    825 		testing.expect_value(t, info.program_hdrs[2].type, u32(PT_INTERP))
    826 		testing.expect_value(t, info.program_hdrs[3].type, u32(PT_DYNAMIC))
    827 	}
    828 }
    829 
    830 @(test)
    831 test_in_bounds :: proc(t: ^testing.T) {
    832 	data := make([]u8, 16)
    833 	defer delete(data)
    834 
    835 	testing.expect_value(t, in_bounds(data, 0, 16), true)
    836 	testing.expect_value(t, in_bounds(data, 16, 0), true)
    837 	testing.expect_value(t, in_bounds(data, 8, 8), true)
    838 	testing.expect_value(t, in_bounds(data, 15, 1), true)
    839 
    840 	testing.expect_value(t, in_bounds(data, 17, 0), false)
    841 	testing.expect_value(t, in_bounds(data, 16, 1), false)
    842 	testing.expect_value(t, in_bounds(data, 8, 9), false)
    843 	testing.expect_value(t, in_bounds(data, 0, 17), false)
    844 	testing.expect_value(t, in_bounds(data, 0, 0xFFFFFFFFFFFFFFFF), false)
    845 	testing.expect_value(t, in_bounds(data, 0xFFFFFFFFFFFFFFFF, 0), false)
    846 	testing.expect_value(t, in_bounds(data, 0xFFFFFFFFFFFFFFFF, 1), false)
    847 	testing.expect_value(t, in_bounds(data, 0xFFFFFFFFFFFFFFF8, 8), false)
    848 	testing.expect_value(t, in_bounds(data, 0xFF00000000000000, 0), false)
    849 	testing.expect_value(t, in_bounds(data, 0x8000000000000000, 0), false)
    850 }
    851 
    852 @(test)
    853 test_in_bounds_empty_slice :: proc(t: ^testing.T) {
    854 	empty: []u8
    855 	testing.expect_value(t, in_bounds(empty, 0, 0), true)
    856 	testing.expect_value(t, in_bounds(empty, 0, 1), false)
    857 	testing.expect_value(t, in_bounds(empty, 1, 0), false)
    858 }
    859 
    860 @(test)
    861 test_getters_reject_negative_offsets :: proc(t: ^testing.T) {
    862 	data := []u8{1, 2, 3, 4, 5, 6, 7, 8}
    863 
    864 	for off in ([3]int{-1, -8, -1024}) {
    865 		testing.expect_value(t, read_u16(data, off, .Little), u16(0))
    866 		testing.expect_value(t, read_u32(data, off, .Little), u32(0))
    867 		testing.expect_value(t, read_u64(data, off, .Little), u64(0))
    868 		testing.expect_value(t, read_u16(data, off, .Big), u16(0))
    869 		testing.expect_value(t, read_u32(data, off, .Big), u32(0))
    870 		testing.expect_value(t, read_u64(data, off, .Big), u64(0))
    871 	}
    872 }
    873 
    874 @(test)
    875 test_getters_reject_past_end :: proc(t: ^testing.T) {
    876 	data := []u8{1, 2, 3, 4, 5, 6, 7, 8}
    877 
    878 	testing.expect_value(t, read_u16(data, 7, .Little), u16(0))
    879 	testing.expect_value(t, read_u16(data, 8, .Little), u16(0))
    880 	testing.expect_value(t, read_u16(data, 999, .Little), u16(0))
    881 	testing.expect_value(t, read_u16(data, 7, .Big), u16(0))
    882 	testing.expect_value(t, read_u16(data, 999, .Big), u16(0))
    883 
    884 	testing.expect_value(t, read_u32(data, 5, .Little), u32(0))
    885 	testing.expect_value(t, read_u32(data, 8, .Little), u32(0))
    886 	testing.expect_value(t, read_u32(data, 5, .Big), u32(0))
    887 	testing.expect_value(t, read_u32(data, 999, .Big), u32(0))
    888 
    889 	testing.expect_value(t, read_u64(data, 1, .Little), u64(0))
    890 	testing.expect_value(t, read_u64(data, 8, .Little), u64(0))
    891 	testing.expect_value(t, read_u64(data, 1, .Big), u64(0))
    892 	testing.expect_value(t, read_u64(data, 999, .Big), u64(0))
    893 }
    894 
    895 @(test)
    896 test_getters_exact_fit :: proc(t: ^testing.T) {
    897 	data := []u8{1, 2, 3, 4, 5, 6, 7, 8}
    898 
    899 	testing.expect_value(t, read_u16(data, 6, .Little), u16(0x0807))
    900 	testing.expect_value(t, read_u16(data, 6, .Big), u16(0x0708))
    901 	testing.expect_value(t, read_u32(data, 4, .Little), u32(0x08070605))
    902 	testing.expect_value(t, read_u32(data, 4, .Big), u32(0x05060708))
    903 	testing.expect_value(t, read_u64(data, 0, .Little), u64(0x0807060504030201))
    904 	testing.expect_value(t, read_u64(data, 0, .Big), u64(0x0102030405060708))
    905 }
    906 
    907 @(test)
    908 test_getters_on_empty_slice :: proc(t: ^testing.T) {
    909 	empty: []u8
    910 	testing.expect_value(t, read_u16(empty, 0, .Little), u16(0))
    911 	testing.expect_value(t, read_u32(empty, 0, .Little), u32(0))
    912 	testing.expect_value(t, read_u64(empty, 0, .Little), u64(0))
    913 	testing.expect_value(t, read_u16(empty, 0, .Big), u16(0))
    914 	testing.expect_value(t, read_u32(empty, 0, .Big), u32(0))
    915 	testing.expect_value(t, read_u64(empty, 0, .Big), u64(0))
    916 }
    917 
    918 build_elf64_with_segments :: proc(segs: []Program_Header) -> []u8 {
    919 	ehsize := 64
    920 	total := ehsize + len(segs) * 56
    921 	data := make([]u8, total)
    922 
    923 	data[0] = 0x7f
    924 	data[1] = 'E'
    925 	data[2] = 'L'
    926 	data[3] = 'F'
    927 	data[4] = ELF_CLASS_64
    928 	data[5] = ELF_DATA_LSB
    929 	data[6] = 1
    930 
    931 	put_u16(data, 16, ET_DYN, true)
    932 	put_u16(data, 18, EM_X86_64, true)
    933 	put_u64(data, 32, u64(ehsize), true)
    934 	put_u16(data, 52, u16(ehsize), true)
    935 	put_u16(data, 54, 56, true)
    936 	put_u16(data, 56, u16(len(segs)), true)
    937 	put_u16(data, 58, 64, true)
    938 
    939 	for seg, i in segs {
    940 		write_phdr(data, ehsize + i * 56, true, true, seg)
    941 	}
    942 	return data
    943 }
    944 
    945 @(test)
    946 test_is_dynamic_with_pt_dynamic_and_no_interp :: proc(t: ^testing.T) {
    947 	segs := [2]Program_Header{
    948 		{type = PT_LOAD, offset = 0, filesz = 0x100, memsz = 0x100},
    949 		{type = PT_DYNAMIC, offset = 0x2000, filesz = 0xe0, memsz = 0xe0},
    950 	}
    951 	data := build_elf64_with_segments(segs[:])
    952 	defer delete(data)
    953 
    954 	info, err := load(data)
    955 	testing.expect(t, err == "", "load should succeed")
    956 	defer destroy(&info)
    957 
    958 	testing.expect_value(t, info.is_dynamic, true)
    959 	testing.expect_value(t, info.interp_offset, u64(0))
    960 	testing.expect_value(t, info.is_pie, true)
    961 }
    962 
    963 @(test)
    964 test_is_dynamic_with_interp_only :: proc(t: ^testing.T) {
    965 	segs := [2]Program_Header{
    966 		{type = PT_LOAD, offset = 0, filesz = 0x100, memsz = 0x100},
    967 		{type = PT_INTERP, offset = 0x318, filesz = 0x1c, memsz = 0x1c},
    968 	}
    969 	data := build_elf64_with_segments(segs[:])
    970 	defer delete(data)
    971 
    972 	info, err := load(data)
    973 	testing.expect(t, err == "", "load should succeed")
    974 	defer destroy(&info)
    975 
    976 	testing.expect_value(t, info.is_dynamic, true)
    977 	testing.expect_value(t, info.interp_offset, u64(0x318))
    978 }
    979 
    980 @(test)
    981 test_is_dynamic_false_for_static :: proc(t: ^testing.T) {
    982 	segs := [2]Program_Header{
    983 		{type = PT_LOAD, offset = 0, filesz = 0x100, memsz = 0x100},
    984 		{type = PT_GNU_STACK, offset = 0, filesz = 0, memsz = 0},
    985 	}
    986 	data := build_elf64_with_segments(segs[:])
    987 	defer delete(data)
    988 
    989 	info, err := load(data)
    990 	testing.expect(t, err == "", "load should succeed")
    991 	defer destroy(&info)
    992 
    993 	testing.expect_value(t, info.is_dynamic, false)
    994 	testing.expect_value(t, info.interp_offset, u64(0))
    995 }
    996 
    997 @(test)
    998 test_load_destroy_leaks_nothing :: proc(t: ^testing.T) {
    999 	track: mem.Tracking_Allocator
   1000 	mem.tracking_allocator_init(&track, context.allocator)
   1001 	defer mem.tracking_allocator_destroy(&track)
   1002 
   1003 	{
   1004 		context.allocator = mem.tracking_allocator(&track)
   1005 
   1006 		classes := [2]u8{ELF_CLASS_32, ELF_CLASS_64}
   1007 		endians := [2]u8{ELF_DATA_LSB, ELF_DATA_MSB}
   1008 		for class in classes {
   1009 			for endian in endians {
   1010 				f := build_elf(class, endian)
   1011 				info, err := load(f.data)
   1012 				testing.expect(t, err == "", "load should succeed")
   1013 				testing.expect(t, len(info.symbols) > 0, "fixture should carry symbols")
   1014 				destroy(&info)
   1015 				delete(f.data)
   1016 			}
   1017 		}
   1018 	}
   1019 
   1020 	testing.expect_value(t, len(track.allocation_map), 0)
   1021 	testing.expect_value(t, len(track.bad_free_array), 0)
   1022 }
   1023 
   1024 POISON :: [7]u64{
   1025 	0xFF00000000000000,
   1026 	0x8000000000000000,
   1027 	0xFFFFFFFFFFFFFFFF,
   1028 	0x7FFFFFFFFFFFFFFF,
   1029 	0x00000000FFFFFFFF,
   1030 	0x0000000080000000,
   1031 	0xFFFFFFFFFFFFFFF0,
   1032 }
   1033 
   1034 // Every string the parser hands back must alias the caller's buffer.
   1035 inside :: proc(ptr: rawptr, size: int, data: []u8) -> bool {
   1036 	if size == 0 { return true }
   1037 	if size < 0 { return false }
   1038 	lo := uintptr(raw_data(data))
   1039 	p  := uintptr(ptr)
   1040 	return p >= lo && p - lo <= uintptr(len(data)) && uintptr(len(data)) - (p - lo) >= uintptr(size)
   1041 }
   1042 
   1043 expect_sane_load :: proc(t: ^testing.T, data: []u8, label: string) {
   1044 	info, err := load(data)
   1045 	if err != "" { return }
   1046 	defer destroy(&info)
   1047 
   1048 	phentsize := 56 if info.is_64bit else 32
   1049 	shentsize := 64 if info.is_64bit else 40
   1050 
   1051 	// e_phnum == PN_XNUM and e_shnum == 0 are escape hatches; the real counts
   1052 	// come from section[0], so the header field is not an upper bound there.
   1053 	if info.header.phnum != PN_XNUM {
   1054 		testing.expect(t, len(info.program_hdrs) <= int(info.header.phnum), label)
   1055 	}
   1056 	if info.header.shnum != 0 {
   1057 		testing.expect(t, len(info.section_hdrs) <= int(info.header.shnum), label)
   1058 	}
   1059 	testing.expect(t, len(info.program_hdrs) * phentsize <= len(data), label)
   1060 	testing.expect(t, len(info.section_hdrs) * shentsize <= len(data), label)
   1061 
   1062 	testing.expect(t, inside(raw_data(info.shstrtab), len(info.shstrtab), data), label)
   1063 	testing.expect(t, inside(raw_data(info.strtab), len(info.strtab), data), label)
   1064 	testing.expect(t, inside(raw_data(info.dynstr), len(info.dynstr), data), label)
   1065 
   1066 	for shdr in info.section_hdrs {
   1067 		name := get_section_name(&info, shdr)
   1068 		if name == "<unknown>" { continue }
   1069 		testing.expect(t, inside(raw_data(name), len(name), data), label)
   1070 	}
   1071 	for sym in info.symbols {
   1072 		testing.expect(t, inside(raw_data(sym.name), len(sym.name), data), label)
   1073 	}
   1074 	for sym in info.dyn_symbols {
   1075 		testing.expect(t, inside(raw_data(sym.name), len(sym.name), data), label)
   1076 	}
   1077 }
   1078 
   1079 poison_header_offsets :: proc(f: Fixture, v: u64) {
   1080 	little := f.endian == ELF_DATA_LSB
   1081 	if f.class == ELF_CLASS_64 {
   1082 		put_u64(f.data, 32, v, little)
   1083 		put_u64(f.data, 40, v, little)
   1084 	} else {
   1085 		put_u32(f.data, 28, u32(v), little)
   1086 		put_u32(f.data, 32, u32(v), little)
   1087 	}
   1088 }
   1089 
   1090 @(test)
   1091 test_malformed_poisoned_phoff_shoff :: proc(t: ^testing.T) {
   1092 	classes := [2]u8{ELF_CLASS_32, ELF_CLASS_64}
   1093 	endians := [2]u8{ELF_DATA_LSB, ELF_DATA_MSB}
   1094 	poisons := POISON
   1095 
   1096 	for class in classes {
   1097 		for endian in endians {
   1098 			for p in poisons {
   1099 				f := build_elf(class, endian)
   1100 				poison_header_offsets(f, p)
   1101 				expect_sane_load(t, f.data, "poisoned phoff/shoff")
   1102 				delete(f.data)
   1103 			}
   1104 
   1105 			f := build_elf(class, endian)
   1106 			poison_header_offsets(f, u64(len(f.data)) + 1)
   1107 			expect_sane_load(t, f.data, "phoff/shoff just past end")
   1108 			delete(f.data)
   1109 
   1110 			g := build_elf(class, endian)
   1111 			poison_header_offsets(g, u64(len(g.data)))
   1112 			expect_sane_load(t, g.data, "phoff/shoff exactly at end")
   1113 			delete(g.data)
   1114 		}
   1115 	}
   1116 }
   1117 
   1118 @(test)
   1119 test_malformed_saturated_counts :: proc(t: ^testing.T) {
   1120 	classes := [2]u8{ELF_CLASS_32, ELF_CLASS_64}
   1121 	endians := [2]u8{ELF_DATA_LSB, ELF_DATA_MSB}
   1122 
   1123 	for class in classes {
   1124 		for endian in endians {
   1125 			f := build_elf(class, endian)
   1126 			little := endian == ELF_DATA_LSB
   1127 			if class == ELF_CLASS_64 {
   1128 				put_u16(f.data, 56, 0xFFFF, little)
   1129 				put_u16(f.data, 60, 0xFFFF, little)
   1130 				put_u16(f.data, 62, 0xFFFF, little)
   1131 			} else {
   1132 				put_u16(f.data, 44, 0xFFFF, little)
   1133 				put_u16(f.data, 48, 0xFFFF, little)
   1134 				put_u16(f.data, 50, 0xFFFF, little)
   1135 			}
   1136 			expect_sane_load(t, f.data, "saturated phnum/shnum/shstrndx")
   1137 			delete(f.data)
   1138 		}
   1139 	}
   1140 }
   1141 
   1142 @(test)
   1143 test_malformed_saturated_entsizes :: proc(t: ^testing.T) {
   1144 	classes := [2]u8{ELF_CLASS_32, ELF_CLASS_64}
   1145 
   1146 	for class in classes {
   1147 		f := build_elf(class, ELF_DATA_LSB)
   1148 		if class == ELF_CLASS_64 {
   1149 			put_u16(f.data, 54, 0xFFFF, true)
   1150 			put_u16(f.data, 58, 0xFFFF, true)
   1151 		} else {
   1152 			put_u16(f.data, 42, 0xFFFF, true)
   1153 			put_u16(f.data, 46, 0xFFFF, true)
   1154 		}
   1155 		expect_sane_load(t, f.data, "saturated phentsize/shentsize")
   1156 		delete(f.data)
   1157 	}
   1158 }
   1159 
   1160 @(test)
   1161 test_malformed_poisoned_section_offsets :: proc(t: ^testing.T) {
   1162 	classes := [2]u8{ELF_CLASS_32, ELF_CLASS_64}
   1163 	endians := [2]u8{ELF_DATA_LSB, ELF_DATA_MSB}
   1164 	poisons := POISON
   1165 
   1166 	for class in classes {
   1167 		for endian in endians {
   1168 			for p in poisons {
   1169 				f := build_elf(class, endian)
   1170 				little := endian == ELF_DATA_LSB
   1171 				for i in 0 ..< int(f.shnum) {
   1172 					base := int(f.shoff) + i * int(f.shentsize)
   1173 					if class == ELF_CLASS_64 {
   1174 						put_u64(f.data, base+24, p, little)
   1175 						put_u64(f.data, base+32, p, little)
   1176 					} else {
   1177 						put_u32(f.data, base+16, u32(p), little)
   1178 						put_u32(f.data, base+20, u32(p), little)
   1179 					}
   1180 				}
   1181 				expect_sane_load(t, f.data, "poisoned sh_offset/sh_size")
   1182 				delete(f.data)
   1183 			}
   1184 		}
   1185 	}
   1186 }
   1187 
   1188 // offset + size wraps past 2^64, which a naive `offset + size <= len` check accepts.
   1189 @(test)
   1190 test_malformed_section_offset_size_wraps :: proc(t: ^testing.T) {
   1191 	pairs := [5][2]u64{
   1192 		{0xFFFFFFFFFFFFFFF0, 0x10},
   1193 		{0xFFFFFFFFFFFFFF00, 0x100},
   1194 		{0xFFFFFFFFFFFFFFFF, 1},
   1195 		{0x8000000000000000, 0x8000000000000000},
   1196 		{8, 0xFFFFFFFFFFFFFFF8},
   1197 	}
   1198 	targets := [4]int{SEC_SYMTAB, SEC_STRTAB, SEC_DYNSYM, SEC_SHSTRTAB}
   1199 
   1200 	for pair in pairs {
   1201 		for target in targets {
   1202 			f := build_elf(ELF_CLASS_64, ELF_DATA_LSB)
   1203 			base := int(f.shoff) + target * int(f.shentsize)
   1204 			put_u64(f.data, base+24, pair[0], true)
   1205 			put_u64(f.data, base+32, pair[1], true)
   1206 			expect_sane_load(t, f.data, "sh_offset + sh_size wraps around u64")
   1207 			delete(f.data)
   1208 		}
   1209 	}
   1210 }
   1211 
   1212 @(test)
   1213 test_malformed_poisoned_section_names :: proc(t: ^testing.T) {
   1214 	classes := [2]u8{ELF_CLASS_32, ELF_CLASS_64}
   1215 	names := [4]u32{0xFFFFFFFF, 0x80000000, u32(len(SHSTRTAB_BLOB)), u32(len(SHSTRTAB_BLOB)) - 1}
   1216 
   1217 	for class in classes {
   1218 		for n in names {
   1219 			f := build_elf(class, ELF_DATA_LSB)
   1220 			for i in 0 ..< int(f.shnum) {
   1221 				put_u32(f.data, int(f.shoff) + i * int(f.shentsize), n, true)
   1222 			}
   1223 			expect_sane_load(t, f.data, "poisoned sh_name")
   1224 			delete(f.data)
   1225 		}
   1226 	}
   1227 }
   1228 
   1229 @(test)
   1230 test_malformed_symtab_link_out_of_range :: proc(t: ^testing.T) {
   1231 	classes := [2]u8{ELF_CLASS_32, ELF_CLASS_64}
   1232 	links := [4]u32{0xFFFFFFFF, 0x7FFFFFFF, 8, 0}
   1233 
   1234 	for class in classes {
   1235 		for link in links {
   1236 			f := build_elf(class, ELF_DATA_LSB)
   1237 			link_off := 40 if class == ELF_CLASS_64 else 24
   1238 			base := int(f.shoff) + SEC_SYMTAB * int(f.shentsize)
   1239 			put_u32(f.data, base + link_off, link, true)
   1240 
   1241 			info, err := load(f.data)
   1242 			testing.expect(t, err == "", "load should succeed")
   1243 
   1244 			if link >= u32(f.shnum) {
   1245 				testing.expect_value(t, len(info.symbols), 0)
   1246 			}
   1247 			for sym in info.symbols {
   1248 				testing.expect(t, len(sym.name) <= len(info.strtab), "symbol name must stay inside strtab")
   1249 			}
   1250 			destroy(&info)
   1251 			delete(f.data)
   1252 		}
   1253 	}
   1254 }
   1255 
   1256 @(test)
   1257 test_malformed_truncated_images :: proc(t: ^testing.T) {
   1258 	classes := [2]u8{ELF_CLASS_32, ELF_CLASS_64}
   1259 	endians := [2]u8{ELF_DATA_LSB, ELF_DATA_MSB}
   1260 
   1261 	for class in classes {
   1262 		for endian in endians {
   1263 			f := build_elf(class, endian)
   1264 			for n in 0 ..= len(f.data) {
   1265 				expect_sane_load(t, f.data[:n], "truncated image")
   1266 			}
   1267 			delete(f.data)
   1268 		}
   1269 	}
   1270 }
   1271 
   1272 @(test)
   1273 test_malformed_header_byte_sweep :: proc(t: ^testing.T) {
   1274 	patterns := [4]u8{0x00, 0x01, 0x7f, 0xff}
   1275 
   1276 	for pat in patterns {
   1277 		for i in 0 ..< 64 {
   1278 			f := build_elf(ELF_CLASS_64, ELF_DATA_LSB)
   1279 			f.data[i] = pat
   1280 			expect_sane_load(t, f.data, "single byte flip in ELF64 header")
   1281 			delete(f.data)
   1282 
   1283 			g := build_elf(ELF_CLASS_32, ELF_DATA_MSB)
   1284 			if i < len(g.data) {
   1285 				g.data[i] = pat
   1286 			}
   1287 			expect_sane_load(t, g.data, "single byte flip in ELF32 header")
   1288 			delete(g.data)
   1289 		}
   1290 	}
   1291 }
   1292 
   1293 @(test)
   1294 test_parse_header_unknown_class :: proc(t: ^testing.T) {
   1295 	data := make([]u8, 64)
   1296 	defer delete(data)
   1297 	make_elf64_header(data, ET_EXEC, EM_X86_64)
   1298 	data[4] = 7
   1299 
   1300 	_, err := parse_header(data)
   1301 	testing.expect(t, err != "", "unknown class must be rejected")
   1302 
   1303 	info, load_err := load(data)
   1304 	testing.expect(t, load_err != "", "load must propagate the class error")
   1305 	testing.expect_value(t, len(info.program_hdrs), 0)
   1306 }
   1307 
   1308 @(test)
   1309 test_load_rejects_non_elf :: proc(t: ^testing.T) {
   1310 	data := []u8{'M', 'Z', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
   1311 	_, err := load(data)
   1312 	testing.expect(t, err != "", "non-ELF data must be rejected")
   1313 }
   1314 
   1315 @(test)
   1316 test_get_section_name_without_shstrtab :: proc(t: ^testing.T) {
   1317 	info: ELF_Info
   1318 	shdr := Section_Header{name = 4}
   1319 	testing.expect_value(t, get_section_name(&info, shdr), "<unknown>")
   1320 
   1321 	empty: []u8
   1322 	info.shstrtab = empty
   1323 	testing.expect_value(t, get_section_name(&info, shdr), "<unknown>")
   1324 }
   1325 
   1326 @(test)
   1327 test_get_symbol_name_bounds :: proc(t: ^testing.T) {
   1328 	strtab := transmute([]u8)string(STRTAB_BLOB)
   1329 
   1330 	testing.expect_value(t, get_symbol_name(strtab, STR_MAIN), "main")
   1331 	testing.expect_value(t, get_symbol_name(strtab, STR_COUNTER), "counter")
   1332 	testing.expect_value(t, get_symbol_name(strtab, STR_PRINTF), "printf")
   1333 	testing.expect_value(t, get_symbol_name(strtab, 0), "")
   1334 	testing.expect_value(t, get_symbol_name(strtab, u32(len(STRTAB_BLOB))), "")
   1335 	testing.expect_value(t, get_symbol_name(strtab, 0xFFFFFFFF), "")
   1336 
   1337 	unterminated := []u8{'a', 'b', 'c'}
   1338 	testing.expect_value(t, get_symbol_name(unterminated, 0), "")
   1339 
   1340 	empty: []u8
   1341 	testing.expect_value(t, get_symbol_name(empty, 0), "")
   1342 	testing.expect_value(t, get_symbol_name(nil, 5), "")
   1343 }
   1344 
   1345 @(test)
   1346 test_printer_strings :: proc(t: ^testing.T) {
   1347 	defer free_all(context.temp_allocator)
   1348 
   1349 	testing.expect_value(t, type_to_str(ET_REL), "REL (Relocatable)")
   1350 	testing.expect_value(t, type_to_str(ET_EXEC), "EXEC (Executable)")
   1351 	testing.expect_value(t, type_to_str(ET_DYN), "DYN (Shared Object/PIE)")
   1352 	testing.expect_value(t, type_to_str(ET_CORE), "CORE")
   1353 	testing.expect_value(t, type_to_str(999), "UNKNOWN (999)")
   1354 
   1355 	testing.expect_value(t, machine_to_str(EM_386), "x86 (i386)")
   1356 	testing.expect_value(t, machine_to_str(EM_X86_64), "x86-64")
   1357 	testing.expect_value(t, machine_to_str(EM_ARM), "ARM")
   1358 	testing.expect_value(t, machine_to_str(EM_AARCH64), "AArch64")
   1359 	testing.expect_value(t, machine_to_str(EM_RISCV), "RISC-V")
   1360 	testing.expect_value(t, machine_to_str(1234), "Unknown (1234)")
   1361 
   1362 	testing.expect_value(t, section_type_str(SHT_NULL), "NULL")
   1363 	testing.expect_value(t, section_type_str(SHT_PROGBITS), "PROGBITS")
   1364 	testing.expect_value(t, section_type_str(SHT_SYMTAB), "SYMTAB")
   1365 	testing.expect_value(t, section_type_str(SHT_STRTAB), "STRTAB")
   1366 	testing.expect_value(t, section_type_str(SHT_DYNSYM), "DYNSYM")
   1367 	testing.expect_value(t, section_type_str(0x6ffffff6), "GNU_HASH")
   1368 	testing.expect_value(t, section_type_str(0x1234), "0x1234")
   1369 
   1370 	testing.expect_value(t, symbol_type_str(STT_NOTYPE), "NOTYPE")
   1371 	testing.expect_value(t, symbol_type_str(STT_OBJECT), "OBJECT")
   1372 	testing.expect_value(t, symbol_type_str(STT_FUNC), "FUNC")
   1373 	testing.expect_value(t, symbol_type_str(STT_SECTION), "SECTION")
   1374 	testing.expect_value(t, symbol_type_str(STT_FILE), "FILE")
   1375 	testing.expect_value(t, symbol_type_str(13), "0xd")
   1376 
   1377 	testing.expect_value(t, symbol_bind_str(STB_LOCAL), "LOCAL")
   1378 	testing.expect_value(t, symbol_bind_str(STB_GLOBAL), "GLOBAL")
   1379 	testing.expect_value(t, symbol_bind_str(STB_WEAK), "WEAK")
   1380 	testing.expect_value(t, symbol_bind_str(10), "0xa")
   1381 
   1382 	testing.expect_value(t, segment_type_str(PT_NULL), "NULL")
   1383 	testing.expect_value(t, segment_type_str(PT_LOAD), "LOAD")
   1384 	testing.expect_value(t, segment_type_str(PT_DYNAMIC), "DYNAMIC")
   1385 	testing.expect_value(t, segment_type_str(PT_INTERP), "INTERP")
   1386 	testing.expect_value(t, segment_type_str(PT_GNU_RELRO), "GNU_RELRO")
   1387 	testing.expect_value(t, segment_type_str(0xabc), "0xabc")
   1388 }
   1389 
   1390 @(test)
   1391 test_flags_to_str :: proc(t: ^testing.T) {
   1392 	testing.expect_value(t, flags_to_str(0), "   ")
   1393 	testing.expect_value(t, flags_to_str(PF_X), "  X")
   1394 	testing.expect_value(t, flags_to_str(PF_W), " W ")
   1395 	testing.expect_value(t, flags_to_str(PF_R), "R  ")
   1396 	testing.expect_value(t, flags_to_str(PF_R | PF_X), "R X")
   1397 	testing.expect_value(t, flags_to_str(PF_R | PF_W), "RW ")
   1398 	testing.expect_value(t, flags_to_str(PF_R | PF_W | PF_X), "RWX")
   1399 	testing.expect_value(t, flags_to_str(0xFFFFFFFFFFFFFFFF), "RWX")
   1400 }