v / vlib / v2 / gen / x64 / x64_object_format_test.v
2572 lines · 2362 sloc · 80.81 KB · ddb021b9866c3b4523b746fa2f4c16a594f8bd89
Raw
1module x64
2
3import os
4import v2.mir
5import v2.ssa
6
7fn temp_object_path(name string) string {
8 return os.join_path(os.vtmp_dir(), 'v2_x64_${name}_${os.getpid()}.o')
9}
10
11fn read_u32_le(data []u8, off int) u32 {
12 return u32(data[off]) | (u32(data[off + 1]) << 8) | (u32(data[off + 2]) << 16) | (u32(data[
13 off + 3]) << 24)
14}
15
16fn read_u16_le(data []u8, off int) u16 {
17 return u16(data[off]) | (u16(data[off + 1]) << 8)
18}
19
20fn read_u64_le(data []u8, off int) u64 {
21 return u64(read_u32_le(data, off)) | (u64(read_u32_le(data, off + 4)) << 32)
22}
23
24fn read_string(data []u8, off int) string {
25 mut end := off
26 for end < data.len && data[end] != 0 {
27 end++
28 }
29 return data[off..end].bytestr()
30}
31
32fn read_fixed_string(data []u8, off int, len int) string {
33 return data[off..off + len].bytestr().trim_right('\0')
34}
35
36fn test_decode_c_string_literal_bytes_handles_hex_octal_and_standard_escapes() {
37 got := decode_c_string_literal_bytes(r'\x00username\xff\xfe\101\n\t\r\a\b\f\v\\\"')
38 assert got == [u8(0), `u`, `s`, `e`, `r`, `n`, `a`, `m`, `e`, 0xff, 0xfe, `A`, 10, 9, 13, 7,
39 8, 12, 11, 92, 34]
40}
41
42fn new_macho_global_codegen_test_module(name string, linkage ssa.Linkage) mir.Module {
43 mut ts := ssa.TypeStore.new()
44 i8_t := ts.get_int(8)
45 ptr_t := ts.get_ptr(i8_t)
46 ptr_ptr_t := ts.get_ptr(ptr_t)
47 return mir.Module{
48 type_store: unsafe { *ts }
49 values: [
50 mir.Value{
51 id: 0
52 typ: ptr_ptr_t
53 index: 0
54 kind: .global
55 name: name
56 },
57 ]
58 globals: [
59 ssa.GlobalVar{
60 name: name
61 typ: ptr_t
62 linkage: linkage
63 },
64 ]
65 }
66}
67
68fn new_func_ref_codegen_test_module(name string) mir.Module {
69 mut ts := ssa.TypeStore.new()
70 i8_t := ts.get_int(8)
71 ptr_t := ts.get_ptr(i8_t)
72 return mir.Module{
73 type_store: unsafe { *ts }
74 values: [
75 mir.Value{
76 id: 0
77 typ: ptr_t
78 index: 0
79 kind: .func_ref
80 name: name
81 },
82 ]
83 }
84}
85
86fn new_windows_coff_global_codegen_test_module() mir.Module {
87 mut ts := ssa.TypeStore.new()
88 i64_t := ts.get_int(64)
89 ptr_i64_t := ts.get_ptr(i64_t)
90 return mir.Module{
91 type_store: unsafe { *ts }
92 values: [
93 mir.Value{
94 id: 0
95 typ: ptr_i64_t
96 index: 0
97 kind: .global
98 name: 'keep_global'
99 },
100 mir.Value{
101 id: 1
102 typ: ptr_i64_t
103 index: 1
104 kind: .global
105 name: 'drop_global'
106 },
107 mir.Value{
108 id: 2
109 typ: ptr_i64_t
110 index: 0
111 kind: .instruction
112 },
113 ]
114 instrs: [
115 mir.Instruction{
116 op: .ret
117 operands: [0]
118 typ: ptr_i64_t
119 block: 0
120 },
121 ]
122 blocks: [
123 mir.BasicBlock{
124 id: 0
125 val_id: 2
126 name: 'entry'
127 parent: 0
128 instrs: [2]
129 },
130 ]
131 funcs: [
132 mir.Function{
133 id: 0
134 name: 'main'
135 typ: ptr_i64_t
136 blocks: [0]
137 },
138 ]
139 globals: [
140 ssa.GlobalVar{
141 name: 'keep_global'
142 typ: i64_t
143 linkage: .private
144 initial_data: [u8(1), 2, 3, 4, 5, 6, 7, 8]
145 },
146 ssa.GlobalVar{
147 name: 'drop_global'
148 typ: i64_t
149 linkage: .private
150 initial_data: [u8(9), 10, 11, 12, 13, 14, 15, 16]
151 },
152 ]
153 }
154}
155
156fn coff_test_symbol(obj &CoffObject, name string) ?CoffSymbol {
157 for sym in obj.symbols {
158 if sym.name == name {
159 return sym
160 }
161 }
162 return none
163}
164
165struct TestByteRange {
166 label string
167 start u64
168 limit u64
169}
170
171fn make_byte_range(label string, start u64, size u64) TestByteRange {
172 return TestByteRange{
173 label: label
174 start: start
175 limit: start + size
176 }
177}
178
179fn assert_range_in_file(data []u8, range TestByteRange) {
180 assert range.start <= range.limit
181 assert range.limit <= u64(data.len)
182}
183
184fn assert_ranges_do_not_overlap(ranges []TestByteRange) {
185 for i in 0 .. ranges.len {
186 for j in i + 1 .. ranges.len {
187 if ranges[i].start == ranges[i].limit || ranges[j].start == ranges[j].limit {
188 continue
189 }
190 assert ranges[i].limit <= ranges[j].start || ranges[j].limit <= ranges[i].start
191 }
192 }
193}
194
195struct ElfTestSection {
196 name string
197 name_idx u32
198 type_ u32
199 flags u64
200 offset u64
201 size u64
202 link u32
203 info u32
204 align u64
205 entsize u64
206}
207
208fn elf_test_sections(data []u8) []ElfTestSection {
209 shoff := int(read_u64_le(data, 40))
210 shentsize := int(read_u16_le(data, 58))
211 shnum := int(read_u16_le(data, 60))
212 shstrndx := int(read_u16_le(data, 62))
213 shstr_off := int(read_u64_le(data, shoff + shstrndx * shentsize + 24))
214
215 mut sections := []ElfTestSection{cap: shnum}
216 for i in 0 .. shnum {
217 off := shoff + i * shentsize
218 name_idx := int(read_u32_le(data, off))
219 sections << ElfTestSection{
220 name: read_string(data, shstr_off + name_idx)
221 name_idx: u32(name_idx)
222 type_: read_u32_le(data, off + 4)
223 flags: read_u64_le(data, off + 8)
224 offset: read_u64_le(data, off + 24)
225 size: read_u64_le(data, off + 32)
226 link: read_u32_le(data, off + 40)
227 info: read_u32_le(data, off + 44)
228 align: read_u64_le(data, off + 48)
229 entsize: read_u64_le(data, off + 56)
230 }
231 }
232 return sections
233}
234
235fn elf_test_payload_ranges(sections []ElfTestSection) []TestByteRange {
236 mut ranges := []TestByteRange{}
237 for section in sections {
238 if section.size == 0 {
239 continue
240 }
241 ranges << make_byte_range(section.name, section.offset, section.size)
242 }
243 return ranges
244}
245
246struct MachOTestSection {
247 sectname string
248 segname string
249 addr u64
250 size u64
251 offset u32
252 align u32
253 reloff u32
254 nreloc u32
255 flags u32
256}
257
258struct MachOTestSymbol {
259 name string
260 type_ u8
261 sect u8
262 desc u16
263 value u64
264}
265
266struct MachOTestRelocation {
267 addr u32
268 sym_idx int
269 pcrel bool
270 length int
271 extern bool
272 type_ int
273}
274
275fn macho_test_load_commands(data []u8) []int {
276 ncmds := int(read_u32_le(data, 16))
277 mut off := 32
278 mut cmds := []int{cap: ncmds}
279 for _ in 0 .. ncmds {
280 cmds << off
281 off += int(read_u32_le(data, off + 4))
282 }
283 assert off == 32 + int(read_u32_le(data, 20))
284 assert off <= data.len
285 return cmds
286}
287
288fn macho_test_sections(data []u8, seg_cmd_off int) []MachOTestSection {
289 nsects := int(read_u32_le(data, seg_cmd_off + 64))
290 mut sections := []MachOTestSection{cap: nsects}
291 for i in 0 .. nsects {
292 off := seg_cmd_off + 72 + i * 80
293 sections << MachOTestSection{
294 sectname: read_fixed_string(data, off, 16)
295 segname: read_fixed_string(data, off + 16, 16)
296 addr: read_u64_le(data, off + 32)
297 size: read_u64_le(data, off + 40)
298 offset: read_u32_le(data, off + 48)
299 align: read_u32_le(data, off + 52)
300 reloff: read_u32_le(data, off + 56)
301 nreloc: read_u32_le(data, off + 60)
302 flags: read_u32_le(data, off + 64)
303 }
304 }
305 return sections
306}
307
308fn macho_test_section_ranges(sections []MachOTestSection) []TestByteRange {
309 mut ranges := []TestByteRange{}
310 for section in sections {
311 if section.size == 0 {
312 continue
313 }
314 ranges << make_byte_range(section.sectname, u64(section.offset), section.size)
315 }
316 return ranges
317}
318
319fn macho_test_symbols(data []u8) []MachOTestSymbol {
320 load_cmds := macho_test_load_commands(data)
321 symtab_cmd_off := load_cmds[1]
322 sym_off := int(read_u32_le(data, symtab_cmd_off + 8))
323 nsyms := int(read_u32_le(data, symtab_cmd_off + 12))
324 str_off := int(read_u32_le(data, symtab_cmd_off + 16))
325 mut symbols := []MachOTestSymbol{cap: nsyms}
326 for i in 0 .. nsyms {
327 off := sym_off + i * 16
328 name_off := int(read_u32_le(data, off))
329 symbols << MachOTestSymbol{
330 name: read_string(data, str_off + name_off)
331 type_: data[off + 4]
332 sect: data[off + 5]
333 desc: read_u16_le(data, off + 6)
334 value: read_u64_le(data, off + 8)
335 }
336 }
337 return symbols
338}
339
340fn macho_test_symbol_names(data []u8) []string {
341 symbols := macho_test_symbols(data)
342 mut names := []string{cap: symbols.len}
343 for symbol in symbols {
344 names << symbol.name
345 }
346 return names
347}
348
349fn macho_test_symbol_by_name(symbols []MachOTestSymbol, name string) ?MachOTestSymbol {
350 for symbol in symbols {
351 if symbol.name == name {
352 return symbol
353 }
354 }
355 return none
356}
357
358fn macho_test_text_relocations(data []u8) []MachOTestRelocation {
359 load_cmds := macho_test_load_commands(data)
360 sections := macho_test_sections(data, load_cmds[0])
361 text_section := sections[0]
362 mut relocs := []MachOTestRelocation{cap: int(text_section.nreloc)}
363 for i in 0 .. int(text_section.nreloc) {
364 off := int(text_section.reloff) + i * 8
365 info := read_u32_le(data, off + 4)
366 relocs << MachOTestRelocation{
367 addr: read_u32_le(data, off)
368 sym_idx: int(info & 0x00ff_ffff)
369 pcrel: ((info >> 24) & 1) == 1
370 length: int((info >> 25) & 3)
371 extern: ((info >> 27) & 1) == 1
372 type_: int(info >> 28)
373 }
374 }
375 return relocs
376}
377
378fn macho_test_text_bytes(data []u8) []u8 {
379 load_cmds := macho_test_load_commands(data)
380 sections := macho_test_sections(data, load_cmds[0])
381 return data[int(sections[0].offset)..int(sections[0].offset + sections[0].size)]
382}
383
384fn macho_test_bytes_index(data []u8, needle []u8) int {
385 if needle.len == 0 {
386 return 0
387 }
388 if needle.len > data.len {
389 return -1
390 }
391 for i in 0 .. data.len - needle.len + 1 {
392 mut matches := true
393 for j in 0 .. needle.len {
394 if data[i + j] != needle[j] {
395 matches = false
396 break
397 }
398 }
399 if matches {
400 return i
401 }
402 }
403 return -1
404}
405
406fn assert_macho_test_bytes_contains(data []u8, needle []u8, label string) {
407 assert macho_test_bytes_index(data, needle) >= 0, label
408}
409
410struct CoffTestSection {
411 name string
412 virtual_size u32
413 virtual_address u32
414 raw_size u32
415 raw_pointer u32
416 reloc_pointer u32
417 line_pointer u32
418 nreloc u16
419 nline u16
420 characteristics u32
421}
422
423fn coff_test_sections(data []u8) []CoffTestSection {
424 nsections := int(read_u16_le(data, 2))
425 mut sections := []CoffTestSection{cap: nsections}
426 for i in 0 .. nsections {
427 off := 20 + i * 40
428 sections << CoffTestSection{
429 name: read_fixed_string(data, off, 8)
430 virtual_size: read_u32_le(data, off + 8)
431 virtual_address: read_u32_le(data, off + 12)
432 raw_size: read_u32_le(data, off + 16)
433 raw_pointer: read_u32_le(data, off + 20)
434 reloc_pointer: read_u32_le(data, off + 24)
435 line_pointer: read_u32_le(data, off + 28)
436 nreloc: read_u16_le(data, off + 32)
437 nline: read_u16_le(data, off + 34)
438 characteristics: read_u32_le(data, off + 36)
439 }
440 }
441 return sections
442}
443
444fn coff_test_section_ranges(sections []CoffTestSection) []TestByteRange {
445 mut ranges := []TestByteRange{}
446 for section in sections {
447 if section.raw_size == 0 {
448 continue
449 }
450 ranges << make_byte_range(section.name, u64(section.raw_pointer), u64(section.raw_size))
451 }
452 return ranges
453}
454
455fn assert_command_ok(res os.Result, label string) {
456 assert res.exit_code == 0, '${label} failed: ${res.output}'
457}
458
459fn output_has_line_with_all(output string, words []string) bool {
460 for line in output.split_into_lines() {
461 mut matches := true
462 for word in words {
463 if !line.contains(word) {
464 matches = false
465 break
466 }
467 }
468 if matches {
469 return true
470 }
471 }
472 return false
473}
474
475enum ExternalObjectToolKind {
476 llvm_readobj
477 llvm_objdump
478 otool
479 dumpbin
480}
481
482struct ExternalObjectTool {
483 kind ExternalObjectToolKind
484 path string
485}
486
487fn find_first_available_external_tool(candidates []ExternalObjectTool) ?ExternalObjectTool {
488 for candidate in candidates {
489 if path := os.find_abs_path_of_executable(candidate.path) {
490 return ExternalObjectTool{
491 kind: candidate.kind
492 path: path
493 }
494 }
495 }
496 return none
497}
498
499fn macho_external_tool_candidates() []ExternalObjectTool {
500 return [
501 ExternalObjectTool{
502 kind: .llvm_readobj
503 path: 'llvm-readobj-20'
504 },
505 ExternalObjectTool{
506 kind: .llvm_readobj
507 path: 'llvm-readobj-19'
508 },
509 ExternalObjectTool{
510 kind: .llvm_readobj
511 path: 'llvm-readobj'
512 },
513 ExternalObjectTool{
514 kind: .llvm_readobj
515 path: 'llvm-readobj-18'
516 },
517 ExternalObjectTool{
518 kind: .llvm_readobj
519 path: 'llvm-readobj-17'
520 },
521 ExternalObjectTool{
522 kind: .llvm_readobj
523 path: 'llvm-readobj-16'
524 },
525 ExternalObjectTool{
526 kind: .llvm_objdump
527 path: 'llvm-objdump-20'
528 },
529 ExternalObjectTool{
530 kind: .llvm_objdump
531 path: 'llvm-objdump-19'
532 },
533 ExternalObjectTool{
534 kind: .llvm_objdump
535 path: 'llvm-objdump'
536 },
537 ExternalObjectTool{
538 kind: .llvm_objdump
539 path: 'llvm-objdump-18'
540 },
541 ExternalObjectTool{
542 kind: .llvm_objdump
543 path: 'llvm-objdump-17'
544 },
545 ExternalObjectTool{
546 kind: .llvm_objdump
547 path: 'llvm-objdump-16'
548 },
549 ExternalObjectTool{
550 kind: .otool
551 path: 'otool'
552 },
553 ]
554}
555
556fn coff_external_tool_candidates() []ExternalObjectTool {
557 return [
558 ExternalObjectTool{
559 kind: .llvm_readobj
560 path: 'llvm-readobj-20'
561 },
562 ExternalObjectTool{
563 kind: .llvm_readobj
564 path: 'llvm-readobj-19'
565 },
566 ExternalObjectTool{
567 kind: .llvm_readobj
568 path: 'llvm-readobj'
569 },
570 ExternalObjectTool{
571 kind: .llvm_readobj
572 path: 'llvm-readobj-18'
573 },
574 ExternalObjectTool{
575 kind: .llvm_readobj
576 path: 'llvm-readobj-17'
577 },
578 ExternalObjectTool{
579 kind: .llvm_readobj
580 path: 'llvm-readobj-16'
581 },
582 ExternalObjectTool{
583 kind: .llvm_objdump
584 path: 'llvm-objdump-20'
585 },
586 ExternalObjectTool{
587 kind: .llvm_objdump
588 path: 'llvm-objdump-19'
589 },
590 ExternalObjectTool{
591 kind: .llvm_objdump
592 path: 'llvm-objdump'
593 },
594 ExternalObjectTool{
595 kind: .llvm_objdump
596 path: 'llvm-objdump-18'
597 },
598 ExternalObjectTool{
599 kind: .llvm_objdump
600 path: 'llvm-objdump-17'
601 },
602 ExternalObjectTool{
603 kind: .llvm_objdump
604 path: 'llvm-objdump-16'
605 },
606 ExternalObjectTool{
607 kind: .dumpbin
608 path: 'dumpbin'
609 },
610 ExternalObjectTool{
611 kind: .dumpbin
612 path: 'dumpbin.exe'
613 },
614 ]
615}
616
617fn output_has_nearby_lines_with_all(output string, anchor string, words []string, radius int) bool {
618 lines := output.split_into_lines()
619 for i, line in lines {
620 if !line.contains(anchor) {
621 continue
622 }
623 start := if i > radius { i - radius } else { 0 }
624 limit := if i + radius + 1 < lines.len { i + radius + 1 } else { lines.len }
625 mut block := ''
626 for candidate in lines[start..limit] {
627 block += candidate + '\n'
628 }
629 mut matches := true
630 for word in words {
631 if !block.contains(word) {
632 matches = false
633 break
634 }
635 }
636 if matches {
637 return true
638 }
639 }
640 return false
641}
642
643fn llvm_readobj_relocation_blocks(output string) []string {
644 lines := output.split_into_lines()
645 mut blocks := []string{}
646 mut in_block := false
647 mut depth := 0
648 mut block := ''
649 for line in lines {
650 trimmed := line.trim_space()
651 if !in_block {
652 if trimmed == 'Relocation {' {
653 in_block = true
654 depth = 1
655 block = line + '\n'
656 }
657 continue
658 }
659 block += line + '\n'
660 if trimmed.ends_with('{') {
661 depth++
662 }
663 if trimmed == '}' {
664 depth--
665 }
666 if depth == 0 {
667 blocks << block
668 in_block = false
669 block = ''
670 }
671 }
672 return blocks
673}
674
675fn llvm_readobj_has_relocation_block(output string, words []string) bool {
676 for block in llvm_readobj_relocation_blocks(output) {
677 mut matches := true
678 for word in words {
679 if !block.contains(word) {
680 matches = false
681 break
682 }
683 }
684 if matches {
685 return true
686 }
687 }
688 return false
689}
690
691fn test_llvm_readobj_relocation_blocks_groups_expanded_multiline_entries() {
692 output := [
693 'Relocations [',
694 ' Section __text {',
695 ' Relocation {',
696 ' Offset: 0xF',
697 ' Type: X86_64_RELOC_GOT_LOAD (3)',
698 ' Symbol: ___stderrp',
699 ' }',
700 ' Relocation {',
701 ' Offset: 0x8',
702 ' Type: X86_64_RELOC_SIGNED (1)',
703 ' Symbol: L_str_0',
704 ' }',
705 ' }',
706 ']',
707 ].join('\n')
708 blocks := llvm_readobj_relocation_blocks(output)
709 assert blocks.len == 2
710 assert blocks[0].contains('___stderrp')
711 assert blocks[0].contains('GOT_LOAD')
712 assert !blocks[0].contains('L_str_0')
713 assert blocks[1].contains('L_str_0')
714 assert blocks[1].contains('SIGNED')
715 assert !blocks[1].contains('___stderrp')
716}
717
718fn find_first_available_tool(names []string) ?string {
719 for name in names {
720 if path := os.find_abs_path_of_executable(name) {
721 return path
722 }
723 }
724 return none
725}
726
727fn test_elf_writer_emits_x64_relocatable_object() {
728 path := temp_object_path('elf')
729 defer {
730 os.rm(path) or {}
731 }
732
733 mut obj := ElfObject.new()
734 obj.text_data << u8(0xc3)
735 obj.add_symbol('main', 0, true, 1)
736 obj.write(path)
737
738 data := os.read_bytes(path) or { panic(err) }
739 assert data.len > 64
740 assert data[0] == 0x7f
741 assert data[1] == `E`
742 assert data[2] == `L`
743 assert data[3] == `F`
744 assert data[4] == elfclass64
745 assert data[5] == elfdata2lsb
746 assert data[6] == ev_current
747 assert read_u16_le(data, 16) == et_rel
748 assert read_u16_le(data, 18) == em_x86_64
749 assert read_u32_le(data, 20) == ev_current
750 assert read_u64_le(data, 24) == 0
751 assert read_u64_le(data, 32) == 0
752 shoff := read_u64_le(data, 40)
753 assert shoff >= 64
754 assert read_u32_le(data, 48) == 0
755 assert read_u16_le(data, 52) == 64
756 assert read_u16_le(data, 54) == 0
757 assert read_u16_le(data, 56) == 0
758 assert read_u16_le(data, 58) == 64
759 assert read_u16_le(data, 60) == 8
760 assert read_u16_le(data, 62) == 7
761 assert_range_in_file(data, make_byte_range('ELF section headers', shoff, u64(read_u16_le(data,
762 58)) * u64(read_u16_le(data, 60))))
763}
764
765fn test_linux_tiny_elf_writer_emits_exec_program_headers_without_sections() {
766 $if linux {
767 path := os.join_path(os.vtmp_dir(), 'v2_x64_tiny_elf_${os.getpid()}')
768 defer {
769 os.rm(path) or {}
770 }
771
772 mut obj := ElfObject.new()
773 obj.text_data << [u8(0x31), 0xc0, 0xc3] // xor eax, eax; ret
774 obj.add_symbol('main', 0, true, 1)
775 mut linker := ElfTinyLinker{
776 elf: obj
777 }
778 linker.write(path) or { panic(err) }
779
780 data := os.read_bytes(path) or { panic(err) }
781 assert data.len > elf_tiny_text_file_offset(1)
782 assert data[0] == 0x7f
783 assert data[1] == `E`
784 assert data[2] == `L`
785 assert data[3] == `F`
786 assert read_u16_le(data, 16) == et_exec
787 assert read_u16_le(data, 18) == em_x86_64
788 assert read_u64_le(data, 24) == linux_tiny_base_vaddr + u64(elf_tiny_text_file_offset(1))
789 assert read_u64_le(data, 32) == 64
790 assert read_u64_le(data, 40) == 0
791 assert read_u16_le(data, 54) == 56
792 assert read_u16_le(data, 56) == 1
793 assert read_u16_le(data, 58) == 0
794 assert read_u16_le(data, 60) == 0
795 assert read_u32_le(data, 64) == pt_load
796 assert read_u32_le(data, 68) == pf_r | pf_x
797 }
798}
799
800fn test_linux_tiny_elf_writer_keeps_runtime_metadata_wx_clean() {
801 $if linux {
802 path := os.join_path(os.vtmp_dir(), 'v2_x64_tiny_elf_runtime_${os.getpid()}')
803 defer {
804 os.rm(path) or {}
805 }
806
807 mut obj := ElfObject.new()
808 obj.text_data << [u8(0xbf), 23, 0, 0, 0, 0xe8, 0, 0, 0, 0, 0xc3]
809 int_str_sym := obj.add_undefined('builtin__int__str')
810 obj.add_symbol('main', 0, true, 1)
811 obj.add_text_reloc(6, int_str_sym, r_x86_64_plt32, -4)
812 mut linker := ElfTinyLinker{
813 elf: obj
814 }
815 linker.write(path) or { panic(err) }
816
817 data := os.read_bytes(path) or { panic(err) }
818 assert data.len > elf_tiny_text_file_offset(2)
819 assert data.len < linux_tiny_page_align
820 assert read_u16_le(data, 16) == et_exec
821 assert read_u16_le(data, 56) == 2
822 assert read_u16_le(data, 58) == 0
823 assert read_u16_le(data, 60) == 0
824
825 text_ph := 64
826 assert read_u32_le(data, text_ph) == pt_load
827 assert read_u32_le(data, text_ph + 4) == pf_r | pf_x
828 assert read_u64_le(data, text_ph + 8) == 0
829 assert read_u64_le(data, text_ph + 16) == linux_tiny_base_vaddr
830 text_filesz := read_u64_le(data, text_ph + 32)
831 assert text_filesz == read_u64_le(data, text_ph + 40)
832 assert read_u64_le(data, text_ph + 48) == u64(linux_tiny_page_align)
833
834 rw_ph := text_ph + 56
835 assert read_u32_le(data, rw_ph) == pt_load
836 assert read_u32_le(data, rw_ph + 4) == pf_r | pf_w
837 rw_offset := read_u64_le(data, rw_ph + 8)
838 rw_vaddr := read_u64_le(data, rw_ph + 16)
839 rw_align := read_u64_le(data, rw_ph + 48)
840 assert read_u64_le(data, rw_ph + 32) == 0
841 assert read_u64_le(data, rw_ph + 40) == linux_tiny_int_str_arena_metadata_bytes
842 assert rw_align == u64(linux_tiny_page_align)
843 assert rw_vaddr % rw_align == rw_offset % rw_align
844 assert (read_u32_le(data, text_ph + 4) & u32(pf_w | pf_x)) != u32(pf_w | pf_x)
845 assert (read_u32_le(data, rw_ph + 4) & u32(pf_w | pf_x)) != u32(pf_w | pf_x)
846 }
847}
848
849fn test_linux_tiny_write_runtime_exits_on_non_positive_syscall_result() {
850 $if linux {
851 mut obj := ElfObject.new()
852 mut linker := ElfTinyLinker{
853 elf: obj
854 }
855 rt := linker.build_runtime({
856 'write': true
857 })
858 write_off := int(rt.symbols['write'] or { panic('missing write runtime symbol') })
859 write_bytes := rt.text[write_off..]
860 mut has_exit_group_failure := false
861 mut has_old_partial_success_return := false
862 for i in 0 .. write_bytes.len {
863 if i + 10 <= write_bytes.len && write_bytes[i] == 0xbf && write_bytes[i + 1] == 0x01
864 && write_bytes[i + 2] == 0 && write_bytes[i + 3] == 0 && write_bytes[i + 4] == 0
865 && write_bytes[i + 5] == 0xb8
866 && read_u32_le(write_bytes, i + 6) == linux_sys_exit_group {
867 has_exit_group_failure = true
868 }
869 if i + 4 <= write_bytes.len && write_bytes[i] == 0x49 && write_bytes[i + 1] == 0x0f
870 && write_bytes[i + 2] == 0x45 && write_bytes[i + 3] == 0xc2 {
871 has_old_partial_success_return = true
872 }
873 }
874 assert has_exit_group_failure
875 assert !has_old_partial_success_return
876 }
877}
878
879fn test_elf_writer_serializes_sections_symbols_and_relocations() {
880 path := temp_object_path('elf_relocs')
881 defer {
882 os.rm(path) or {}
883 }
884
885 mut obj := ElfObject.new()
886 obj.text_data << [u8(0xe8), 0, 0, 0, 0, 0x48, 0x8d, 0x05, 0, 0, 0, 0]
887 obj.rodata << 'hello'.bytes()
888 obj.rodata << 0
889 obj.data_data << [u8(1), 2, 3, 4]
890 call_sym := obj.add_undefined('calloc')
891 main_sym := obj.add_symbol('main', 0, true, 1)
892 rodata_sym := obj.add_symbol('L_str_0', 0, false, 3)
893 data_sym := obj.add_symbol('app_global', 2, false, 2)
894 obj.add_text_reloc(1, call_sym, r_x86_64_plt32, -4)
895 obj.add_text_reloc(8, rodata_sym, r_x86_64_pc32, -4)
896 obj.write(path)
897
898 data := os.read_bytes(path) or { panic(err) }
899 assert read_u64_le(data, 32) == 0
900 assert read_u16_le(data, 56) == 0
901 sections := elf_test_sections(data)
902 assert sections.len == 8
903 section_header_range := make_byte_range('ELF section headers', read_u64_le(data, 40),
904 u64(read_u16_le(data, 58)) * u64(read_u16_le(data, 60)))
905 assert_range_in_file(data, section_header_range)
906 mut ranges := [make_byte_range('ELF header', 0, u64(read_u16_le(data, 52))), section_header_range]
907 ranges << elf_test_payload_ranges(sections)
908 for range in ranges {
909 assert_range_in_file(data, range)
910 }
911 assert_ranges_do_not_overlap(ranges)
912 assert sections[0].name == ''
913 assert sections[0].name_idx == 0
914 assert sections[0].offset == 0
915 assert sections[0].size == 0
916 assert sections[1].name == '.text'
917 assert sections[1].type_ == sht_progbits
918 assert sections[1].flags == shf_alloc | shf_execinstr
919 assert sections[1].offset % 16 == 0
920 assert sections[1].size == u64(obj.text_data.len)
921 assert data[int(sections[1].offset)..int(sections[1].offset + sections[1].size)] == obj.text_data
922 assert sections[2].name == '.data'
923 assert sections[2].type_ == sht_progbits
924 assert sections[2].flags == shf_alloc | shf_write
925 assert sections[2].offset % 8 == 0
926 assert sections[2].size == u64(obj.data_data.len)
927 assert data[int(sections[2].offset)..int(sections[2].offset + sections[2].size)] == obj.data_data
928 assert sections[3].name == '.rodata'
929 assert sections[3].type_ == sht_progbits
930 assert sections[3].flags == shf_alloc
931 assert sections[3].offset % 4 == 0
932 assert sections[3].size == u64(obj.rodata.len)
933 assert data[int(sections[3].offset)..int(sections[3].offset + sections[3].size)] == obj.rodata
934 assert sections[4].name == '.symtab'
935 assert sections[4].type_ == sht_symtab
936 assert sections[4].link == 5
937 assert sections[4].info == 1
938 assert sections[4].align == 8
939 assert sections[4].entsize == 24
940 assert sections[4].size == u64(obj.symbols.len * 24)
941 assert sections[5].name == '.strtab'
942 assert sections[5].type_ == sht_strtab
943 assert sections[5].align == 1
944 assert sections[6].name == '.rela.text'
945 assert sections[6].type_ == sht_rela
946 assert sections[6].link == 4
947 assert sections[6].info == 1
948 assert sections[6].align == 8
949 assert sections[6].entsize == 24
950 assert sections[6].size == 48
951 assert sections[7].name == '.shstrtab'
952 assert sections[7].type_ == sht_strtab
953 for section in sections {
954 if section.name != '' {
955 assert u64(section.name_idx) < sections[7].size
956 }
957 if section.align > 1 && section.size > 0 {
958 assert section.offset % section.align == 0
959 }
960 }
961
962 sym_off := int(sections[4].offset)
963 str_off := int(sections[5].offset)
964 assert read_u32_le(data, sym_off) == 0
965 assert data[sym_off + 4] == 0
966 assert read_u16_le(data, sym_off + 6) == 0
967
968 call_off := sym_off + call_sym * 24
969 call_name_off := int(read_u32_le(data, call_off))
970 assert read_string(data, str_off + call_name_off) == 'calloc'
971 assert data[call_off + 4] == 0x10
972 assert read_u16_le(data, call_off + 6) == 0
973 assert read_u64_le(data, call_off + 8) == 0
974
975 main_off := sym_off + main_sym * 24
976 main_name_off := int(read_u32_le(data, main_off))
977 assert read_string(data, str_off + main_name_off) == 'main'
978 assert data[main_off + 4] == 0x12
979 assert read_u16_le(data, main_off + 6) == 1
980 assert read_u64_le(data, main_off + 8) == 0
981
982 rodata_off := sym_off + rodata_sym * 24
983 rodata_name_off := int(read_u32_le(data, rodata_off))
984 assert read_string(data, str_off + rodata_name_off) == 'L_str_0'
985 assert data[rodata_off + 4] == 0x11
986 assert read_u16_le(data, rodata_off + 6) == 3
987 assert read_u64_le(data, rodata_off + 8) == 0
988
989 data_off := sym_off + data_sym * 24
990 data_name_off := int(read_u32_le(data, data_off))
991 assert read_string(data, str_off + data_name_off) == 'app_global'
992 assert data[data_off + 4] == 0x11
993 assert read_u16_le(data, data_off + 6) == 2
994 assert read_u64_le(data, data_off + 8) == 2
995
996 rela_off := int(sections[6].offset)
997 assert read_u64_le(data, rela_off) == 1
998 assert read_u64_le(data, rela_off + 8) == (u64(call_sym) << 32) | u64(r_x86_64_plt32)
999 assert read_u64_le(data, rela_off + 16) == 0xffff_ffff_ffff_fffc
1000 assert read_u64_le(data, rela_off + 24) == 8
1001 assert read_u64_le(data, rela_off + 32) == (u64(rodata_sym) << 32) | u64(r_x86_64_pc32)
1002 assert read_u64_le(data, rela_off + 40) == 0xffff_ffff_ffff_fffc
1003}
1004
1005fn test_elf_writer_object_is_accepted_by_linux_binutils() {
1006 $if linux {
1007 readelf := os.find_abs_path_of_executable('readelf') or {
1008 println('skipping ${@FN}: readelf is not available')
1009 return
1010 }
1011 objdump := os.find_abs_path_of_executable('objdump') or {
1012 println('skipping ${@FN}: objdump is not available')
1013 return
1014 }
1015 path := temp_object_path('elf_binutils')
1016 defer {
1017 os.rm(path) or {}
1018 }
1019
1020 mut obj := ElfObject.new()
1021 obj.text_data << [u8(0xe8), 0, 0, 0, 0, 0x48, 0x8d, 0x05, 0, 0, 0, 0]
1022 obj.rodata << 'hello'.bytes()
1023 obj.rodata << 0
1024 obj.data_data << [u8(1), 2, 3, 4]
1025 call_sym := obj.add_undefined('calloc')
1026 rodata_sym := obj.add_symbol('L_str_0', 0, false, 3)
1027 obj.add_symbol('main', 0, true, 1)
1028 obj.add_text_reloc(1, call_sym, r_x86_64_plt32, -4)
1029 obj.add_text_reloc(8, rodata_sym, r_x86_64_pc32, -4)
1030 obj.write(path)
1031
1032 quoted_path := os.quoted_path(path)
1033 readelf_header := os.execute('${os.quoted_path(readelf)} -h ${quoted_path}')
1034 assert_command_ok(readelf_header, 'readelf -h')
1035 assert readelf_header.output.contains('ELF Header:')
1036 assert readelf_header.output.contains('Class: ELF64')
1037 assert readelf_header.output.contains("Data: 2's complement, little endian")
1038 assert readelf_header.output.contains('Type: REL (Relocatable file)')
1039 assert readelf_header.output.contains('Machine: Advanced Micro Devices X86-64')
1040 assert readelf_header.output.contains('Start of program headers: 0 (bytes into file)')
1041 assert readelf_header.output.contains('Number of program headers: 0')
1042 assert readelf_header.output.contains('Size of section headers: 64 (bytes)')
1043 assert readelf_header.output.contains('Section header string table index: 7')
1044
1045 readelf_sections := os.execute('${os.quoted_path(readelf)} --wide -S ${quoted_path}')
1046 assert_command_ok(readelf_sections, 'readelf -S')
1047 assert readelf_sections.output.contains('There are 8 section headers')
1048 assert readelf_sections.output.contains('.text')
1049 assert readelf_sections.output.contains('PROGBITS')
1050 assert readelf_sections.output.contains('AX')
1051 assert readelf_sections.output.contains('.data')
1052 assert readelf_sections.output.contains('WA')
1053 assert readelf_sections.output.contains('.rodata')
1054 assert readelf_sections.output.contains('.symtab')
1055 assert readelf_sections.output.contains('SYMTAB')
1056 assert readelf_sections.output.contains('.strtab')
1057 assert readelf_sections.output.contains('.rela.text')
1058 assert readelf_sections.output.contains('RELA')
1059 assert readelf_sections.output.contains('.shstrtab')
1060
1061 readelf_symbols := os.execute('${os.quoted_path(readelf)} --wide -s ${quoted_path}')
1062 assert_command_ok(readelf_symbols, 'readelf -s')
1063 assert readelf_symbols.output.contains("Symbol table '.symtab' contains 4 entries")
1064 assert output_has_line_with_all(readelf_symbols.output,
1065 ['NOTYPE', 'GLOBAL', 'UND', 'calloc'])
1066 assert output_has_line_with_all(readelf_symbols.output, ['FUNC', 'GLOBAL', '1', 'main'])
1067 assert output_has_line_with_all(readelf_symbols.output,
1068 ['OBJECT', 'GLOBAL', '3', 'L_str_0'])
1069
1070 readelf_relocs := os.execute('${os.quoted_path(readelf)} --wide -r ${quoted_path}')
1071 assert_command_ok(readelf_relocs, 'readelf -r')
1072 assert readelf_relocs.output.contains("Relocation section '.rela.text'")
1073 assert readelf_relocs.output.contains('R_X86_64_PLT32')
1074 assert readelf_relocs.output.contains('calloc')
1075 assert readelf_relocs.output.contains('R_X86_64_PC32')
1076 assert readelf_relocs.output.contains('L_str_0')
1077 assert readelf_relocs.output.contains('- 4')
1078
1079 objdump_header := os.execute('${os.quoted_path(objdump)} -f ${quoted_path}')
1080 assert_command_ok(objdump_header, 'objdump -f')
1081 assert objdump_header.output.contains('file format elf64-x86-64')
1082 assert objdump_header.output.contains('architecture: i386:x86-64')
1083 assert objdump_header.output.contains('HAS_RELOC')
1084 assert objdump_header.output.contains('HAS_SYMS')
1085
1086 objdump_relocs := os.execute('${os.quoted_path(objdump)} -r ${quoted_path}')
1087 assert_command_ok(objdump_relocs, 'objdump -r')
1088 assert objdump_relocs.output.contains('RELOCATION RECORDS FOR [.text]')
1089 assert objdump_relocs.output.contains('R_X86_64_PLT32')
1090 assert objdump_relocs.output.contains('calloc')
1091 assert objdump_relocs.output.contains('R_X86_64_PC32')
1092 assert objdump_relocs.output.contains('L_str_0')
1093 } $else {
1094 println('skipping ${@FN}: Linux binutils validation only runs on Linux')
1095 }
1096}
1097
1098fn test_macho_writer_emits_x64_relocatable_object() {
1099 path := temp_object_path('macho')
1100 defer {
1101 os.rm(path) or {}
1102 }
1103
1104 mut obj := MachOObject.new()
1105 obj.text_data << u8(0xc3)
1106 obj.add_symbol('_main', 0, true, 1)
1107 obj.write(path)
1108
1109 data := os.read_bytes(path) or { panic(err) }
1110 assert data.len > 64
1111 assert read_u32_le(data, 0) == macho_mh_magic_64
1112 assert read_u32_le(data, 4) == u32(macho_cpu_type_x86_64)
1113 assert read_u32_le(data, 8) == u32(macho_cpu_subtype_x86_64_all)
1114 assert read_u32_le(data, 12) == u32(macho_mh_object)
1115 assert read_u32_le(data, 16) == 2
1116 assert read_u32_le(data, 24) == 0
1117 assert read_u32_le(data, 28) == 0
1118
1119 load_cmds := macho_test_load_commands(data)
1120 assert load_cmds.len == 2
1121 seg_cmd_off := load_cmds[0]
1122 load_cmds_size := read_u32_le(data, 20)
1123 assert_range_in_file(data, make_byte_range('Mach-O header', 0, 32))
1124 assert_range_in_file(data, make_byte_range('Mach-O load commands', 32, u64(load_cmds_size)))
1125 assert read_u32_le(data, seg_cmd_off) == u32(macho_lc_segment_64)
1126 assert read_u32_le(data, seg_cmd_off + 4) == u32(72 + (80 * 3))
1127 assert read_fixed_string(data, seg_cmd_off + 8, 16) == ''
1128 assert read_u64_le(data, seg_cmd_off + 24) == 0
1129 assert read_u64_le(data, seg_cmd_off + 32) >= u64(obj.text_data.len)
1130 assert read_u64_le(data, seg_cmd_off + 40) % 16 == 0
1131 assert read_u64_le(data, seg_cmd_off + 48) >= u64(obj.text_data.len)
1132 assert read_u32_le(data, seg_cmd_off + 56) == 7
1133 assert read_u32_le(data, seg_cmd_off + 60) == 7
1134 assert read_u32_le(data, seg_cmd_off + 64) == 3
1135 assert read_u32_le(data, seg_cmd_off + 68) == 0
1136
1137 sections := macho_test_sections(data, seg_cmd_off)
1138 assert sections.len == 3
1139 assert sections[0].sectname == '__text'
1140 assert sections[0].segname == '__TEXT'
1141 assert sections[0].addr == 0
1142 assert sections[0].size == u64(obj.text_data.len)
1143 assert sections[0].offset % 16 == 0
1144 assert sections[0].align == 4
1145 assert sections[0].nreloc == 0
1146 assert sections[0].flags == 0x80000400
1147 assert data[int(sections[0].offset)..int(sections[0].offset + sections[0].size)] == obj.text_data
1148 assert sections[1].sectname == '__const'
1149 assert sections[1].segname == '__TEXT'
1150 assert sections[1].addr % 8 == 0
1151 assert sections[1].size == u64(obj.rodata.len)
1152 assert sections[1].offset % 8 == 0
1153 assert sections[1].align == 3
1154 assert sections[1].reloff == 0
1155 assert sections[1].nreloc == 0
1156 assert sections[2].sectname == '__data'
1157 assert sections[2].segname == '__DATA'
1158 assert sections[2].addr % 8 == 0
1159 assert sections[2].size == u64(obj.data_data.len)
1160 assert sections[2].offset % 8 == 0
1161 assert sections[2].align == 3
1162 assert sections[2].reloff == 0
1163 assert sections[2].nreloc == 0
1164 mut macho_ranges := [
1165 make_byte_range('Mach-O header and load commands', 0, 32 + u64(load_cmds_size)),
1166 ]
1167 macho_ranges << macho_test_section_ranges(sections)
1168
1169 symtab_cmd_off := load_cmds[1]
1170 assert read_u32_le(data, symtab_cmd_off) == u32(macho_lc_symtab)
1171 assert read_u32_le(data, symtab_cmd_off + 4) == 24
1172 sym_off := int(read_u32_le(data, symtab_cmd_off + 8))
1173 nsyms := int(read_u32_le(data, symtab_cmd_off + 12))
1174 str_off := int(read_u32_le(data, symtab_cmd_off + 16))
1175 str_size := int(read_u32_le(data, symtab_cmd_off + 20))
1176 assert nsyms == 1
1177 assert sym_off % 8 == 0
1178 assert str_off == sym_off + nsyms * 16
1179 assert str_size == obj.str_table.len
1180 macho_ranges << make_byte_range('Mach-O symbol table', u64(sym_off), u64(nsyms * 16))
1181 macho_ranges << make_byte_range('Mach-O string table', u64(str_off), u64(str_size))
1182 for range in macho_ranges {
1183 assert_range_in_file(data, range)
1184 }
1185 assert_ranges_do_not_overlap(macho_ranges)
1186 name_off := int(read_u32_le(data, sym_off))
1187 assert read_string(data, str_off + name_off) == '_main'
1188 assert data[sym_off + 4] == 0x0f
1189 assert data[sym_off + 5] == 1
1190 assert read_u16_le(data, sym_off + 6) == 0
1191 assert read_u64_le(data, sym_off + 8) == 0
1192}
1193
1194fn test_macho_writer_records_x64_text_relocations() {
1195 mut obj := MachOObject.new()
1196 call_sym := obj.add_undefined('_calloc')
1197 data_sym := obj.add_symbol('L_str_0', 0, false, 2)
1198 got_sym := obj.add_undefined('___stderrp')
1199 obj.add_reloc(1, call_sym, x86_64_reloc_branch, true, 2)
1200 obj.add_reloc(8, data_sym, x86_64_reloc_signed, true, 2)
1201 obj.add_reloc(15, got_sym, x86_64_reloc_got_load, true, 2)
1202
1203 assert obj.relocs.len == 3
1204 assert obj.relocs[0].addr == 1
1205 assert obj.relocs[0].sym_idx == call_sym
1206 assert obj.relocs[0].type_ == x86_64_reloc_branch
1207 assert obj.relocs[0].pcrel
1208 assert obj.relocs[0].length == 2
1209 assert obj.relocs[1].addr == 8
1210 assert obj.relocs[1].sym_idx == data_sym
1211 assert obj.relocs[1].type_ == x86_64_reloc_signed
1212 assert obj.relocs[1].pcrel
1213 assert obj.relocs[1].length == 2
1214 assert obj.relocs[2].addr == 15
1215 assert obj.relocs[2].sym_idx == got_sym
1216 assert obj.relocs[2].type_ == x86_64_reloc_got_load
1217 assert obj.relocs[2].pcrel
1218 assert obj.relocs[2].length == 2
1219}
1220
1221fn test_macho_writer_serializes_got_load_relocation_for_external_data_symbol() {
1222 path := temp_object_path('macho_got_load')
1223 defer {
1224 os.rm(path) or {}
1225 }
1226
1227 mut obj := MachOObject.new()
1228 obj.text_data << [u8(0x48), 0x8b, 0x05, 0, 0, 0, 0]
1229 stderr_name := ObjectFormat.macho.symbol_name('__stderrp')
1230 stderr_sym := obj.add_undefined(stderr_name)
1231 obj.add_reloc(3, stderr_sym, x86_64_reloc_got_load, true, 2)
1232 obj.write(path)
1233
1234 data := os.read_bytes(path) or { panic(err) }
1235 seg_cmd_off := 32
1236 text_section_off := seg_cmd_off + 72
1237 reloc_off := int(read_u32_le(data, text_section_off + 56))
1238 nreloc := int(read_u32_le(data, text_section_off + 60))
1239 assert nreloc == 1
1240 assert_range_in_file(data, make_byte_range('Mach-O GOT_LOAD relocation', u64(reloc_off),
1241 u64(nreloc * 8)))
1242
1243 reloc_addr := read_u32_le(data, reloc_off)
1244 reloc_info := read_u32_le(data, reloc_off + 4)
1245 assert reloc_addr == 3
1246 assert reloc_info & 0x00ff_ffff == u32(stderr_sym)
1247 assert ((reloc_info >> 24) & 1) == 1
1248 assert ((reloc_info >> 25) & 3) == 2
1249 assert ((reloc_info >> 27) & 1) == 1
1250 assert (reloc_info >> 28) == u32(x86_64_reloc_got_load)
1251
1252 symtab_cmd_off := seg_cmd_off + 72 + (80 * 3)
1253 sym_off := int(read_u32_le(data, symtab_cmd_off + 8))
1254 str_off := int(read_u32_le(data, symtab_cmd_off + 16))
1255 name_off := int(read_u32_le(data, sym_off))
1256 assert read_string(data, str_off + name_off) == '___stderrp'
1257 assert data[sym_off + 4] == 0x01
1258 assert data[sym_off + 5] == 0
1259}
1260
1261fn test_macho_codegen_loads_external_data_global_through_got_load() {
1262 mut mod := new_macho_global_codegen_test_module('__stderrp', .external)
1263 mut gen := Gen.new_with_format(&mod, .macho)
1264 gen.load_val_to_reg(0, 0)
1265
1266 assert gen.macho.text_data == [u8(0x48), 0x8b, 0x05, 0, 0, 0, 0]
1267 assert gen.macho.relocs.len == 1
1268 assert gen.macho.relocs[0].addr == 3
1269 assert gen.macho.relocs[0].type_ == x86_64_reloc_got_load
1270 assert gen.macho.relocs[0].type_ != x86_64_reloc_signed
1271 assert gen.macho.relocs[0].pcrel
1272 assert gen.macho.relocs[0].extern
1273 assert gen.macho.relocs[0].length == 2
1274 assert gen.macho.symbols[gen.macho.relocs[0].sym_idx].name == '___stderrp'
1275}
1276
1277fn test_macho_codegen_got_load_mov_supports_high_registers() {
1278 mut mod := new_macho_global_codegen_test_module('__stderrp', .external)
1279 mut gen := Gen.new_with_format(&mod, .macho)
1280 gen.load_val_to_reg(int(r8), 0)
1281
1282 assert gen.macho.text_data == [u8(0x4c), 0x8b, 0x05, 0, 0, 0, 0]
1283 assert gen.macho.relocs.len == 1
1284 assert gen.macho.relocs[0].type_ == x86_64_reloc_got_load
1285}
1286
1287fn test_macho_codegen_keeps_private_global_references_signed() {
1288 mut mod := new_macho_global_codegen_test_module('app_global', .private)
1289 mut gen := Gen.new_with_format(&mod, .macho)
1290 gen.load_val_to_reg(0, 0)
1291
1292 assert gen.macho.text_data == [u8(0x48), 0x8d, 0x05, 0, 0, 0, 0]
1293 assert gen.macho.relocs.len == 1
1294 assert gen.macho.relocs[0].addr == 3
1295 assert gen.macho.relocs[0].type_ == x86_64_reloc_signed
1296 assert gen.macho.relocs[0].type_ != x86_64_reloc_got_load
1297 assert gen.macho.relocs[0].pcrel
1298 assert gen.macho.relocs[0].extern
1299 assert gen.macho.relocs[0].length == 2
1300 assert gen.macho.symbols[gen.macho.relocs[0].sym_idx].name == '_app_global'
1301}
1302
1303fn test_elf_codegen_loads_func_ref_as_rip_relative_address() {
1304 mut mod := new_func_ref_codegen_test_module('_anon_fn_0')
1305 mut gen := Gen.new_with_format(&mod, .elf)
1306 gen.load_val_to_reg(0, 0)
1307
1308 assert gen.elf.text_data == [u8(0x48), 0x8d, 0x05, 0, 0, 0, 0]
1309 assert gen.elf.text_relocs.len == 1
1310 assert gen.elf.text_relocs[0].offset == 3
1311 assert gen.elf.text_relocs[0].info & u64(0xffff_ffff) == u64(r_x86_64_pc32)
1312 sym_idx := int(gen.elf.text_relocs[0].info >> 32)
1313 assert gen.elf.symbols[sym_idx].name == '_anon_fn_0'
1314 assert gen.elf.text_relocs[0].addend == i64(-4)
1315}
1316
1317fn test_macho_codegen_loads_func_ref_as_rip_relative_address() {
1318 mut mod := new_func_ref_codegen_test_module('_anon_fn_0')
1319 mut gen := Gen.new_with_format(&mod, .macho)
1320 gen.load_val_to_reg(0, 0)
1321
1322 assert gen.macho.text_data == [u8(0x48), 0x8d, 0x05, 0, 0, 0, 0]
1323 assert gen.macho.relocs.len == 1
1324 assert gen.macho.relocs[0].addr == 3
1325 assert gen.macho.relocs[0].type_ == x86_64_reloc_signed
1326 assert gen.macho.relocs[0].pcrel
1327 assert gen.macho.relocs[0].extern
1328 assert gen.macho.relocs[0].length == 2
1329 assert gen.macho.symbols[gen.macho.relocs[0].sym_idx].name == '__anon_fn_0'
1330}
1331
1332fn test_coff_codegen_loads_func_ref_as_rip_relative_address() {
1333 mut mod := new_func_ref_codegen_test_module('_anon_fn_0')
1334 mut gen := Gen.new_with_format(&mod, .coff)
1335 gen.load_val_to_reg(0, 0)
1336
1337 assert gen.coff.text_data == [u8(0x48), 0x8d, 0x05, 0, 0, 0, 0]
1338 assert gen.coff.text_relocs.len == 1
1339 assert gen.coff.text_relocs[0].offset == 3
1340 assert gen.coff.text_relocs[0].type_ == coff_image_rel_amd64_rel32
1341 assert gen.coff.symbols[gen.coff.text_relocs[0].sym_idx].name == '_anon_fn_0'
1342}
1343
1344fn test_windows_coff_codegen_omits_unused_global_data() {
1345 mut mod := new_windows_coff_global_codegen_test_module()
1346 mut gen := Gen.new_with_format_and_abi(&mod, .coff, .windows)
1347 gen.gen()
1348
1349 assert gen.coff.data_data == [u8(1), 2, 3, 4, 5, 6, 7, 8]
1350 keep_sym := coff_test_symbol(gen.coff, 'keep_global') or {
1351 panic('missing referenced global symbol')
1352 }
1353 assert keep_sym.section == 3
1354 assert keep_sym.value == 0
1355 assert coff_test_symbol(gen.coff, 'drop_global') == none
1356 assert gen.coff.text_relocs.len == 1
1357 reloc := gen.coff.text_relocs[0]
1358 assert gen.coff.symbols[reloc.sym_idx].name == 'keep_global'
1359 assert gen.coff.symbols[reloc.sym_idx].section == 3
1360}
1361
1362fn test_windows_coff_codegen_keeps_globals_when_reference_scan_is_ambiguous() {
1363 mut mod := new_windows_coff_global_codegen_test_module()
1364 ghost_id := mod.values.len
1365 mod.values << mir.Value{
1366 id: ghost_id
1367 typ: mod.values[0].typ
1368 index: 99
1369 kind: .global
1370 name: 'ghost_global'
1371 }
1372 mod.instrs[0].operands = [ghost_id]
1373 mut gen := Gen.new_with_format_and_abi(&mod, .coff, .windows)
1374 gen.gen()
1375
1376 assert gen.coff.data_data == [
1377 u8(1),
1378 2,
1379 3,
1380 4,
1381 5,
1382 6,
1383 7,
1384 8,
1385 9,
1386 10,
1387 11,
1388 12,
1389 13,
1390 14,
1391 15,
1392 16,
1393 ]
1394 assert coff_test_symbol(gen.coff, 'keep_global') != none
1395 assert coff_test_symbol(gen.coff, 'drop_global') != none
1396}
1397
1398fn test_non_windows_coff_codegen_preserves_existing_global_emission() {
1399 mut mod := new_windows_coff_global_codegen_test_module()
1400 mut gen := Gen.new_with_format(&mod, .coff)
1401 gen.gen()
1402
1403 assert gen.coff.data_data == [
1404 u8(1),
1405 2,
1406 3,
1407 4,
1408 5,
1409 6,
1410 7,
1411 8,
1412 9,
1413 10,
1414 11,
1415 12,
1416 13,
1417 14,
1418 15,
1419 16,
1420 ]
1421 assert coff_test_symbol(gen.coff, 'keep_global') != none
1422 assert coff_test_symbol(gen.coff, 'drop_global') != none
1423}
1424
1425fn test_macho_codegen_keeps_local_rodata_references_signed() {
1426 mut ts := ssa.TypeStore.new()
1427 i64_t := ts.get_int(64)
1428 mut mod := mir.Module{
1429 type_store: unsafe { *ts }
1430 values: [
1431 mir.Value{
1432 id: 0
1433 typ: i64_t
1434 index: 0
1435 kind: .constant
1436 name: '"hello"'
1437 },
1438 ]
1439 }
1440 mut gen := Gen.new_with_format(&mod, .macho)
1441 gen.load_val_to_reg(0, 0)
1442
1443 assert gen.macho.text_data == [u8(0x48), 0x8d, 0x05, 0, 0, 0, 0]
1444 assert gen.macho.relocs.len == 1
1445 assert gen.macho.relocs[0].addr == 3
1446 assert gen.macho.relocs[0].type_ == x86_64_reloc_signed
1447 assert gen.macho.symbols[gen.macho.relocs[0].sym_idx].name.starts_with('L_str_')
1448}
1449
1450fn test_macho_codegen_keeps_external_calls_as_branch_relocations() {
1451 mut ts := ssa.TypeStore.new()
1452 mut mod := mir.Module{
1453 type_store: unsafe { *ts }
1454 }
1455 mut gen := Gen.new_with_format(&mod, .macho)
1456 asm_call_rel32(mut gen)
1457 sym_idx := gen.add_undefined('calloc')
1458 gen.add_call_reloc(sym_idx)
1459 gen.emit_u32(0)
1460
1461 assert gen.macho.text_data == [u8(0xe8), 0, 0, 0, 0]
1462 assert gen.macho.relocs.len == 1
1463 assert gen.macho.relocs[0].addr == 1
1464 assert gen.macho.relocs[0].type_ == x86_64_reloc_branch
1465 assert gen.macho.relocs[0].type_ != x86_64_reloc_got_load
1466 assert gen.macho.symbols[gen.macho.relocs[0].sym_idx].name == '_calloc'
1467}
1468
1469fn test_macho_object_format_prefixes_external_symbols() {
1470 assert ObjectFormat.macho.symbol_name('main') == '_main'
1471 assert ObjectFormat.macho.symbol_name('calloc') == '_calloc'
1472 assert ObjectFormat.macho.symbol_name('_main') == '__main'
1473 assert ObjectFormat.macho.symbol_name('__stdoutp') == '___stdoutp'
1474 assert ObjectFormat.macho.symbol_name('__stderrp') == '___stderrp'
1475 assert ObjectFormat.macho.symbol_name('__stdinp') == '___stdinp'
1476 assert ObjectFormat.macho.symbol_name('__error') == '___error'
1477 assert ObjectFormat.macho.symbol_name('L_str_0') == 'L_str_0'
1478 assert ObjectFormat.elf.symbol_name('main') == 'main'
1479 assert ObjectFormat.elf.symbol_name('calloc') == 'calloc'
1480 assert ObjectFormat.elf.symbol_name('__stdoutp') == '__stdoutp'
1481 assert ObjectFormat.coff.symbol_name('main') == 'main'
1482 assert ObjectFormat.coff.symbol_name('calloc') == 'calloc'
1483 assert ObjectFormat.coff.symbol_name('__stdoutp') == '__stdoutp'
1484}
1485
1486fn test_elf_writer_reuses_undefined_symbol_when_it_is_defined_later() {
1487 mut obj := ElfObject.new()
1488 undef_idx := obj.add_undefined('app_global')
1489 def_idx := obj.add_symbol('app_global', 16, false, 2)
1490
1491 assert def_idx == undef_idx
1492 assert obj.symbols[def_idx].name == 'app_global'
1493 assert obj.symbols[def_idx].shndx == 2
1494 assert obj.symbols[def_idx].value == 16
1495}
1496
1497fn test_elf_writer_reuses_defined_symbol_when_it_is_referenced_later() {
1498 mut obj := ElfObject.new()
1499 def_idx := obj.add_symbol('app_global', 16, false, 2)
1500 undef_idx := obj.add_undefined('app_global')
1501
1502 assert undef_idx == def_idx
1503 assert obj.symbols[undef_idx].name == 'app_global'
1504 assert obj.symbols[undef_idx].shndx == 2
1505 assert obj.symbols[undef_idx].value == 16
1506}
1507
1508fn test_macho_writer_reuses_undefined_symbol_when_it_is_defined_later() {
1509 mut obj := MachOObject.new()
1510 undef_idx := obj.add_undefined('_app_global')
1511 def_idx := obj.add_symbol('_app_global', 24, true, 3)
1512
1513 assert def_idx == undef_idx
1514 assert obj.symbols[def_idx].name == '_app_global'
1515 assert obj.symbols[def_idx].sect == 3
1516 assert obj.symbols[def_idx].value == 24
1517}
1518
1519fn test_macho_writer_serializes_text_relocations_and_symbols() {
1520 path := temp_object_path('macho_relocs')
1521 defer {
1522 os.rm(path) or {}
1523 }
1524
1525 mut obj := MachOObject.new()
1526 obj.text_data << [u8(0xe8), 0, 0, 0, 0, 0x48, 0x8d, 0x05, 0, 0, 0, 0]
1527 call_name := ObjectFormat.macho.symbol_name('__stdoutp')
1528 local_name := ObjectFormat.macho.symbol_name('L_str_0')
1529 call_sym := obj.add_undefined(call_name)
1530 data_sym := obj.add_symbol(local_name, 0, false, 2)
1531 obj.rodata << 'hello'.bytes()
1532 obj.rodata << 0
1533 obj.add_reloc(1, call_sym, x86_64_reloc_branch, true, 2)
1534 obj.add_reloc(8, data_sym, x86_64_reloc_signed, true, 2)
1535 obj.write(path)
1536
1537 data := os.read_bytes(path) or { panic(err) }
1538 seg_cmd_off := 32
1539 text_section_off := seg_cmd_off + 72
1540 load_cmds_size := read_u32_le(data, 20)
1541 assert_range_in_file(data, make_byte_range('Mach-O header and load commands', 0, 32 +
1542 u64(load_cmds_size)))
1543 reloc_off := int(read_u32_le(data, text_section_off + 56))
1544 nreloc := int(read_u32_le(data, text_section_off + 60))
1545 assert nreloc == 2
1546 assert_range_in_file(data, make_byte_range('Mach-O text relocations', u64(reloc_off),
1547 u64(nreloc * 8)))
1548
1549 reloc0_addr := read_u32_le(data, reloc_off)
1550 reloc0_info := read_u32_le(data, reloc_off + 4)
1551 reloc1_addr := read_u32_le(data, reloc_off + 8)
1552 reloc1_info := read_u32_le(data, reloc_off + 12)
1553 assert reloc0_addr == 1
1554 assert reloc0_info & 0x00ff_ffff == u32(call_sym)
1555 assert ((reloc0_info >> 24) & 1) == 1
1556 assert ((reloc0_info >> 25) & 3) == 2
1557 assert ((reloc0_info >> 27) & 1) == 1
1558 assert (reloc0_info >> 28) == u32(x86_64_reloc_branch)
1559 assert reloc1_addr == 8
1560 assert reloc1_info & 0x00ff_ffff == u32(data_sym)
1561 assert ((reloc1_info >> 24) & 1) == 1
1562 assert ((reloc1_info >> 25) & 3) == 2
1563 assert ((reloc1_info >> 27) & 1) == 1
1564 assert (reloc1_info >> 28) == u32(x86_64_reloc_signed)
1565
1566 symtab_cmd_off := seg_cmd_off + 72 + (80 * 3)
1567 assert read_u32_le(data, symtab_cmd_off) == u32(macho_lc_symtab)
1568 sym_off := int(read_u32_le(data, symtab_cmd_off + 8))
1569 nsyms := int(read_u32_le(data, symtab_cmd_off + 12))
1570 str_off := int(read_u32_le(data, symtab_cmd_off + 16))
1571 str_size := int(read_u32_le(data, symtab_cmd_off + 20))
1572 assert nsyms == 2
1573 assert str_size > 1
1574 mut macho_reloc_ranges := [
1575 make_byte_range('Mach-O header and load commands', 0, 32 + u64(load_cmds_size)),
1576 make_byte_range('Mach-O text section', u64(read_u32_le(data, text_section_off + 48)), read_u64_le(data,
1577
1578 text_section_off + 40)),
1579 make_byte_range('Mach-O text relocations', u64(reloc_off), u64(nreloc * 8)),
1580 make_byte_range('Mach-O symbol table', u64(sym_off), u64(nsyms * 16)),
1581 make_byte_range('Mach-O string table', u64(str_off), u64(str_size)),
1582 ]
1583 for range in macho_reloc_ranges {
1584 assert_range_in_file(data, range)
1585 }
1586 assert_ranges_do_not_overlap(macho_reloc_ranges)
1587
1588 call_name_off := int(read_u32_le(data, sym_off))
1589 local_name_off := int(read_u32_le(data, sym_off + 16))
1590 assert read_string(data, str_off + call_name_off) == call_name
1591 assert read_string(data, str_off + local_name_off) == local_name
1592 assert data[sym_off + 4] == 0x01
1593 assert data[sym_off + 5] == 0
1594 assert data[sym_off + 16 + 4] == 0x0e
1595 assert data[sym_off + 16 + 5] == 2
1596}
1597
1598fn test_macho_tiny_object_writer_prunes_unreachable_text_and_data() {
1599 path := temp_object_path('macho_tiny_pruned')
1600 defer {
1601 os.rm(path) or {}
1602 }
1603
1604 mut obj := MachOObject.new()
1605 obj.text_data << [u8(0xe8), 0, 0, 0, 0, 0x48, 0x8d, 0x05, 0, 0, 0, 0, 0xc3]
1606 main_sym := obj.add_symbol('_main', 0, true, 1)
1607 helper_start := obj.text_data.len
1608 obj.text_data << u8(0xc3)
1609 helper_sym := obj.add_symbol('_helper', u64(helper_start), true, 1)
1610 unused_start := obj.text_data.len
1611 obj.text_data << u8(0xc3)
1612 obj.add_symbol('_unused', u64(unused_start), true, 1)
1613 keep_sym := obj.add_symbol('L_keep', 0, false, 2)
1614 obj.rodata << 'ok'.bytes()
1615 obj.rodata << 0
1616 drop_start := obj.rodata.len
1617 obj.add_symbol('L_drop', u64(drop_start), false, 2)
1618 obj.rodata << 'drop'.bytes()
1619 obj.rodata << 0
1620 obj.add_reloc(1, helper_sym, x86_64_reloc_branch, true, 2)
1621 obj.add_reloc(8, keep_sym, x86_64_reloc_signed, true, 2)
1622
1623 mut writer := MachOTinyObjectWriter{
1624 macho: obj
1625 }
1626 writer.write(path) or { panic(err) }
1627
1628 data := os.read_bytes(path) or { panic(err) }
1629 load_cmds := macho_test_load_commands(data)
1630 sections := macho_test_sections(data, load_cmds[0])
1631 assert sections[0].size == u64(helper_start + 1)
1632 assert sections[1].size == u64(3)
1633 assert data[int(sections[0].offset)..int(sections[0].offset + sections[0].size)] == obj.text_data[..
1634 helper_start + 1]
1635 assert data[int(sections[1].offset)..int(sections[1].offset + sections[1].size)] == 'ok\0'.bytes()
1636 assert sections[0].nreloc == 2
1637
1638 symbols := macho_test_symbols(data)
1639 names := symbols.map(it.name)
1640 assert '_main' in names
1641 assert '_helper' in names
1642 assert 'L_keep' in names
1643 assert '_unused' !in names
1644 assert 'L_drop' !in names
1645 main_out := macho_test_symbol_by_name(symbols, '_main') or {
1646 assert false, 'missing _main in Mach-O tiny output'
1647 return
1648 }
1649 helper_out := macho_test_symbol_by_name(symbols, '_helper') or {
1650 assert false, 'missing _helper in Mach-O tiny output'
1651 return
1652 }
1653 keep_out := macho_test_symbol_by_name(symbols, 'L_keep') or {
1654 assert false, 'missing L_keep in Mach-O tiny output'
1655 return
1656 }
1657 assert main_out.type_ == 0x0f
1658 assert main_out.sect == 1
1659 assert helper_out.type_ == 0x0e
1660 assert helper_out.sect == 1
1661 assert keep_out.type_ == 0x0e
1662 assert keep_out.sect == 2
1663 assert main_sym == 0
1664}
1665
1666fn test_macho_tiny_object_writer_preserves_undefined_externals_and_remaps_relocations() {
1667 path := temp_object_path('macho_tiny_undefined_remap')
1668 defer {
1669 os.rm(path) or {}
1670 }
1671
1672 mut obj := MachOObject.new()
1673 obj.text_data << [u8(0xe8), 0, 0, 0, 0, 0x48, 0x8b, 0x05, 0, 0, 0, 0, 0x48, 0x8d, 0x05, 0,
1674 0, 0, 0, 0xc3]
1675 unused_start := obj.text_data.len
1676 obj.text_data << u8(0xc3)
1677 obj.add_symbol('_unused', u64(unused_start), true, 1)
1678 obj.add_undefined('_unused_external')
1679 call_sym := obj.add_undefined('_calloc')
1680 obj.add_symbol('_main', 0, true, 1)
1681 got_load_sym := obj.add_undefined('___stderrp')
1682 got_sym := obj.add_undefined('___error')
1683 obj.add_reloc(1, call_sym, x86_64_reloc_branch, true, 2)
1684 obj.add_reloc(8, got_load_sym, x86_64_reloc_got_load, true, 2)
1685 obj.add_reloc(15, got_sym, x86_64_reloc_got, true, 2)
1686
1687 mut writer := MachOTinyObjectWriter{
1688 macho: obj
1689 }
1690 writer.write(path) or { panic(err) }
1691
1692 data := os.read_bytes(path) or { panic(err) }
1693 symbols := macho_test_symbols(data)
1694 names := symbols.map(it.name)
1695 assert '_main' in names
1696 assert '_calloc' in names
1697 assert '___stderrp' in names
1698 assert '___error' in names
1699 assert '_unused' !in names
1700 assert '_unused_external' !in names
1701 for name in ['_calloc', '___stderrp', '___error'] {
1702 symbol := macho_test_symbol_by_name(symbols, name) or {
1703 assert false, 'missing ${name} in Mach-O tiny output'
1704 return
1705 }
1706 assert symbol.type_ == 0x01
1707 assert symbol.sect == 0
1708 assert symbol.value == 0
1709 }
1710
1711 relocs := macho_test_text_relocations(data)
1712 assert relocs.len == 3
1713 assert relocs[0].addr == 1
1714 assert relocs[0].type_ == x86_64_reloc_branch
1715 assert symbols[relocs[0].sym_idx].name == '_calloc'
1716 assert relocs[0].sym_idx != call_sym
1717 assert relocs[1].addr == 8
1718 assert relocs[1].type_ == x86_64_reloc_got_load
1719 assert symbols[relocs[1].sym_idx].name == '___stderrp'
1720 assert relocs[1].sym_idx != got_load_sym
1721 assert relocs[2].addr == 15
1722 assert relocs[2].type_ == x86_64_reloc_got
1723 assert symbols[relocs[2].sym_idx].name == '___error'
1724 assert relocs[2].sym_idx != got_sym
1725 for reloc in relocs {
1726 assert reloc.pcrel
1727 assert reloc.length == 2
1728 assert reloc.extern
1729 }
1730}
1731
1732fn test_macho_tiny_object_writer_resolves_builtin_string_plus_with_libsystem_runtime() {
1733 path := temp_object_path('macho_tiny_string_plus_runtime')
1734 defer {
1735 os.rm(path) or {}
1736 }
1737
1738 mut obj := MachOObject.new()
1739 obj.text_data << [u8(0xe8), 0, 0, 0, 0, 0xc3]
1740 string_plus_sym := obj.add_undefined('_builtin__string__+')
1741 obj.add_symbol('_main', 0, true, 1)
1742 obj.add_reloc(1, string_plus_sym, x86_64_reloc_branch, true, 2)
1743
1744 mut writer := MachOTinyObjectWriter{
1745 macho: obj
1746 }
1747 writer.write(path) or { panic(err) }
1748
1749 data := os.read_bytes(path) or { panic(err) }
1750 symbols := macho_test_symbols(data)
1751 names := symbols.map(it.name)
1752 assert '_main' in names
1753 assert '_builtin__string__+' in names
1754 assert '_malloc' in names
1755 assert '_exit' in names
1756
1757 string_plus_out := macho_test_symbol_by_name(symbols, '_builtin__string__+') or {
1758 assert false, 'missing _builtin__string__+ in Mach-O tiny output'
1759 return
1760 }
1761 assert string_plus_out.type_ == 0x0e
1762 assert string_plus_out.sect == 1
1763 assert string_plus_out.value > 0
1764 for name in ['_malloc', '_exit'] {
1765 symbol := macho_test_symbol_by_name(symbols, name) or {
1766 assert false, 'missing ${name} in Mach-O tiny output'
1767 return
1768 }
1769 assert symbol.type_ == 0x01
1770 assert symbol.sect == 0
1771 assert symbol.value == 0
1772 }
1773
1774 relocs := macho_test_text_relocations(data)
1775 assert relocs.len == 2
1776 assert relocs[0].addr > int(string_plus_out.value)
1777 assert symbols[relocs[0].sym_idx].name == '_malloc'
1778 assert relocs[1].addr > int(string_plus_out.value)
1779 assert relocs[1].addr > relocs[0].addr
1780 assert symbols[relocs[1].sym_idx].name == '_exit'
1781 mut reloc_names := []string{}
1782 for reloc in relocs {
1783 reloc_names << symbols[reloc.sym_idx].name
1784 assert reloc.pcrel
1785 assert reloc.length == 2
1786 assert reloc.extern
1787 assert reloc.type_ == x86_64_reloc_branch
1788 }
1789 reloc_names.sort()
1790 assert reloc_names == ['_exit', '_malloc']
1791
1792 text := macho_test_text_bytes(data)
1793 expected_disp := i64(string_plus_out.value) - i64(1 + 4)
1794 assert read_u32_le(text, 1) == u32(i32(expected_disp))
1795}
1796
1797fn test_macho_tiny_object_writer_resolves_builtin_i64_str_with_libsystem_runtime() {
1798 path := temp_object_path('macho_tiny_i64_str_runtime')
1799 defer {
1800 os.rm(path) or {}
1801 }
1802
1803 mut obj := MachOObject.new()
1804 obj.text_data << [u8(0xe8), 0, 0, 0, 0, 0xc3]
1805 i64_str_sym := obj.add_undefined('_builtin__i64__str')
1806 obj.add_symbol('_main', 0, true, 1)
1807 obj.add_reloc(1, i64_str_sym, x86_64_reloc_branch, true, 2)
1808
1809 mut writer := MachOTinyObjectWriter{
1810 macho: obj
1811 }
1812 writer.write(path) or { panic(err) }
1813
1814 data := os.read_bytes(path) or { panic(err) }
1815 symbols := macho_test_symbols(data)
1816 i64_str_out := macho_test_symbol_by_name(symbols, '_builtin__i64__str') or {
1817 assert false, 'missing _builtin__i64__str in Mach-O tiny output'
1818 return
1819 }
1820 assert i64_str_out.type_ == 0x0e
1821 assert i64_str_out.sect == 1
1822 assert i64_str_out.value > 0
1823
1824 relocs := macho_test_text_relocations(data)
1825 assert relocs.len == 2
1826 mut reloc_names := []string{}
1827 for reloc in relocs {
1828 reloc_names << symbols[reloc.sym_idx].name
1829 assert reloc.pcrel
1830 assert reloc.length == 2
1831 assert reloc.extern
1832 assert reloc.type_ == x86_64_reloc_branch
1833 assert reloc.addr > int(i64_str_out.value)
1834 }
1835 reloc_names.sort()
1836 assert reloc_names == ['_exit', '_malloc']
1837
1838 text := macho_test_text_bytes(data)
1839 expected_disp := i64(i64_str_out.value) - i64(1 + 4)
1840 assert read_u32_le(text, 1) == u32(i32(expected_disp))
1841}
1842
1843fn test_macho_tiny_i64_str_helper_uses_signed_64_bit_decimal_runtime() {
1844 path := temp_object_path('macho_tiny_i64_str_runtime_bytes')
1845 defer {
1846 os.rm(path) or {}
1847 }
1848
1849 mut obj := MachOObject.new()
1850 obj.text_data << [u8(0xe8), 0, 0, 0, 0, 0xc3]
1851 i64_str_sym := obj.add_undefined('_builtin__i64__str')
1852 obj.add_symbol('_main', 0, true, 1)
1853 obj.add_reloc(1, i64_str_sym, x86_64_reloc_branch, true, 2)
1854
1855 mut writer := MachOTinyObjectWriter{
1856 macho: obj
1857 }
1858 writer.write(path) or { panic(err) }
1859
1860 data := os.read_bytes(path) or { panic(err) }
1861 symbols := macho_test_symbols(data)
1862 i64_str_out := macho_test_symbol_by_name(symbols, '_builtin__i64__str') or {
1863 assert false, 'missing _builtin__i64__str in Mach-O tiny output'
1864 return
1865 }
1866 text := macho_test_text_bytes(data)
1867 helper := text[int(i64_str_out.value)..]
1868
1869 assert helper[0..7] == [u8(0x57), 0xbf, 0x20, 0, 0, 0, 0xe8]
1870 assert helper[32] == 0x5f
1871 assert_macho_test_bytes_contains(helper, [u8(0x48), 0x89, 0xf8],
1872 'i64 str helper must read the full i64 argument from rdi')
1873 assert_macho_test_bytes_contains(helper, [u8(0x41), 0xb2, 0x01],
1874 'i64 str helper must remember signed negative inputs')
1875 assert_macho_test_bytes_contains(helper, [u8(0x48), 0xf7, 0xd8],
1876 'i64 str helper must negate the full 64-bit value, preserving i64_min as unsigned magnitude')
1877 assert_macho_test_bytes_contains(helper, [u8(0x48), 0xf7, 0xf1],
1878 'i64 str helper must divide the full 64-bit magnitude by 10')
1879 assert_macho_test_bytes_contains(helper, [u8(0x41), 0xc6, 0x00, 0x2d],
1880 'i64 str helper must emit a minus sign for signed negative inputs')
1881 assert_macho_test_bytes_contains(helper, [u8(0x4c), 0x89, 0xc0, 0x4c, 0x89, 0xca, 0xc3],
1882 'i64 str helper must return ptr in rax and len in rdx')
1883
1884 relocs := macho_test_text_relocations(data)
1885 mut malloc_relocs := 0
1886 mut exit_relocs := 0
1887 for reloc in relocs {
1888 name := symbols[reloc.sym_idx].name
1889 if name == '_malloc' {
1890 malloc_relocs++
1891 assert reloc.addr == u32(i64_str_out.value + 7)
1892 } else if name == '_exit' {
1893 exit_relocs++
1894 assert reloc.addr == u32(i64_str_out.value + 26)
1895 } else {
1896 assert false, 'unexpected Mach-O tiny i64 str helper relocation to ${name}'
1897 }
1898 assert reloc.extern
1899 assert reloc.pcrel
1900 assert reloc.length == 2
1901 assert reloc.type_ == x86_64_reloc_branch
1902 }
1903 assert malloc_relocs == 1
1904 assert exit_relocs == 1
1905}
1906
1907fn test_macho_tiny_object_writer_resolves_i64_str_and_string_plus_locally() {
1908 path := temp_object_path('macho_tiny_i64_str_string_plus_runtime')
1909 defer {
1910 os.rm(path) or {}
1911 }
1912
1913 mut obj := MachOObject.new()
1914 obj.text_data << [u8(0xe8), 0, 0, 0, 0, 0xe8, 0, 0, 0, 0, 0xc3]
1915 i64_str_sym := obj.add_undefined('_builtin__i64__str')
1916 string_plus_sym := obj.add_undefined('_builtin__string__+')
1917 obj.add_symbol('_main', 0, true, 1)
1918 obj.add_reloc(1, i64_str_sym, x86_64_reloc_branch, true, 2)
1919 obj.add_reloc(6, string_plus_sym, x86_64_reloc_branch, true, 2)
1920
1921 mut writer := MachOTinyObjectWriter{
1922 macho: obj
1923 }
1924 writer.write(path) or { panic(err) }
1925
1926 data := os.read_bytes(path) or { panic(err) }
1927 symbols := macho_test_symbols(data)
1928 i64_str_out := macho_test_symbol_by_name(symbols, '_builtin__i64__str') or {
1929 assert false, 'missing _builtin__i64__str in Mach-O tiny output'
1930 return
1931 }
1932 string_plus_out := macho_test_symbol_by_name(symbols, '_builtin__string__+') or {
1933 assert false, 'missing _builtin__string__+ in Mach-O tiny output'
1934 return
1935 }
1936 assert i64_str_out.type_ == 0x0e
1937 assert i64_str_out.sect == 1
1938 assert i64_str_out.value > 0
1939 assert string_plus_out.type_ == 0x0e
1940 assert string_plus_out.sect == 1
1941 assert string_plus_out.value > i64_str_out.value
1942
1943 relocs := macho_test_text_relocations(data)
1944 assert relocs.len == 4
1945 mut reloc_names := []string{}
1946 for reloc in relocs {
1947 reloc_names << symbols[reloc.sym_idx].name
1948 assert reloc.pcrel
1949 assert reloc.length == 2
1950 assert reloc.extern
1951 assert reloc.type_ == x86_64_reloc_branch
1952 }
1953 reloc_names.sort()
1954 assert reloc_names == ['_exit', '_exit', '_malloc', '_malloc']
1955
1956 text := macho_test_text_bytes(data)
1957 expected_i64_disp := i64(i64_str_out.value) - i64(1 + 4)
1958 expected_string_plus_disp := i64(string_plus_out.value) - i64(6 + 4)
1959 assert read_u32_le(text, 1) == u32(i32(expected_i64_disp))
1960 assert read_u32_le(text, 6) == u32(i32(expected_string_plus_disp))
1961}
1962
1963fn test_macho_tiny_object_writer_keeps_referenced_rodata_and_data() {
1964 path := temp_object_path('macho_tiny_rodata_data')
1965 defer {
1966 os.rm(path) or {}
1967 }
1968
1969 mut obj := MachOObject.new()
1970 obj.text_data << [u8(0x48), 0x8d, 0x05, 0, 0, 0, 0, 0x48, 0x8d, 0x05, 0, 0, 0, 0, 0xc3]
1971 obj.add_symbol('_main', 0, true, 1)
1972 ro_keep_sym := obj.add_symbol('L_ro_keep', 0, false, 2)
1973 obj.rodata << 'ro'.bytes()
1974 obj.rodata << 0
1975 ro_drop_start := obj.rodata.len
1976 obj.add_symbol('L_ro_drop', u64(ro_drop_start), false, 2)
1977 obj.rodata << 'drop'.bytes()
1978 obj.rodata << 0
1979 data_keep_sym := obj.add_symbol('_data_keep', 0, true, 3)
1980 obj.data_data << [u8(1), 2, 3, 4, 5, 6, 7, 8]
1981 data_drop_start := obj.data_data.len
1982 obj.add_symbol('_data_drop', u64(data_drop_start), true, 3)
1983 obj.data_data << [u8(9), 10, 11, 12]
1984 obj.add_reloc(3, ro_keep_sym, x86_64_reloc_signed, true, 2)
1985 obj.add_reloc(10, data_keep_sym, x86_64_reloc_signed, true, 2)
1986
1987 mut writer := MachOTinyObjectWriter{
1988 macho: obj
1989 }
1990 writer.write(path) or { panic(err) }
1991
1992 data := os.read_bytes(path) or { panic(err) }
1993 load_cmds := macho_test_load_commands(data)
1994 sections := macho_test_sections(data, load_cmds[0])
1995 assert sections[1].size == u64(3)
1996 assert sections[2].size == u64(8)
1997 assert data[int(sections[1].offset)..int(sections[1].offset + sections[1].size)] == 'ro\0'.bytes()
1998 assert data[int(sections[2].offset)..int(sections[2].offset + sections[2].size)] == [
1999 u8(1),
2000 2,
2001 3,
2002 4,
2003 5,
2004 6,
2005 7,
2006 8,
2007 ]
2008
2009 names := macho_test_symbol_names(data)
2010 assert 'L_ro_keep' in names
2011 assert '_data_keep' in names
2012 assert 'L_ro_drop' !in names
2013 assert '_data_drop' !in names
2014 relocs := macho_test_text_relocations(data)
2015 symbols := macho_test_symbols(data)
2016 ro_keep_out := macho_test_symbol_by_name(symbols, 'L_ro_keep') or {
2017 assert false, 'missing L_ro_keep in Mach-O tiny output'
2018 return
2019 }
2020 data_keep_out := macho_test_symbol_by_name(symbols, '_data_keep') or {
2021 assert false, 'missing _data_keep in Mach-O tiny output'
2022 return
2023 }
2024 assert ro_keep_out.type_ == 0x0e
2025 assert ro_keep_out.sect == 2
2026 assert data_keep_out.type_ == 0x0e
2027 assert data_keep_out.sect == 3
2028 assert relocs.len == 2
2029 assert symbols[relocs[0].sym_idx].name == 'L_ro_keep'
2030 assert symbols[relocs[1].sym_idx].name == '_data_keep'
2031}
2032
2033fn test_macho_tiny_object_writer_remaps_multiple_aligned_data_ranges() {
2034 path := temp_object_path('macho_tiny_aligned_data_ranges')
2035 defer {
2036 os.rm(path) or {}
2037 }
2038
2039 ro_a := [u8(0x01), 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]
2040 ro_b := [u8(0x11), 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18]
2041 data_a := [u8(0x21), 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28]
2042 data_b := [u8(0x31), 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38]
2043
2044 mut obj := MachOObject.new()
2045 obj.text_data << [u8(0x48), 0x8d, 0x05, 0, 0, 0, 0, 0x48, 0x8d, 0x05, 0, 0, 0, 0, 0x48, 0x8d,
2046 0x05, 0, 0, 0, 0, 0x48, 0x8d, 0x05, 0, 0, 0, 0, 0xc3]
2047 obj.add_symbol('_main', 0, true, 1)
2048 ro_a_sym := obj.add_symbol('L_ro64_a', 0, false, 2)
2049 obj.rodata << ro_a
2050 ro_b_sym := obj.add_symbol('L_ro64_b', u64(obj.rodata.len), false, 2)
2051 obj.rodata << ro_b
2052 obj.add_symbol('L_ro64_unused', u64(obj.rodata.len), false, 2)
2053 obj.rodata << [u8(0xf1), 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8]
2054 data_a_sym := obj.add_symbol('_data64_a', 0, true, 3)
2055 obj.data_data << data_a
2056 data_b_sym := obj.add_symbol('_data64_b', u64(obj.data_data.len), true, 3)
2057 obj.data_data << data_b
2058 obj.add_symbol('_data64_unused', u64(obj.data_data.len), true, 3)
2059 obj.data_data << [u8(0xe1), 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8]
2060 obj.add_reloc(3, ro_b_sym, x86_64_reloc_signed, true, 2)
2061 obj.add_reloc(10, ro_a_sym, x86_64_reloc_signed, true, 2)
2062 obj.add_reloc(17, data_b_sym, x86_64_reloc_signed, true, 2)
2063 obj.add_reloc(24, data_a_sym, x86_64_reloc_signed, true, 2)
2064
2065 mut writer := MachOTinyObjectWriter{
2066 macho: obj
2067 }
2068 writer.write(path) or { panic(err) }
2069
2070 data := os.read_bytes(path) or { panic(err) }
2071 load_cmds := macho_test_load_commands(data)
2072 sections := macho_test_sections(data, load_cmds[0])
2073 rodata_payload := data[int(sections[1].offset)..int(sections[1].offset + sections[1].size)]
2074 data_payload := data[int(sections[2].offset)..int(sections[2].offset + sections[2].size)]
2075 mut expected_rodata := []u8{}
2076 expected_rodata << ro_b
2077 expected_rodata << ro_a
2078 mut expected_data := []u8{}
2079 expected_data << data_b
2080 expected_data << data_a
2081 assert rodata_payload == expected_rodata
2082 assert data_payload == expected_data
2083
2084 symbols := macho_test_symbols(data)
2085 ro_b_out := macho_test_symbol_by_name(symbols, 'L_ro64_b') or {
2086 assert false, 'missing L_ro64_b'
2087 return
2088 }
2089 ro_a_out := macho_test_symbol_by_name(symbols, 'L_ro64_a') or {
2090 assert false, 'missing L_ro64_a'
2091 return
2092 }
2093 data_b_out := macho_test_symbol_by_name(symbols, '_data64_b') or {
2094 assert false, 'missing _data64_b'
2095 return
2096 }
2097 data_a_out := macho_test_symbol_by_name(symbols, '_data64_a') or {
2098 assert false, 'missing _data64_a'
2099 return
2100 }
2101 assert ro_b_out.value == sections[1].addr
2102 assert ro_a_out.value == sections[1].addr + 8
2103 assert data_b_out.value == sections[2].addr
2104 assert data_a_out.value == sections[2].addr + 8
2105
2106 names := symbols.map(it.name)
2107 assert 'L_ro64_unused' !in names
2108 assert '_data64_unused' !in names
2109 relocs := macho_test_text_relocations(data)
2110 assert relocs.len == 4
2111 assert symbols[relocs[0].sym_idx].name == 'L_ro64_b'
2112 assert symbols[relocs[1].sym_idx].name == 'L_ro64_a'
2113 assert symbols[relocs[2].sym_idx].name == '_data64_b'
2114 assert symbols[relocs[3].sym_idx].name == '_data64_a'
2115}
2116
2117fn test_macho_tiny_object_writer_rejects_unknown_relocation_type() {
2118 path := temp_object_path('macho_tiny_bad_reloc')
2119 defer {
2120 os.rm(path) or {}
2121 }
2122
2123 mut obj := MachOObject.new()
2124 obj.text_data << [u8(0xe8), 0, 0, 0, 0, 0xc3]
2125 call_sym := obj.add_undefined('_calloc')
2126 obj.add_symbol('_main', 0, true, 1)
2127 obj.add_reloc(1, call_sym, 99, true, 2)
2128 mut writer := MachOTinyObjectWriter{
2129 macho: obj
2130 }
2131 writer.write(path) or {
2132 assert err.msg().starts_with(macos_tiny_not_eligible_prefix)
2133 return
2134 }
2135 assert false, 'Mach-O tiny writer accepted an unsupported relocation type'
2136}
2137
2138fn test_macho_tiny_object_writer_rejects_non_pcrel_relocation() {
2139 path := temp_object_path('macho_tiny_non_pcrel_reloc')
2140 defer {
2141 os.rm(path) or {}
2142 }
2143
2144 mut obj := MachOObject.new()
2145 obj.text_data << [u8(0xe8), 0, 0, 0, 0, 0xc3]
2146 call_sym := obj.add_undefined('_calloc')
2147 obj.add_symbol('_main', 0, true, 1)
2148 obj.add_reloc(1, call_sym, x86_64_reloc_branch, false, 2)
2149 mut writer := MachOTinyObjectWriter{
2150 macho: obj
2151 }
2152 writer.write(path) or {
2153 assert err.msg().starts_with(macos_tiny_not_eligible_prefix)
2154 assert err.msg().contains('non-pcrel')
2155 return
2156 }
2157 assert false, 'Mach-O tiny writer accepted a non-pcrel relocation'
2158}
2159
2160fn test_macho_tiny_object_writer_rejects_non_32_bit_relocation() {
2161 path := temp_object_path('macho_tiny_non_32_bit_reloc')
2162 defer {
2163 os.rm(path) or {}
2164 }
2165
2166 mut obj := MachOObject.new()
2167 obj.text_data << [u8(0xe8), 0, 0, 0, 0, 0xc3]
2168 call_sym := obj.add_undefined('_calloc')
2169 obj.add_symbol('_main', 0, true, 1)
2170 obj.add_reloc(1, call_sym, x86_64_reloc_branch, true, 3)
2171 mut writer := MachOTinyObjectWriter{
2172 macho: obj
2173 }
2174 writer.write(path) or {
2175 assert err.msg().starts_with(macos_tiny_not_eligible_prefix)
2176 assert err.msg().contains('non-32-bit')
2177 return
2178 }
2179 assert false, 'Mach-O tiny writer accepted a non-32-bit relocation'
2180}
2181
2182fn test_macho_tiny_object_writer_rejects_missing_main_symbol() {
2183 path := temp_object_path('macho_tiny_missing_main')
2184 defer {
2185 os.rm(path) or {}
2186 }
2187
2188 mut obj := MachOObject.new()
2189 obj.text_data << u8(0xc3)
2190 obj.add_symbol('_not_main', 0, true, 1)
2191 mut writer := MachOTinyObjectWriter{
2192 macho: obj
2193 }
2194 writer.write(path) or {
2195 assert err.msg().starts_with(macos_tiny_not_eligible_prefix)
2196 assert err.msg().contains('missing _main symbol')
2197 return
2198 }
2199 assert false, 'Mach-O tiny writer accepted an object without _main'
2200}
2201
2202fn test_macho_tiny_object_writer_rejects_module_init_symbol() {
2203 path := temp_object_path('macho_tiny_module_init')
2204 defer {
2205 os.rm(path) or {}
2206 }
2207
2208 mut obj := MachOObject.new()
2209 obj.text_data << [u8(0xe8), 0, 0, 0, 0, 0xc3]
2210 init_start := obj.text_data.len
2211 obj.text_data << u8(0xc3)
2212 init_sym := obj.add_symbol('_dep__init', u64(init_start), true, 1)
2213 obj.add_symbol('_main', 0, true, 1)
2214 obj.add_reloc(1, init_sym, x86_64_reloc_branch, true, 2)
2215 mut writer := MachOTinyObjectWriter{
2216 macho: obj
2217 }
2218 writer.write(path) or {
2219 assert err.msg().starts_with(macos_tiny_not_eligible_prefix)
2220 assert err.msg().contains('module init symbol')
2221 return
2222 }
2223 assert false, 'Mach-O tiny writer accepted a module init symbol'
2224}
2225
2226fn test_macho_object_is_accepted_by_external_tools_when_available() {
2227 tool := find_first_available_external_tool(macho_external_tool_candidates()) or {
2228 println('skipping ${@FN}: llvm-readobj, llvm-objdump, and otool variants are not available')
2229 return
2230 }
2231 path := temp_object_path('macho_external_tools')
2232 defer {
2233 os.rm(path) or {}
2234 }
2235
2236 // Tests-only external validation palier. The same worktree also contains
2237 // prior accepted production changes for Mach-O GOT_LOAD emission.
2238 // Apple/XNU mach-o/x86_64/reloc.h defines BRANCH for calls, SIGNED for
2239 // direct RIP-relative references, and GOT_LOAD for movq foo@GOTPCREL(%rip).
2240 mut obj := MachOObject.new()
2241 obj.text_data << [u8(0xe8), 0, 0, 0, 0, 0x48, 0x8d, 0x05, 0, 0, 0, 0, 0x48, 0x8b, 0x05, 0,
2242 0, 0, 0]
2243 call_sym := obj.add_undefined('_calloc')
2244 local_sym := obj.add_symbol('L_str_0', 0, false, 2)
2245 stderr_sym := obj.add_undefined('___stderrp')
2246 obj.rodata << 'hello'.bytes()
2247 obj.rodata << 0
2248 obj.data_data << [u8(1), 2, 3, 4]
2249 obj.add_reloc(1, call_sym, x86_64_reloc_branch, true, 2)
2250 obj.add_reloc(8, local_sym, x86_64_reloc_signed, true, 2)
2251 obj.add_reloc(15, stderr_sym, x86_64_reloc_got_load, true, 2)
2252 obj.write(path)
2253
2254 quoted_tool := os.quoted_path(tool.path)
2255 quoted_path := os.quoted_path(path)
2256 output := match tool.kind {
2257 .llvm_readobj {
2258 res :=
2259 os.execute('${quoted_tool} --sections --symbols --relocations --expand-relocs ${quoted_path}')
2260 assert_command_ok(res, 'llvm-readobj Mach-O')
2261 res.output
2262 }
2263 .llvm_objdump {
2264 res := os.execute('${quoted_tool} -h -t -r ${quoted_path}')
2265 assert_command_ok(res, 'llvm-objdump Mach-O')
2266 res.output
2267 }
2268 .otool {
2269 res := os.execute('${quoted_tool} -lrv ${quoted_path}')
2270 assert_command_ok(res, 'otool Mach-O')
2271 res.output
2272 }
2273 .dumpbin {
2274 panic('dumpbin is not a Mach-O validator')
2275 }
2276 }
2277
2278 assert output.contains('__text')
2279 assert output.contains('__const')
2280 assert output.contains('__data')
2281 assert output.contains('___stderrp')
2282 assert output.contains('_calloc')
2283 assert output.contains('L_str_0')
2284 if tool.kind == .llvm_readobj {
2285 assert output.contains('X86_64_RELOC_GOT_LOAD')
2286 || output.contains('type=X86_64_RELOC_GOT_LOAD') || output.contains('GOT_LOAD')
2287 assert output.contains('X86_64_RELOC_BRANCH') || output.contains('BRANCH')
2288 assert output.contains('X86_64_RELOC_SIGNED') || output.contains('SIGNED')
2289 assert llvm_readobj_has_relocation_block(output, ['___stderrp', 'GOT_LOAD'])
2290 assert !llvm_readobj_has_relocation_block(output, ['___stderrp', 'SIGNED'])
2291 assert llvm_readobj_has_relocation_block(output, ['_calloc', 'BRANCH'])
2292 assert llvm_readobj_has_relocation_block(output, ['L_str_0', 'SIGNED'])
2293 } else if tool.kind == .llvm_objdump {
2294 assert output_has_line_with_all(output, ['GOT_LOAD', '___stderrp'])
2295 assert !output_has_line_with_all(output, ['SIGNED', '___stderrp'])
2296 assert output_has_line_with_all(output, ['BRANCH', '_calloc'])
2297 assert output_has_line_with_all(output, ['SIGNED', 'L_str_0'])
2298 } else if tool.kind == .otool {
2299 // Apple otool is kept as an external acceptance smoke. Its relocation
2300 // type labels are presentation details (for example, cctools prints
2301 // GOT_LD for X86_64_RELOC_GOT_LOAD), while the binary-level tests above
2302 // and the LLVM tool paths still validate the exact type/symbol pairing.
2303 assert output.contains('Relocation information')
2304 }
2305}
2306
2307fn test_coff_writer_emits_x64_relocatable_object() {
2308 path := temp_object_path('coff')
2309 defer {
2310 os.rm(path) or {}
2311 }
2312
2313 mut obj := CoffObject.new()
2314 obj.text_data << u8(0xc3)
2315 obj.rodata << 'hello'.bytes()
2316 obj.rodata << 0
2317 obj.data_data << [u8(1), 2, 3, 4]
2318 obj.add_symbol('main', 0, true, 1)
2319 obj.write(path)
2320
2321 data := os.read_bytes(path) or { panic(err) }
2322 assert data.len > 20 + (3 * 40)
2323 assert read_u16_le(data, 0) == coff_image_file_machine_amd64
2324 assert read_u16_le(data, 2) == 3
2325 assert read_u32_le(data, 4) == 0
2326 assert read_u16_le(data, 16) == 0
2327 assert read_u16_le(data, 18) == 0
2328 sections := coff_test_sections(data)
2329 assert sections.len == 3
2330 mut coff_ranges := [
2331 make_byte_range('COFF file header and section table', 0, 20 + 3 * 40),
2332 ]
2333 assert sections[0].name == '.text'
2334 assert sections[1].name == '.rdata'
2335 assert sections[2].name == '.data'
2336 assert read_u32_le(data, 8) == 156
2337 assert sections[0].virtual_size == 0
2338 assert sections[0].virtual_address == 0
2339 assert sections[0].raw_size == 1
2340 assert sections[0].raw_pointer == 140
2341 assert sections[0].raw_pointer % 4 == 0
2342 assert sections[0].reloc_pointer == 0
2343 assert sections[0].line_pointer == 0
2344 assert sections[0].nreloc == 0
2345 assert sections[0].nline == 0
2346 assert data[int(sections[0].raw_pointer)..int(sections[0].raw_pointer + sections[0].raw_size)] == obj.text_data
2347 assert sections[1].virtual_size == 0
2348 assert sections[1].virtual_address == 0
2349 assert sections[1].raw_size == 6
2350 assert sections[1].raw_pointer == 144
2351 assert sections[1].raw_pointer % 4 == 0
2352 assert sections[1].reloc_pointer == 0
2353 assert sections[1].line_pointer == 0
2354 assert sections[1].nreloc == 0
2355 assert sections[1].nline == 0
2356 assert data[int(sections[1].raw_pointer)..int(sections[1].raw_pointer + sections[1].raw_size)] == obj.rodata
2357 assert sections[2].virtual_size == 0
2358 assert sections[2].virtual_address == 0
2359 assert sections[2].raw_size == 4
2360 assert sections[2].raw_pointer == 152
2361 assert sections[2].raw_pointer % 4 == 0
2362 assert sections[2].reloc_pointer == 0
2363 assert sections[2].line_pointer == 0
2364 assert sections[2].nreloc == 0
2365 assert sections[2].nline == 0
2366 assert data[int(sections[2].raw_pointer)..int(sections[2].raw_pointer + sections[2].raw_size)] == obj.data_data
2367 coff_ranges << coff_test_section_ranges(sections)
2368 coff_ranges << make_byte_range('COFF symbol table', u64(read_u32_le(data, 8)),
2369 u64(read_u32_le(data, 12)) * 18)
2370 for range in coff_ranges {
2371 assert_range_in_file(data, range)
2372 }
2373 assert_ranges_do_not_overlap(coff_ranges)
2374 assert read_u32_le(data, 8) % 4 == 0
2375 assert sections[0].characteristics & coff_image_scn_cnt_code != 0
2376 assert sections[0].characteristics & coff_image_scn_mem_execute != 0
2377 assert sections[0].characteristics & coff_image_scn_mem_read != 0
2378 assert sections[0].characteristics & coff_image_scn_align_16bytes != 0
2379 assert sections[1].characteristics & coff_image_scn_cnt_initialized_data != 0
2380 assert sections[1].characteristics & coff_image_scn_mem_read != 0
2381 assert sections[1].characteristics & coff_image_scn_align_8bytes != 0
2382 assert sections[2].characteristics & coff_image_scn_cnt_initialized_data != 0
2383 assert sections[2].characteristics & coff_image_scn_mem_read != 0
2384 assert sections[2].characteristics & coff_image_scn_mem_write != 0
2385 assert sections[2].characteristics & coff_image_scn_align_8bytes != 0
2386}
2387
2388fn test_coff_writer_serializes_symbols_and_string_table() {
2389 path := temp_object_path('coff_symbols')
2390 defer {
2391 os.rm(path) or {}
2392 }
2393
2394 mut obj := CoffObject.new()
2395 undef_idx := obj.add_undefined('external_long_symbol_name')
2396 pure_undef_idx := obj.add_undefined('calloc')
2397 main_idx := obj.add_symbol('main', 0, true, 1)
2398 defined_idx := obj.add_symbol('global_long_symbol_name', 16, false, 3)
2399 local_idx := obj.add_symbol('L_local_data', 4, false, 2)
2400 late_def_idx := obj.add_symbol('external_long_symbol_name', 8, false, 2)
2401 obj.write(path)
2402
2403 assert late_def_idx == undef_idx
2404
2405 data := os.read_bytes(path) or { panic(err) }
2406 sym_off := int(read_u32_le(data, 8))
2407 nsyms := int(read_u32_le(data, 12))
2408 assert nsyms == 5
2409
2410 string_table_off := sym_off + (nsyms * 18)
2411 string_table_size := int(read_u32_le(data, string_table_off))
2412 assert string_table_size > 4
2413
2414 main_off := sym_off + (main_idx * 18)
2415 assert data[main_off..main_off + 8].bytestr().trim_right('\0') == 'main'
2416 assert read_u32_le(data, main_off + 8) == 0
2417 assert read_u16_le(data, main_off + 12) == 1
2418 assert read_u16_le(data, main_off + 14) == coff_image_sym_dtype_function
2419 assert data[main_off + 16] == coff_image_sym_class_external
2420
2421 defined_off := sym_off + (defined_idx * 18)
2422 assert read_u32_le(data, defined_off) == 0
2423 defined_name_off := int(read_u32_le(data, defined_off + 4))
2424 assert read_string(data, string_table_off + defined_name_off) == 'global_long_symbol_name'
2425 assert read_u32_le(data, defined_off + 8) == 16
2426 assert read_u16_le(data, defined_off + 12) == 3
2427
2428 late_def_off := sym_off + (late_def_idx * 18)
2429 late_def_name_off := int(read_u32_le(data, late_def_off + 4))
2430 assert read_string(data, string_table_off + late_def_name_off) == 'external_long_symbol_name'
2431 assert read_u32_le(data, late_def_off + 8) == 8
2432 assert read_u16_le(data, late_def_off + 12) == 2
2433
2434 pure_undef_off := sym_off + (pure_undef_idx * 18)
2435 assert data[pure_undef_off..pure_undef_off + 8].bytestr().trim_right('\0') == 'calloc'
2436 assert read_u32_le(data, pure_undef_off + 8) == 0
2437 assert read_u16_le(data, pure_undef_off + 12) == 0
2438 assert data[pure_undef_off + 16] == coff_image_sym_class_external
2439
2440 local_off := sym_off + (local_idx * 18)
2441 assert read_u32_le(data, local_off) == 0
2442 local_name_off := int(read_u32_le(data, local_off + 4))
2443 assert read_string(data, string_table_off + local_name_off) == 'L_local_data'
2444 assert read_u16_le(data, local_off + 12) == 2
2445 assert data[local_off + 16] == coff_image_sym_class_static
2446}
2447
2448fn test_coff_writer_serializes_text_relocations() {
2449 path := temp_object_path('coff_relocs')
2450 defer {
2451 os.rm(path) or {}
2452 }
2453
2454 mut obj := CoffObject.new()
2455 obj.text_data << [u8(0xe8), 0, 0, 0, 0, 0x48, 0x8d, 0x05, 0, 0, 0, 0]
2456 call_sym := obj.add_undefined('calloc')
2457 data_sym := obj.add_symbol('L_str_0', 0, false, 2)
2458 obj.add_text_reloc(1, call_sym, coff_image_rel_amd64_rel32)
2459 obj.add_text_reloc(8, data_sym, coff_image_rel_amd64_rel32)
2460 obj.write(path)
2461
2462 data := os.read_bytes(path) or { panic(err) }
2463 text_section_off := 20
2464 reloc_off := int(read_u32_le(data, text_section_off + 24))
2465 nreloc := int(read_u16_le(data, text_section_off + 32))
2466 assert nreloc == 2
2467 assert reloc_off == 152
2468 assert reloc_off % 4 == 0
2469 assert read_u32_le(data, 8) == 172
2470 assert read_u32_le(data, 8) % 4 == 0
2471 mut coff_reloc_ranges := [
2472 make_byte_range('COFF file header and section table', 0, 20 + 3 * 40),
2473 make_byte_range('COFF text section', u64(read_u32_le(data, text_section_off + 20)), u64(read_u32_le(data,
2474
2475 text_section_off + 16))),
2476 make_byte_range('COFF text relocations', u64(reloc_off), u64(nreloc) * 10),
2477 make_byte_range('COFF symbol table', u64(read_u32_le(data, 8)),
2478 u64(read_u32_le(data, 12)) * 18),
2479 ]
2480 for range in coff_reloc_ranges {
2481 assert_range_in_file(data, range)
2482 }
2483 assert_ranges_do_not_overlap(coff_reloc_ranges)
2484
2485 assert read_u32_le(data, reloc_off) == 1
2486 assert read_u32_le(data, reloc_off + 4) == u32(call_sym)
2487 assert read_u16_le(data, reloc_off + 8) == coff_image_rel_amd64_rel32
2488 assert read_u32_le(data, reloc_off + 10) == 8
2489 assert read_u32_le(data, reloc_off + 14) == u32(data_sym)
2490 assert read_u16_le(data, reloc_off + 18) == coff_image_rel_amd64_rel32
2491}
2492
2493fn test_coff_object_is_accepted_by_external_tools_when_available() {
2494 tool := find_first_available_external_tool(coff_external_tool_candidates()) or {
2495 println('skipping ${@FN}: llvm-readobj, llvm-objdump, and dumpbin variants are not available')
2496 return
2497 }
2498 path := temp_object_path('coff_external_tools')
2499 defer {
2500 os.rm(path) or {}
2501 }
2502
2503 // Tests-only external validation palier. The same worktree also contains
2504 // prior accepted production changes for Mach-O GOT_LOAD emission.
2505 // Microsoft PE/COFF specifies IMAGE_FILE_MACHINE_AMD64, section tables,
2506 // COFF symbols, and IMAGE_REL_AMD64_REL32 for x64 rel32 relocations.
2507 mut obj := CoffObject.new()
2508 obj.text_data << [u8(0xe8), 0, 0, 0, 0, 0x48, 0x8d, 0x05, 0, 0, 0, 0]
2509 obj.rodata << 'hello'.bytes()
2510 obj.rodata << 0
2511 obj.data_data << [u8(1), 2, 3, 4]
2512 call_sym := obj.add_undefined('calloc')
2513 obj.add_symbol('main', 0, true, 1)
2514 obj.add_symbol('app_global', 0, false, 3)
2515 local_sym := obj.add_symbol('L_str_0', 0, false, 2)
2516 obj.add_text_reloc(1, call_sym, coff_image_rel_amd64_rel32)
2517 obj.add_text_reloc(8, local_sym, coff_image_rel_amd64_rel32)
2518 obj.write(path)
2519
2520 quoted_tool := os.quoted_path(tool.path)
2521 quoted_path := os.quoted_path(path)
2522 output := match tool.kind {
2523 .llvm_readobj {
2524 res :=
2525 os.execute('${quoted_tool} --file-headers --sections --symbols --relocations --expand-relocs ${quoted_path}')
2526 assert_command_ok(res, 'llvm-readobj COFF')
2527 res.output
2528 }
2529 .llvm_objdump {
2530 res := os.execute('${quoted_tool} -f -h -t -r ${quoted_path}')
2531 assert_command_ok(res, 'llvm-objdump COFF')
2532 res.output
2533 }
2534 .dumpbin {
2535 res := os.execute('${quoted_tool} /headers /symbols /relocations ${quoted_path}')
2536 assert_command_ok(res, 'dumpbin COFF')
2537 res.output
2538 }
2539 .otool {
2540 panic('otool is not a COFF validator')
2541 }
2542 }
2543
2544 assert output.contains('AMD64') || output.contains('x86-64') || output.contains('X86_64')
2545 assert output.contains('.text')
2546 assert output.contains('.rdata')
2547 assert output.contains('.data')
2548 assert output.contains('main')
2549 assert output.contains('calloc')
2550 assert output.contains('app_global')
2551 assert output.contains('L_str_0')
2552 assert output.contains('REL32')
2553 if tool.kind == .llvm_readobj {
2554 assert output.contains('IMAGE_REL_AMD64_REL32') || output.contains('REL32')
2555 assert output_has_nearby_lines_with_all(output, 'calloc', ['REL32'], 8)
2556 } else {
2557 assert output_has_line_with_all(output, ['REL32', 'calloc'])
2558 }
2559}
2560
2561fn test_coff_writer_rejects_relocation_count_overflow() {
2562 section := CoffSection{
2563 name: '.text'
2564 relocs: []CoffRelocation{len: 0x10000}
2565 }
2566 if _ := coff_relocation_count_for_header(section) {
2567 assert false
2568 } else {
2569 assert err.msg().contains('COFF section .text has 65536 relocations')
2570 assert err.msg().contains('extended relocations are not supported')
2571 }
2572}
2573