| 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 encoding.binary |
| 8 | |
| 9 | pub const macos_tiny_not_eligible_prefix = 'macOS tiny object is not eligible: ' |
| 10 | |
| 11 | const macos_tiny_builtin_string_plus_symbol = '_builtin__string__+' |
| 12 | const macos_tiny_builtin_i64_str_symbol = '_builtin__i64__str' |
| 13 | const macos_tiny_malloc_symbol = '_malloc' |
| 14 | const macos_tiny_exit_symbol = '_exit' |
| 15 | |
| 16 | struct MachOTextRange { |
| 17 | name string |
| 18 | start u64 |
| 19 | end u64 |
| 20 | } |
| 21 | |
| 22 | struct MachODataRange { |
| 23 | section ObjectSection |
| 24 | start u64 |
| 25 | end u64 |
| 26 | } |
| 27 | |
| 28 | struct MachOTinyReachable { |
| 29 | mut: |
| 30 | names []string |
| 31 | data []MachODataRange |
| 32 | undefined map[string]bool |
| 33 | } |
| 34 | |
| 35 | struct MachOTinyObjectWriter { |
| 36 | macho &MachOObject |
| 37 | } |
| 38 | |
| 39 | struct MachOTinyRuntimeReloc { |
| 40 | addr int |
| 41 | symbol string |
| 42 | } |
| 43 | |
| 44 | pub fn (mut g Gen) write_macos_tiny_object(path string) ! { |
| 45 | if g.obj_format != .macho { |
| 46 | return error('macOS tiny object writing requires Mach-O object output') |
| 47 | } |
| 48 | if g.abi != .sysv { |
| 49 | return error('macOS tiny object writing requires SysV x64 code generation') |
| 50 | } |
| 51 | mut writer := MachOTinyObjectWriter{ |
| 52 | macho: g.macho |
| 53 | } |
| 54 | writer.write(path)! |
| 55 | } |
| 56 | |
| 57 | fn (mut w MachOTinyObjectWriter) write(path string) ! { |
| 58 | mut reachable := w.collect_reachable()! |
| 59 | mut out := MachOObject.new() |
| 60 | mut symbol_indices := map[string]int{} |
| 61 | mut func_offsets := map[string]u64{} |
| 62 | mut runtime_symbols := []string{} |
| 63 | mut runtime_relocs := []MachOTinyRuntimeReloc{} |
| 64 | |
| 65 | for name in reachable.names { |
| 66 | range := w.text_range(name)! |
| 67 | func_offsets[name] = u64(out.text_data.len) |
| 68 | out.text_data << w.macho.text_data[int(range.start)..int(range.end)] |
| 69 | } |
| 70 | macho_tiny_add_runtime_helpers(mut out, mut reachable, mut func_offsets, mut runtime_symbols, mut |
| 71 | runtime_relocs) |
| 72 | |
| 73 | mut data_offsets := map[string]u64{} |
| 74 | w.copy_data_ranges(reachable.data, .rodata, mut out.rodata, mut data_offsets)! |
| 75 | w.copy_data_ranges(reachable.data, .data, mut out.data_data, mut data_offsets)! |
| 76 | |
| 77 | for name in reachable.names { |
| 78 | symbol_indices[name] = out.add_symbol(name, func_offsets[name], |
| 79 | macho_tiny_defined_symbol_is_external(name), ObjectFormat.macho.section_index(.text)) |
| 80 | } |
| 81 | for name in runtime_symbols { |
| 82 | symbol_indices[name] = out.add_symbol(name, func_offsets[name], |
| 83 | macho_tiny_defined_symbol_is_external(name), ObjectFormat.macho.section_index(.text)) |
| 84 | } |
| 85 | mut data_names := data_offsets.keys() |
| 86 | data_names.sort() |
| 87 | for name in data_names { |
| 88 | orig := w.symbol_by_name(name)! |
| 89 | section := w.symbol_object_section(orig)! |
| 90 | symbol_indices[name] = out.add_symbol(name, data_offsets[name], |
| 91 | macho_tiny_defined_symbol_is_external(name), ObjectFormat.macho.section_index(section)) |
| 92 | } |
| 93 | mut undefined_names := reachable.undefined.keys() |
| 94 | undefined_names.sort() |
| 95 | for name in undefined_names { |
| 96 | symbol_indices[name] = out.add_undefined(name) |
| 97 | } |
| 98 | for name in reachable.names { |
| 99 | range := w.text_range(name)! |
| 100 | new_base := func_offsets[name] |
| 101 | for reloc in w.macho.relocs { |
| 102 | if reloc.addr < int(range.start) || reloc.addr >= int(range.end) { |
| 103 | continue |
| 104 | } |
| 105 | sym := w.reloc_symbol(reloc)! |
| 106 | sym_idx := symbol_indices[sym.name] or { |
| 107 | return macos_tiny_not_eligible('symbol `${sym.name}` is not resolved by the tiny object') |
| 108 | } |
| 109 | out_reloc_addr := int(new_base + u64(reloc.addr - int(range.start))) |
| 110 | if macho_tiny_resolve_relocation_locally(sym.name, reloc) { |
| 111 | macho_tiny_patch_rel32_local(mut out.text_data, out_reloc_addr, |
| 112 | int(func_offsets[sym.name])) |
| 113 | continue |
| 114 | } |
| 115 | out.add_reloc(out_reloc_addr, sym_idx, reloc.type_, reloc.pcrel, reloc.length) |
| 116 | } |
| 117 | } |
| 118 | for reloc in runtime_relocs { |
| 119 | sym_idx := symbol_indices[reloc.symbol] or { |
| 120 | return macos_tiny_not_eligible('runtime helper symbol `${reloc.symbol}` is not resolved by the tiny object') |
| 121 | } |
| 122 | out.add_reloc(reloc.addr, sym_idx, x86_64_reloc_branch, true, 2) |
| 123 | } |
| 124 | |
| 125 | out.write(path) |
| 126 | } |
| 127 | |
| 128 | fn (mut w MachOTinyObjectWriter) collect_reachable() !MachOTinyReachable { |
| 129 | _ = w.text_range('_main') or { return macos_tiny_not_eligible('missing _main symbol') } |
| 130 | mut selected := []string{} |
| 131 | mut seen := map[string]bool{} |
| 132 | mut queue := ['_main'] |
| 133 | mut selected_data := []MachODataRange{} |
| 134 | mut undefined := map[string]bool{} |
| 135 | for queue.len > 0 { |
| 136 | name := queue[0] |
| 137 | queue.delete(0) |
| 138 | if seen[name] { |
| 139 | continue |
| 140 | } |
| 141 | if macho_tiny_is_module_init_symbol(name) { |
| 142 | return macos_tiny_not_eligible('module init symbol `${name}` is not supported') |
| 143 | } |
| 144 | range := w.text_range(name)! |
| 145 | seen[name] = true |
| 146 | selected << name |
| 147 | for reloc in w.macho.relocs { |
| 148 | if reloc.addr < int(range.start) || reloc.addr >= int(range.end) { |
| 149 | continue |
| 150 | } |
| 151 | validate_macho_tiny_relocation(reloc)! |
| 152 | sym := w.reloc_symbol(reloc)! |
| 153 | if sym.sect == ObjectFormat.macho.section_index(.text) { |
| 154 | if !seen[sym.name] { |
| 155 | queue << sym.name |
| 156 | } |
| 157 | } else if sym.sect == ObjectFormat.macho.section_index(.rodata) { |
| 158 | w.add_data_range(mut selected_data, .rodata, sym.value)! |
| 159 | } else if sym.sect == ObjectFormat.macho.section_index(.data) { |
| 160 | if x64_main_argc_global_name(sym.name) || x64_main_argv_global_name(sym.name) { |
| 161 | return macos_tiny_not_eligible('arguments() requires hosted argc/argv; macOS tiny _main argv is not implemented yet') |
| 162 | } |
| 163 | w.add_data_range(mut selected_data, .data, sym.value)! |
| 164 | } else if sym.sect == 0 && sym.type_ == 0x01 { |
| 165 | undefined[sym.name] = true |
| 166 | } else { |
| 167 | return macos_tiny_not_eligible('relocation to symbol `${sym.name}` in Mach-O section ${sym.sect} is not supported') |
| 168 | } |
| 169 | } |
| 170 | } |
| 171 | return MachOTinyReachable{ |
| 172 | names: selected |
| 173 | data: selected_data |
| 174 | undefined: undefined |
| 175 | } |
| 176 | } |
| 177 | |
| 178 | fn validate_macho_tiny_relocation(reloc MachORelocationInfo) ! { |
| 179 | if !reloc.pcrel || reloc.length != 2 { |
| 180 | return macos_tiny_not_eligible('non-pcrel or non-32-bit relocation at __text+${reloc.addr} is not supported') |
| 181 | } |
| 182 | if reloc.type_ !in [x86_64_reloc_signed, x86_64_reloc_branch, x86_64_reloc_got_load, |
| 183 | x86_64_reloc_got] { |
| 184 | return macos_tiny_not_eligible('Mach-O relocation type ${reloc.type_} is not supported') |
| 185 | } |
| 186 | } |
| 187 | |
| 188 | fn (w MachOTinyObjectWriter) text_range(name string) !MachOTextRange { |
| 189 | ranges := w.text_symbol_ranges() |
| 190 | for range in ranges { |
| 191 | if range.name == name { |
| 192 | return range |
| 193 | } |
| 194 | } |
| 195 | return error('missing text symbol `${name}`') |
| 196 | } |
| 197 | |
| 198 | fn (w MachOTinyObjectWriter) text_symbol_ranges() []MachOTextRange { |
| 199 | mut syms := []MachOSymbol{} |
| 200 | for sym in w.macho.symbols { |
| 201 | if sym.name != '' && sym.sect == ObjectFormat.macho.section_index(.text) { |
| 202 | syms << sym |
| 203 | } |
| 204 | } |
| 205 | for i := 1; i < syms.len; i++ { |
| 206 | mut j := i |
| 207 | for j > 0 && syms[j - 1].value > syms[j].value { |
| 208 | syms[j - 1], syms[j] = syms[j], syms[j - 1] |
| 209 | j-- |
| 210 | } |
| 211 | } |
| 212 | mut ranges := []MachOTextRange{} |
| 213 | for i, sym in syms { |
| 214 | mut end := u64(w.macho.text_data.len) |
| 215 | for j := i + 1; j < syms.len; j++ { |
| 216 | if syms[j].value > sym.value { |
| 217 | end = syms[j].value |
| 218 | break |
| 219 | } |
| 220 | } |
| 221 | if end > sym.value { |
| 222 | ranges << MachOTextRange{ |
| 223 | name: sym.name |
| 224 | start: sym.value |
| 225 | end: end |
| 226 | } |
| 227 | } |
| 228 | } |
| 229 | return ranges |
| 230 | } |
| 231 | |
| 232 | fn (w MachOTinyObjectWriter) reloc_symbol(reloc MachORelocationInfo) !MachOSymbol { |
| 233 | if reloc.sym_idx < 0 || reloc.sym_idx >= w.macho.symbols.len { |
| 234 | return error('macOS tiny object relocation references invalid symbol index ${reloc.sym_idx}') |
| 235 | } |
| 236 | return w.macho.symbols[reloc.sym_idx] |
| 237 | } |
| 238 | |
| 239 | fn (w MachOTinyObjectWriter) symbol_by_name(name string) !MachOSymbol { |
| 240 | idx := w.macho.sym_by_name[name] or { return error('missing symbol `${name}`') } |
| 241 | if idx < 0 || idx >= w.macho.symbols.len { |
| 242 | return error('invalid symbol index ${idx} for `${name}`') |
| 243 | } |
| 244 | return w.macho.symbols[idx] |
| 245 | } |
| 246 | |
| 247 | fn (w MachOTinyObjectWriter) symbol_object_section(sym MachOSymbol) !ObjectSection { |
| 248 | if sym.sect == ObjectFormat.macho.section_index(.rodata) { |
| 249 | return .rodata |
| 250 | } |
| 251 | if sym.sect == ObjectFormat.macho.section_index(.data) { |
| 252 | return .data |
| 253 | } |
| 254 | if sym.sect == ObjectFormat.macho.section_index(.text) { |
| 255 | return .text |
| 256 | } |
| 257 | return error('symbol `${sym.name}` is not in a known Mach-O section') |
| 258 | } |
| 259 | |
| 260 | fn (mut w MachOTinyObjectWriter) add_data_range(mut ranges []MachODataRange, section ObjectSection, start u64) ! { |
| 261 | end := w.data_symbol_range_end(section, start)! |
| 262 | for range in ranges { |
| 263 | if range.section == section && range.start == start && range.end == end { |
| 264 | return |
| 265 | } |
| 266 | } |
| 267 | ranges << MachODataRange{ |
| 268 | section: section |
| 269 | start: start |
| 270 | end: end |
| 271 | } |
| 272 | } |
| 273 | |
| 274 | fn (w MachOTinyObjectWriter) data_symbol_range_end(section ObjectSection, start u64) !u64 { |
| 275 | section_idx := ObjectFormat.macho.section_index(section) |
| 276 | section_len := match section { |
| 277 | .rodata { u64(w.macho.rodata.len) } |
| 278 | .data { u64(w.macho.data_data.len) } |
| 279 | else { u64(0) } |
| 280 | } |
| 281 | |
| 282 | mut end := section_len |
| 283 | for sym in w.macho.symbols { |
| 284 | if sym.sect == section_idx && sym.value > start && sym.value < end { |
| 285 | end = sym.value |
| 286 | } |
| 287 | } |
| 288 | if end < start { |
| 289 | return error('invalid Mach-O data symbol range') |
| 290 | } |
| 291 | return end |
| 292 | } |
| 293 | |
| 294 | fn (w MachOTinyObjectWriter) copy_data_ranges(ranges []MachODataRange, section ObjectSection, mut out []u8, mut offsets map[string]u64) ! { |
| 295 | for dr in ranges { |
| 296 | if dr.section != section { |
| 297 | continue |
| 298 | } |
| 299 | align := int(w.data_section_alignment(section)) |
| 300 | for align > 1 && out.len % align != int(dr.start % u64(align)) { |
| 301 | out << u8(0) |
| 302 | } |
| 303 | off := u64(out.len) |
| 304 | section_idx := ObjectFormat.macho.section_index(section) |
| 305 | for sym in w.macho.symbols { |
| 306 | if sym.sect == section_idx && sym.value == dr.start { |
| 307 | offsets[sym.name] = off |
| 308 | } |
| 309 | } |
| 310 | match section { |
| 311 | .rodata { |
| 312 | out << w.macho.rodata[int(dr.start)..int(dr.end)] |
| 313 | } |
| 314 | .data { |
| 315 | out << w.macho.data_data[int(dr.start)..int(dr.end)] |
| 316 | } |
| 317 | else { |
| 318 | return error('macOS tiny object cannot copy ${section} as data') |
| 319 | } |
| 320 | } |
| 321 | } |
| 322 | } |
| 323 | |
| 324 | fn (w MachOTinyObjectWriter) data_section_alignment(section ObjectSection) u64 { |
| 325 | return match section { |
| 326 | .data { u64(8) } |
| 327 | .rodata { u64(4) } |
| 328 | else { u64(1) } |
| 329 | } |
| 330 | } |
| 331 | |
| 332 | fn macho_tiny_defined_symbol_is_external(name string) bool { |
| 333 | return name == '_main' |
| 334 | } |
| 335 | |
| 336 | fn macho_tiny_resolve_relocation_locally(name string, reloc MachORelocationInfo) bool { |
| 337 | return name in [macos_tiny_builtin_string_plus_symbol, macos_tiny_builtin_i64_str_symbol] |
| 338 | && reloc.type_ == x86_64_reloc_branch && reloc.pcrel && reloc.length == 2 |
| 339 | } |
| 340 | |
| 341 | fn macho_tiny_add_runtime_helpers(mut out MachOObject, mut reachable MachOTinyReachable, mut func_offsets map[string]u64, mut runtime_symbols []string, mut runtime_relocs []MachOTinyRuntimeReloc) { |
| 342 | if reachable.undefined[macos_tiny_builtin_i64_str_symbol] { |
| 343 | reachable.undefined.delete(macos_tiny_builtin_i64_str_symbol) |
| 344 | reachable.undefined[macos_tiny_malloc_symbol] = true |
| 345 | reachable.undefined[macos_tiny_exit_symbol] = true |
| 346 | func_offsets[macos_tiny_builtin_i64_str_symbol] = u64(out.text_data.len) |
| 347 | runtime_symbols << macos_tiny_builtin_i64_str_symbol |
| 348 | macho_tiny_emit_i64_str(mut out.text_data, mut runtime_relocs) |
| 349 | } |
| 350 | if reachable.undefined[macos_tiny_builtin_string_plus_symbol] { |
| 351 | reachable.undefined.delete(macos_tiny_builtin_string_plus_symbol) |
| 352 | reachable.undefined[macos_tiny_malloc_symbol] = true |
| 353 | reachable.undefined[macos_tiny_exit_symbol] = true |
| 354 | func_offsets[macos_tiny_builtin_string_plus_symbol] = u64(out.text_data.len) |
| 355 | runtime_symbols << macos_tiny_builtin_string_plus_symbol |
| 356 | macho_tiny_emit_string_plus(mut out.text_data, mut runtime_relocs) |
| 357 | } |
| 358 | } |
| 359 | |
| 360 | fn macho_tiny_emit_i64_str(mut text []u8, mut runtime_relocs []MachOTinyRuntimeReloc) { |
| 361 | text << [ |
| 362 | u8(0x57), // push rdi, input value |
| 363 | 0xbf, |
| 364 | 0x20, |
| 365 | 0x00, |
| 366 | 0x00, |
| 367 | 0x00, // mov edi, 32 |
| 368 | ] |
| 369 | macho_tiny_emit_call_reloc(mut text, mut runtime_relocs, macos_tiny_malloc_symbol) |
| 370 | text << [ |
| 371 | u8(0x48), |
| 372 | 0x85, |
| 373 | 0xc0, // test rax, rax |
| 374 | ] |
| 375 | malloc_ok := macho_tiny_emit_jcc32(mut text, 0x85) // jne ok |
| 376 | text << [ |
| 377 | u8(0xbf), |
| 378 | 0x01, |
| 379 | 0x00, |
| 380 | 0x00, |
| 381 | 0x00, // mov edi, 1 |
| 382 | ] |
| 383 | macho_tiny_emit_call_reloc(mut text, mut runtime_relocs, macos_tiny_exit_symbol) |
| 384 | text << [u8(0x0f), 0x0b] // ud2 |
| 385 | ok_start := text.len |
| 386 | macho_tiny_patch_rel32_local(mut text, malloc_ok, ok_start) |
| 387 | |
| 388 | text << [ |
| 389 | u8(0x5f), // pop rdi |
| 390 | 0x4c, |
| 391 | 0x8d, |
| 392 | 0x40, |
| 393 | 0x1f, // lea r8, [rax+31] |
| 394 | 0x41, |
| 395 | 0xc6, |
| 396 | 0x00, |
| 397 | 0x00, // mov byte ptr [r8], 0 |
| 398 | 0x48, |
| 399 | 0x89, |
| 400 | 0xf8, // mov rax, rdi |
| 401 | 0x45, |
| 402 | 0x31, |
| 403 | 0xc9, // xor r9d, r9d |
| 404 | 0x45, |
| 405 | 0x31, |
| 406 | 0xd2, // xor r10d, r10d |
| 407 | 0x48, |
| 408 | 0x85, |
| 409 | 0xc0, // test rax, rax |
| 410 | ] |
| 411 | non_negative := macho_tiny_emit_jcc32(mut text, 0x89) // jns non_negative |
| 412 | text << [ |
| 413 | u8(0x41), |
| 414 | 0xb2, |
| 415 | 0x01, // mov r10b, 1 |
| 416 | 0x48, |
| 417 | 0xf7, |
| 418 | 0xd8, // neg rax |
| 419 | ] |
| 420 | non_negative_target := text.len |
| 421 | text << [ |
| 422 | u8(0x48), |
| 423 | 0x85, |
| 424 | 0xc0, // test rax, rax |
| 425 | ] |
| 426 | non_zero := macho_tiny_emit_jcc32(mut text, 0x85) // jne loop |
| 427 | text << [ |
| 428 | u8(0x49), |
| 429 | 0xff, |
| 430 | 0xc8, // dec r8 |
| 431 | 0x41, |
| 432 | 0xc6, |
| 433 | 0x00, |
| 434 | 0x30, // mov byte ptr [r8], '0' |
| 435 | 0x41, |
| 436 | 0xb9, |
| 437 | 0x01, |
| 438 | 0x00, |
| 439 | 0x00, |
| 440 | 0x00, // mov r9d, 1 |
| 441 | ] |
| 442 | maybe_sign_jump := macho_tiny_emit_jmp32(mut text) |
| 443 | loop_start := text.len |
| 444 | text << [ |
| 445 | u8(0x31), |
| 446 | 0xd2, // xor edx, edx |
| 447 | 0xb9, |
| 448 | 0x0a, |
| 449 | 0x00, |
| 450 | 0x00, |
| 451 | 0x00, // mov ecx, 10 |
| 452 | 0x48, |
| 453 | 0xf7, |
| 454 | 0xf1, // div rcx |
| 455 | 0x80, |
| 456 | 0xc2, |
| 457 | 0x30, // add dl, '0' |
| 458 | 0x49, |
| 459 | 0xff, |
| 460 | 0xc8, // dec r8 |
| 461 | 0x41, |
| 462 | 0x88, |
| 463 | 0x10, // mov [r8], dl |
| 464 | 0x49, |
| 465 | 0xff, |
| 466 | 0xc1, // inc r9 |
| 467 | 0x48, |
| 468 | 0x85, |
| 469 | 0xc0, // test rax, rax |
| 470 | ] |
| 471 | loop_more := macho_tiny_emit_jcc32(mut text, 0x85) // jne loop |
| 472 | maybe_sign := text.len |
| 473 | text << [ |
| 474 | u8(0x45), |
| 475 | 0x84, |
| 476 | 0xd2, // test r10b, r10b |
| 477 | ] |
| 478 | done_digits := macho_tiny_emit_jcc32(mut text, 0x84) // je done |
| 479 | text << [ |
| 480 | u8(0x49), |
| 481 | 0xff, |
| 482 | 0xc8, // dec r8 |
| 483 | 0x41, |
| 484 | 0xc6, |
| 485 | 0x00, |
| 486 | 0x2d, // mov byte ptr [r8], '-' |
| 487 | 0x49, |
| 488 | 0xff, |
| 489 | 0xc1, // inc r9 |
| 490 | ] |
| 491 | done_digits_target := text.len |
| 492 | text << [ |
| 493 | u8(0x4c), |
| 494 | 0x89, |
| 495 | 0xc0, // mov rax, r8 |
| 496 | 0x4c, |
| 497 | 0x89, |
| 498 | 0xca, // mov rdx, r9 |
| 499 | 0xc3, // ret |
| 500 | ] |
| 501 | macho_tiny_patch_rel32_local(mut text, non_negative, non_negative_target) |
| 502 | macho_tiny_patch_rel32_local(mut text, non_zero, loop_start) |
| 503 | macho_tiny_patch_rel32_local(mut text, maybe_sign_jump, maybe_sign) |
| 504 | macho_tiny_patch_rel32_local(mut text, loop_more, loop_start) |
| 505 | macho_tiny_patch_rel32_local(mut text, done_digits, done_digits_target) |
| 506 | } |
| 507 | |
| 508 | fn macho_tiny_emit_string_plus(mut text []u8, mut runtime_relocs []MachOTinyRuntimeReloc) { |
| 509 | text << [ |
| 510 | u8(0x57), // push rdi, first string ptr |
| 511 | 0x56, // push rsi, first string len |
| 512 | 0x52, // push rdx, second string ptr |
| 513 | 0x51, // push rcx, second string len |
| 514 | 0x48, |
| 515 | 0x83, |
| 516 | 0xec, |
| 517 | 0x08, // sub rsp, 8, align before libSystem calls |
| 518 | 0x44, |
| 519 | 0x8b, |
| 520 | 0x44, |
| 521 | 0x24, |
| 522 | 0x18, // mov r8d, [rsp+24] |
| 523 | 0x44, |
| 524 | 0x03, |
| 525 | 0x44, |
| 526 | 0x24, |
| 527 | 0x08, // add r8d, [rsp+8] |
| 528 | ] |
| 529 | len_overflow := macho_tiny_emit_jcc32(mut text, 0x82) // jc fail |
| 530 | text << [ |
| 531 | u8(0x4d), |
| 532 | 0x89, |
| 533 | 0xc1, // mov r9, r8 |
| 534 | 0x49, |
| 535 | 0x83, |
| 536 | 0xc1, |
| 537 | 0x01, // add r9, 1 |
| 538 | ] |
| 539 | alloc_overflow := macho_tiny_emit_jcc32(mut text, 0x82) // jc fail |
| 540 | text << [ |
| 541 | u8(0x4c), |
| 542 | 0x89, |
| 543 | 0xcf, // mov rdi, r9 |
| 544 | ] |
| 545 | macho_tiny_emit_call_reloc(mut text, mut runtime_relocs, macos_tiny_malloc_symbol) |
| 546 | text << [ |
| 547 | u8(0x48), |
| 548 | 0x85, |
| 549 | 0xc0, // test rax, rax |
| 550 | ] |
| 551 | malloc_ok := macho_tiny_emit_jcc32(mut text, 0x85) // jne ok |
| 552 | fail_start := text.len |
| 553 | text << [ |
| 554 | u8(0xbf), |
| 555 | 0x01, |
| 556 | 0x00, |
| 557 | 0x00, |
| 558 | 0x00, // mov edi, 1 |
| 559 | ] |
| 560 | macho_tiny_emit_call_reloc(mut text, mut runtime_relocs, macos_tiny_exit_symbol) |
| 561 | text << [u8(0x0f), 0x0b] // ud2 |
| 562 | ok_start := text.len |
| 563 | macho_tiny_patch_rel32_local(mut text, len_overflow, fail_start) |
| 564 | macho_tiny_patch_rel32_local(mut text, alloc_overflow, fail_start) |
| 565 | macho_tiny_patch_rel32_local(mut text, malloc_ok, ok_start) |
| 566 | |
| 567 | text << [ |
| 568 | u8(0x49), |
| 569 | 0x89, |
| 570 | 0xc2, // mov r10, rax |
| 571 | 0x49, |
| 572 | 0x89, |
| 573 | 0xc0, // mov r8, rax |
| 574 | 0x48, |
| 575 | 0x8b, |
| 576 | 0x74, |
| 577 | 0x24, |
| 578 | 0x20, // mov rsi, [rsp+32] |
| 579 | 0x8b, |
| 580 | 0x4c, |
| 581 | 0x24, |
| 582 | 0x18, // mov ecx, [rsp+24] |
| 583 | 0x48, |
| 584 | 0x85, |
| 585 | 0xc9, // test rcx, rcx |
| 586 | ] |
| 587 | copy_a_done := macho_tiny_emit_jcc32(mut text, 0x84) // jz done |
| 588 | copy_a_loop := text.len |
| 589 | text << [ |
| 590 | u8(0x8a), |
| 591 | 0x16, // mov dl, [rsi] |
| 592 | 0x41, |
| 593 | 0x88, |
| 594 | 0x10, // mov [r8], dl |
| 595 | 0x48, |
| 596 | 0xff, |
| 597 | 0xc6, // inc rsi |
| 598 | 0x49, |
| 599 | 0xff, |
| 600 | 0xc0, // inc r8 |
| 601 | 0x48, |
| 602 | 0xff, |
| 603 | 0xc9, // dec rcx |
| 604 | ] |
| 605 | copy_a_continue := macho_tiny_emit_jcc32(mut text, 0x85) // jne loop |
| 606 | copy_a_done_target := text.len |
| 607 | macho_tiny_patch_rel32_local(mut text, copy_a_done, copy_a_done_target) |
| 608 | macho_tiny_patch_rel32_local(mut text, copy_a_continue, copy_a_loop) |
| 609 | |
| 610 | text << [ |
| 611 | u8(0x48), |
| 612 | 0x8b, |
| 613 | 0x74, |
| 614 | 0x24, |
| 615 | 0x10, // mov rsi, [rsp+16] |
| 616 | 0x8b, |
| 617 | 0x4c, |
| 618 | 0x24, |
| 619 | 0x08, // mov ecx, [rsp+8] |
| 620 | 0x48, |
| 621 | 0x85, |
| 622 | 0xc9, // test rcx, rcx |
| 623 | ] |
| 624 | copy_b_done := macho_tiny_emit_jcc32(mut text, 0x84) // jz done |
| 625 | copy_b_loop := text.len |
| 626 | text << [ |
| 627 | u8(0x8a), |
| 628 | 0x16, // mov dl, [rsi] |
| 629 | 0x41, |
| 630 | 0x88, |
| 631 | 0x10, // mov [r8], dl |
| 632 | 0x48, |
| 633 | 0xff, |
| 634 | 0xc6, // inc rsi |
| 635 | 0x49, |
| 636 | 0xff, |
| 637 | 0xc0, // inc r8 |
| 638 | 0x48, |
| 639 | 0xff, |
| 640 | 0xc9, // dec rcx |
| 641 | ] |
| 642 | copy_b_continue := macho_tiny_emit_jcc32(mut text, 0x85) // jne loop |
| 643 | copy_b_done_target := text.len |
| 644 | macho_tiny_patch_rel32_local(mut text, copy_b_done, copy_b_done_target) |
| 645 | macho_tiny_patch_rel32_local(mut text, copy_b_continue, copy_b_loop) |
| 646 | |
| 647 | text << [ |
| 648 | u8(0x41), |
| 649 | 0xc6, |
| 650 | 0x00, |
| 651 | 0x00, // mov byte ptr [r8], 0 |
| 652 | 0x8b, |
| 653 | 0x44, |
| 654 | 0x24, |
| 655 | 0x18, // mov eax, [rsp+24] |
| 656 | 0x03, |
| 657 | 0x44, |
| 658 | 0x24, |
| 659 | 0x08, // add eax, [rsp+8] |
| 660 | 0x48, |
| 661 | 0x89, |
| 662 | 0xc2, // mov rdx, rax |
| 663 | 0x4c, |
| 664 | 0x89, |
| 665 | 0xd0, // mov rax, r10 |
| 666 | 0x48, |
| 667 | 0x83, |
| 668 | 0xc4, |
| 669 | 0x28, // add rsp, 40 |
| 670 | 0xc3, // ret |
| 671 | ] |
| 672 | } |
| 673 | |
| 674 | fn macho_tiny_emit_call_reloc(mut text []u8, mut runtime_relocs []MachOTinyRuntimeReloc, symbol string) { |
| 675 | text << u8(0xe8) |
| 676 | field_off := text.len |
| 677 | text << [u8(0), 0, 0, 0] |
| 678 | runtime_relocs << MachOTinyRuntimeReloc{ |
| 679 | addr: field_off |
| 680 | symbol: symbol |
| 681 | } |
| 682 | } |
| 683 | |
| 684 | fn macho_tiny_emit_jcc32(mut text []u8, opcode u8) int { |
| 685 | text << [u8(0x0f), opcode] |
| 686 | field_off := text.len |
| 687 | text << [u8(0), 0, 0, 0] |
| 688 | return field_off |
| 689 | } |
| 690 | |
| 691 | fn macho_tiny_emit_jmp32(mut text []u8) int { |
| 692 | text << u8(0xe9) |
| 693 | field_off := text.len |
| 694 | text << [u8(0), 0, 0, 0] |
| 695 | return field_off |
| 696 | } |
| 697 | |
| 698 | fn macho_tiny_patch_rel32_local(mut text []u8, field_off int, target_off int) { |
| 699 | disp := i64(target_off) - i64(field_off + 4) |
| 700 | binary.little_endian_put_u32(mut text[field_off..field_off + 4], u32(i32(disp))) |
| 701 | } |
| 702 | |
| 703 | fn macho_tiny_is_module_init_symbol(name string) bool { |
| 704 | return name.ends_with('__init') && name != '_main' |
| 705 | } |
| 706 | |
| 707 | fn macos_tiny_not_eligible(message string) IError { |
| 708 | return error(macos_tiny_not_eligible_prefix + message) |
| 709 | } |
| 710 | |