| 1 | // Copyright (c) 2026 Alexander Medvednikov. All rights reserved. |
| 2 | // Use of this source code is governed by an MIT license |
| 3 | // that can be found in the LICENSE file. |
| 4 | |
| 5 | module x64 |
| 6 | |
| 7 | import os |
| 8 | |
| 9 | const coff_image_file_machine_amd64 = u16(0x8664) |
| 10 | const coff_image_sym_class_external = u8(2) |
| 11 | const coff_image_sym_class_static = u8(3) |
| 12 | const coff_image_sym_dtype_function = u16(0x20) |
| 13 | |
| 14 | const coff_image_rel_amd64_rel32 = u16(0x0004) |
| 15 | |
| 16 | const coff_image_scn_cnt_code = u32(0x00000020) |
| 17 | const coff_image_scn_cnt_initialized_data = u32(0x00000040) |
| 18 | const coff_image_scn_align_8bytes = u32(0x00400000) |
| 19 | const coff_image_scn_align_16bytes = u32(0x00500000) |
| 20 | const coff_image_scn_mem_execute = u32(0x20000000) |
| 21 | const coff_image_scn_mem_read = u32(0x40000000) |
| 22 | const coff_image_scn_mem_write = u32(0x80000000) |
| 23 | |
| 24 | pub struct CoffObject { |
| 25 | pub mut: |
| 26 | text_data []u8 |
| 27 | data_data []u8 |
| 28 | rodata []u8 |
| 29 | |
| 30 | symbols []CoffSymbol |
| 31 | str_table []u8 |
| 32 | sym_by_name map[string]int |
| 33 | |
| 34 | text_relocs []CoffRelocation |
| 35 | } |
| 36 | |
| 37 | struct CoffSymbol { |
| 38 | mut: |
| 39 | name string |
| 40 | name_off int |
| 41 | value u32 |
| 42 | section i16 |
| 43 | type_ u16 |
| 44 | storage_class u8 |
| 45 | } |
| 46 | |
| 47 | struct CoffRelocation { |
| 48 | offset u32 |
| 49 | sym_idx int |
| 50 | type_ u16 |
| 51 | } |
| 52 | |
| 53 | struct CoffSection { |
| 54 | name string |
| 55 | data []u8 |
| 56 | relocs []CoffRelocation |
| 57 | characteristics u32 |
| 58 | } |
| 59 | |
| 60 | pub fn CoffObject.new() &CoffObject { |
| 61 | return &CoffObject{} |
| 62 | } |
| 63 | |
| 64 | pub fn (mut c CoffObject) add_symbol(name string, value u64, is_func bool, section u8) int { |
| 65 | type_ := if is_func { coff_image_sym_dtype_function } else { u16(0) } |
| 66 | storage_class := coff_symbol_storage_class(name, section) |
| 67 | if i := c.sym_by_name[name] { |
| 68 | mut s := &c.symbols[i] |
| 69 | s.value = u32(value) |
| 70 | s.section = i16(section) |
| 71 | s.type_ = type_ |
| 72 | s.storage_class = storage_class |
| 73 | return i |
| 74 | } |
| 75 | |
| 76 | idx := c.symbols.len |
| 77 | c.symbols << CoffSymbol{ |
| 78 | name: name |
| 79 | name_off: c.add_string_if_needed(name) |
| 80 | value: u32(value) |
| 81 | section: i16(section) |
| 82 | type_: type_ |
| 83 | storage_class: storage_class |
| 84 | } |
| 85 | c.sym_by_name[name] = idx |
| 86 | return idx |
| 87 | } |
| 88 | |
| 89 | pub fn (mut c CoffObject) add_undefined(name string) int { |
| 90 | if i := c.sym_by_name[name] { |
| 91 | return i |
| 92 | } |
| 93 | |
| 94 | idx := c.symbols.len |
| 95 | c.symbols << CoffSymbol{ |
| 96 | name: name |
| 97 | name_off: c.add_string_if_needed(name) |
| 98 | value: 0 |
| 99 | section: 0 |
| 100 | type_: 0 |
| 101 | storage_class: coff_image_sym_class_external |
| 102 | } |
| 103 | c.sym_by_name[name] = idx |
| 104 | return idx |
| 105 | } |
| 106 | |
| 107 | fn coff_symbol_storage_class(name string, section u8) u8 { |
| 108 | if section != 0 && name.starts_with('L_') { |
| 109 | return coff_image_sym_class_static |
| 110 | } |
| 111 | return coff_image_sym_class_external |
| 112 | } |
| 113 | |
| 114 | pub fn (mut c CoffObject) add_text_reloc(offset int, sym_idx int, type_ u16) { |
| 115 | c.text_relocs << CoffRelocation{ |
| 116 | offset: u32(offset) |
| 117 | sym_idx: sym_idx |
| 118 | type_: type_ |
| 119 | } |
| 120 | } |
| 121 | |
| 122 | fn (mut c CoffObject) add_string_if_needed(s string) int { |
| 123 | if s.len <= 8 { |
| 124 | return 0 |
| 125 | } |
| 126 | off := 4 + c.str_table.len |
| 127 | c.str_table << s.bytes() |
| 128 | c.str_table << 0 |
| 129 | return off |
| 130 | } |
| 131 | |
| 132 | pub fn (mut c CoffObject) write(path string) { |
| 133 | sections := [ |
| 134 | CoffSection{ |
| 135 | name: '.text' |
| 136 | data: c.text_data |
| 137 | relocs: c.text_relocs |
| 138 | characteristics: coff_image_scn_cnt_code | coff_image_scn_mem_execute | coff_image_scn_mem_read | coff_image_scn_align_16bytes |
| 139 | }, |
| 140 | CoffSection{ |
| 141 | name: '.rdata' |
| 142 | data: c.rodata |
| 143 | characteristics: coff_image_scn_cnt_initialized_data | coff_image_scn_mem_read | coff_image_scn_align_8bytes |
| 144 | }, |
| 145 | CoffSection{ |
| 146 | name: '.data' |
| 147 | data: c.data_data |
| 148 | characteristics: coff_image_scn_cnt_initialized_data | coff_image_scn_mem_read | coff_image_scn_mem_write | coff_image_scn_align_8bytes |
| 149 | }, |
| 150 | ] |
| 151 | |
| 152 | header_size := 20 |
| 153 | section_header_size := 40 |
| 154 | reloc_entry_size := 10 |
| 155 | mut current_offset := header_size + (sections.len * section_header_size) |
| 156 | |
| 157 | mut raw_offsets := []int{len: sections.len} |
| 158 | for i, section in sections { |
| 159 | if section.data.len > 0 { |
| 160 | current_offset = align_int(current_offset, 4) |
| 161 | raw_offsets[i] = current_offset |
| 162 | current_offset += section.data.len |
| 163 | } |
| 164 | } |
| 165 | |
| 166 | mut reloc_offsets := []int{len: sections.len} |
| 167 | for i, section in sections { |
| 168 | if section.relocs.len > 0 { |
| 169 | current_offset = align_int(current_offset, 4) |
| 170 | reloc_offsets[i] = current_offset |
| 171 | current_offset += section.relocs.len * reloc_entry_size |
| 172 | } |
| 173 | } |
| 174 | |
| 175 | symbol_table_off := align_int(current_offset, 4) |
| 176 | string_table_size := 4 + c.str_table.len |
| 177 | |
| 178 | mut buf := []u8{} |
| 179 | write_u16_le(mut buf, coff_image_file_machine_amd64) |
| 180 | write_u16_le(mut buf, u16(sections.len)) |
| 181 | write_u32_le(mut buf, 0) |
| 182 | write_u32_le(mut buf, u32(symbol_table_off)) |
| 183 | write_u32_le(mut buf, u32(c.symbols.len)) |
| 184 | write_u16_le(mut buf, 0) |
| 185 | write_u16_le(mut buf, 0) |
| 186 | |
| 187 | for i, section in sections { |
| 188 | write_fixed_string(mut buf, section.name, 8) |
| 189 | write_u32_le(mut buf, 0) |
| 190 | write_u32_le(mut buf, 0) |
| 191 | write_u32_le(mut buf, u32(section.data.len)) |
| 192 | write_u32_le(mut buf, u32(raw_offsets[i])) |
| 193 | write_u32_le(mut buf, u32(reloc_offsets[i])) |
| 194 | write_u32_le(mut buf, 0) |
| 195 | nrelocs := coff_relocation_count_for_header(section) or { panic(err) } |
| 196 | write_u16_le(mut buf, nrelocs) |
| 197 | write_u16_le(mut buf, 0) |
| 198 | write_u32_le(mut buf, section.characteristics) |
| 199 | } |
| 200 | |
| 201 | for i, section in sections { |
| 202 | if raw_offsets[i] == 0 { |
| 203 | continue |
| 204 | } |
| 205 | for buf.len < raw_offsets[i] { |
| 206 | buf << 0 |
| 207 | } |
| 208 | buf << section.data |
| 209 | } |
| 210 | |
| 211 | for i, section in sections { |
| 212 | if reloc_offsets[i] == 0 { |
| 213 | continue |
| 214 | } |
| 215 | for buf.len < reloc_offsets[i] { |
| 216 | buf << 0 |
| 217 | } |
| 218 | for reloc in section.relocs { |
| 219 | write_u32_le(mut buf, reloc.offset) |
| 220 | write_u32_le(mut buf, u32(reloc.sym_idx)) |
| 221 | write_u16_le(mut buf, reloc.type_) |
| 222 | } |
| 223 | } |
| 224 | |
| 225 | for buf.len < symbol_table_off { |
| 226 | buf << 0 |
| 227 | } |
| 228 | for sym in c.symbols { |
| 229 | c.write_symbol_name(mut buf, sym) |
| 230 | write_u32_le(mut buf, sym.value) |
| 231 | write_u16_le(mut buf, u16(sym.section)) |
| 232 | write_u16_le(mut buf, sym.type_) |
| 233 | buf << sym.storage_class |
| 234 | buf << 0 |
| 235 | } |
| 236 | |
| 237 | write_u32_le(mut buf, u32(string_table_size)) |
| 238 | buf << c.str_table |
| 239 | |
| 240 | os.write_file_array(path, buf) or { panic(err) } |
| 241 | } |
| 242 | |
| 243 | fn coff_relocation_count_for_header(section CoffSection) !u16 { |
| 244 | if section.relocs.len > 0xffff { |
| 245 | return error('COFF section ${section.name} has ${section.relocs.len} relocations; extended relocations are not supported') |
| 246 | } |
| 247 | return u16(section.relocs.len) |
| 248 | } |
| 249 | |
| 250 | fn (c CoffObject) write_symbol_name(mut buf []u8, sym CoffSymbol) { |
| 251 | if sym.name.len <= 8 { |
| 252 | write_fixed_string(mut buf, sym.name, 8) |
| 253 | return |
| 254 | } |
| 255 | write_u32_le(mut buf, 0) |
| 256 | write_u32_le(mut buf, u32(sym.name_off)) |
| 257 | } |
| 258 | |