vxx2 / vlib / v3 / gen / arm64 / linker.v
1602 lines · 1421 sloc · 50.04 KB · 6c4d26f1f98c5464b012a375667ab345e462f7e0
Raw
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
5module arm64
6
7import os
8import time
9
10// C.open declares the C open symbol used by arm64.
11fn C.open(charptr, int, int) int
12
13// C.write declares the C write symbol used by arm64.
14fn C.write(int, voidptr, int) int
15
16// C.close declares the C close symbol used by arm64.
17fn C.close(int) int
18
19// C.chmod declares the C chmod symbol used by arm64.
20fn C.chmod(charptr, int) int
21
22// C.rename declares the C rename symbol used by arm64.
23fn C.rename(charptr, charptr) int
24
25// Mach-O executable constants
26const mh_execute = 2
27const lc_load_dylinker = 0xe
28const lc_load_dylib = 0xc
29const lc_main = u32(0x80000028)
30const lc_dyld_info_only = u32(0x80000022)
31const lc_dysymtab = 0xb
32const lc_uuid = 0x1b
33const lc_build_version = 0x32
34const lc_source_version = 0x2a
35const lc_code_signature = 0x1d
36
37// Code signing constants (big-endian magic numbers)
38const csmagic_embedded_signature = u32(0xfade0cc0)
39const csmagic_codedirectory = u32(0xfade0c02)
40const csmagic_requirements = u32(0xfade0c01)
41const csmagic_blobwrapper = u32(0xfade0b01)
42const csslot_codedirectory = u32(0)
43const csslot_requirements = u32(2)
44const csslot_cms_signature = u32(0x10000)
45const cs_adhoc = u32(0x2) // Ad-hoc signing flag
46const cs_hashtype_sha256 = u8(2)
47const cs_hash_size = 32 // SHA256 = 32 bytes
48const cs_page_size_arm64 = 16384 // Code signing page size for ARM64 macOS
49const cs_page_shift_arm64 = 14 // log2(16384)
50const o_wronly_creat_trunc = 0x601 // O_WRONLY | O_CREAT | O_TRUNC on Darwin
51
52// ARM64 page size on macOS
53const page_size = 0x4000 // 16KB
54
55// Base address for executables
56const base_addr = u64(0x100000000)
57
58// Bind opcodes for dyld
59const bind_opcode_done = 0x00
60const bind_opcode_set_dylib_ordinal_imm = 0x10
61const bind_opcode_set_symbol_flags_imm = 0x40
62const bind_opcode_set_type_imm = 0x50
63const bind_opcode_set_segment_and_offset_uleb = 0x70
64const bind_opcode_do_bind = 0x90
65const bind_type_pointer = 1
66const bind_symbol_flags_weak_import = 0x01
67
68// vfmt off
69
70// Libc symbols that should ALWAYS resolve to the external system library,
71// never to local V wrappers. This prevents infinite recursion where
72// V's malloc() wrapper calls C.malloc() which would otherwise resolve
73// back to the V wrapper.
74const force_external_syms = ['_malloc', '_free', '_calloc', '_realloc', '_exit', '_abort', '_memcpy',
75 '_memmove', '_memset', '_memcmp', '___stdoutp', '___stderrp', '_puts', '_printf', '_write',
76 '_read', '_open', '_close', '_fwrite', '_fflush', '_fopen', '_fclose', '_putchar', '_sprintf',
77 '_snprintf', '_fprintf', '_sscanf', '_mmap', '_munmap', '_getcwd', '_access', '_readlink',
78 '_getenv', '_strlen',
79 // Filesystem/directory operations
80 '_opendir', '_readdir', '_closedir', '_mkdir', '_rmdir',
81 '_unlink', '_rename', '_remove', '_stat', '_lstat', '_fstat', '_chmod', '_chdir', '_realpath',
82 '_symlink', '_link',
83 // Process/system
84 '_getpid', '_getuid', '_geteuid', '_fork', '_execve', '_execvp', '_waitpid',
85 '_kill', '_system', '_posix_spawn', '_signal', '_atexit',
86 // I/O
87 '_fgets', '_fputs', '_fread', '_fseek', '_ftell', '_rewind', '_fileno', '_popen',
88 '_pclose', '_dup', '_dup2', '_pipe', '_isatty', '_freopen', '_dprintf', '_getc',
89 // String/memory
90 '_strdup', '_strcmp', '_strncmp', '_strchr', '_strrchr', '_strerror',
91 '_strncasecmp', '_strcasecmp', '_atoi', '_atof', '_qsort',
92 // Time
93 '_time', '_localtime_r', '_gmtime_r', '_mktime', '_gettimeofday', '_clock',
94 '_clock_gettime_nsec_np', '_mach_absolute_time', '_mach_timebase_info', '_nanosleep', '_sleep',
95 '_usleep', '_strftime',
96 '_task_info', '_mach_task_self_',
97 // Other
98 '_rand', '_srand', '_isdigit', '_isspace', '_tolower', '_toupper', '_setenv',
99 '_unsetenv', '_sysconf', '_uname', '_gethostname', '_pthread_mutex_init', '_pthread_mutex_lock',
100 '_pthread_mutex_unlock', '_pthread_mutex_destroy', '_pthread_self', '_pthread_create',
101 '_pthread_join', '_pthread_attr_init', '_pthread_attr_setstacksize', '_pthread_attr_destroy',
102 '_arc4random_buf',
103 '_proc_pidpath', '_backtrace', '_backtrace_symbols', '_backtrace_symbols_fd',
104 // macOS specific
105 '_dispatch_semaphore_create', '_dispatch_semaphore_signal',
106 '_dispatch_semaphore_wait', '_dispatch_time', '_dispatch_release', '_setvbuf', '_setbuf',
107 '_memchr', '_getlogin_r', '_getppid', '_getgid', '_getegid', '_ftruncate', '_mkstemp', '_statvfs',
108 '_chown', '_sigaction', '_sigemptyset', '_sigaddset', '_sigprocmask', '_select', '_kqueue',
109 '_abs',
110 // Terminal I/O
111 '_tcgetattr', '_tcsetattr', '_ioctl', '_getchar', '_getline',
112 // File I/O
113 '_fdopen', '_feof', '_ferror',
114 // Process
115 '_setpgid', '_ptrace', '_wait',
116 // Time
117 '_timegm', '_clock_gettime',
118 // Memory
119 '_aligned_alloc',
120 // System
121 '_utime', '_getlogin', '_environ',
122 // macOS errno: __error() returns int*
123 '___error',
124 // macOS stdin
125 '___stdinp',
126 // macOS dyld
127 '__dyld_get_image_name', '__dyld_get_image_header',
128 // Math
129 '_cos', '_sin', '_tan', '_acos', '_asin', '_atan', '_atan2',
130 '_cosh', '_sinh', '_tanh', '_acosh', '_asinh', '_atanh',
131 '_exp', '_exp2', '_log', '_log2', '_log10', '_pow', '_sqrt', '_cbrt',
132 '_ceil', '_floor', '_round', '_trunc', '_fmod', '_remainder',
133 '_fabs', '_copysign', '_fmax', '_fmin', '_hypot',
134 '_ldexp', '_frexp', '_modf', '_scalbn', '_ilogb', '_logb',
135 '_erf', '_erfc', '_lgamma', '_tgamma',
136 '_j0', '_j1', '_jn', '_y0', '_y1', '_yn',
137 // Memory protection and cache (hot code reloading)
138 '_mprotect', '_sys_icache_invalidate',
139 // Objective-C runtime (from libobjc.A.dylib)
140 '_objc_msgSend', '_objc_getClass', '_sel_registerName', '_objc_alloc_init',
141 '_objc_autoreleasePoolPush', '_objc_autoreleasePoolPop',
142 // Metal framework
143 '_MTLCreateSystemDefaultDevice',
144 // Dynamic loading
145 '_dlopen', '_dlsym']
146
147// vfmt on
148
149// Symbols that live in libobjc.A.dylib (not libSystem).
150const objc_syms = ['_objc_msgSend', '_objc_getClass', '_sel_registerName', '_objc_alloc_init',
151 '_objc_autoreleasePoolPush', '_objc_autoreleasePoolPop']
152
153// Symbols that live in Metal.framework.
154const metal_syms = ['_MTLCreateSystemDefaultDevice']
155
156// Linker represents linker data used by arm64.
157pub struct Linker {
158 macho &MachOObject
159pub mut:
160 // Frameworks to link (e.g. ['Metal', 'Cocoa', 'QuartzCore'])
161 frameworks []string
162mut:
163 // Output buffer
164 buf []u8
165
166 // Segment/section info
167 text_vmaddr u64
168 text_fileoff int
169 text_size int
170 data_vmaddr u64
171 data_fileoff int
172 data_size int
173 linkedit_off int
174 linkedit_size int
175
176 // External symbols needing binding
177 extern_syms []string
178
179 // GOT entries for external symbols
180 got_offset int // Offset within __DATA segment
181 got_size int
182
183 // Stubs for external function calls
184 stubs_offset int
185 stubs_size int
186
187 // Symbol to GOT index mapping
188 sym_to_got map[string]int
189
190 // Multi-dylib support: dylib paths and per-symbol ordinal mapping
191 dylibs []string // ['/usr/lib/libSystem.B.dylib', '/usr/lib/libobjc.A.dylib', ...]
192 sym_to_dylib map[string]int // symbol name → index into dylibs[] (ordinal = idx + 1)
193
194 // Code start offset (after header + load commands)
195 code_start int
196}
197
198// new creates a Linker value for arm64.
199pub fn Linker.new(macho &MachOObject) &Linker {
200 return unsafe {
201 &Linker{
202 macho: macho
203 frameworks: []string{}
204 buf: []u8{}
205 extern_syms: []string{}
206 sym_to_got: map[string]int{}
207 dylibs: []string{}
208 sym_to_dylib: map[string]int{}
209 }
210 }
211}
212
213// link supports link handling for Linker.
214pub fn (mut l Linker) link(output_path string, entry_name string) {
215 // Pre-allocate buffer with estimated size to avoid reallocations
216 estimated_size := l.macho.text_data.len + l.macho.str_data.len + l.macho.data_data.len + 0x10000
217 l.buf = []u8{cap: estimated_size}
218 mut t := time.now()
219 mut t_total := time.now()
220
221 // First pass: collect all defined symbols (except external ones)
222 mut defined_syms := map[string]bool{}
223 for sym in l.macho.symbols {
224 // N_SECT (0x0E) means symbol is defined in a section
225 if (sym.type_ & 0x0E) == 0x0E {
226 // Don't track external symbols as defined - they should come from libc
227 if sym.name !in force_external_syms {
228 defined_syms[sym.name] = true
229 }
230 }
231 }
232
233 // Second pass: collect truly external symbols.
234 // force_external_syms should go through GOT/stubs.
235 // All other undefined symbols are internal V functions or V-embedded C functions
236 // (like wyhash) that resolve to local stubs.
237 for sym in l.macho.symbols {
238 if sym.name in force_external_syms && sym.name !in l.extern_syms {
239 l.extern_syms << sym.name
240 l.sym_to_got[sym.name] = l.extern_syms.len - 1
241 }
242 }
243
244 l.got_size = l.extern_syms.len * 8
245 l.stubs_size = l.extern_syms.len * 12 // Each stub is 12 bytes on ARM64
246
247 // Build dylib list: libSystem always first, then libobjc + frameworks as needed.
248 l.dylibs = ['/usr/lib/libSystem.B.dylib']
249 // Map all existing symbols to libSystem (ordinal 0 = index into dylibs)
250 for sym_name in l.extern_syms {
251 l.sym_to_dylib[sym_name] = 0 // default: libSystem
252 }
253 // Check if any objc symbols are used → add libobjc
254 mut need_objc := false
255 for sym_name in l.extern_syms {
256 if sym_name in objc_syms {
257 need_objc = true
258 break
259 }
260 }
261 if need_objc {
262 objc_idx := l.dylibs.len
263 l.dylibs << '/usr/lib/libobjc.A.dylib'
264 for sym_name in l.extern_syms {
265 if sym_name in objc_syms {
266 l.sym_to_dylib[sym_name] = objc_idx
267 }
268 }
269 }
270 // Check if any framework symbols are used → add framework dylibs
271 for sym_name in l.extern_syms {
272 if sym_name in metal_syms {
273 if 'Metal' !in l.frameworks {
274 l.frameworks << 'Metal'
275 }
276 }
277 }
278 for fw in l.frameworks {
279 fw_idx := l.dylibs.len
280 l.dylibs << '/System/Library/Frameworks/${fw}.framework/${fw}'
281 // Map framework symbols to this dylib index
282 if fw == 'Metal' {
283 for sym_name in l.extern_syms {
284 if sym_name in metal_syms {
285 l.sym_to_dylib[sym_name] = fw_idx
286 }
287 }
288 }
289 }
290
291 // Calculate layout
292 // On macOS, __TEXT segment MUST start at fileoff 0
293 // The header and load commands are inside the __TEXT segment
294 n_load_cmds := 13 + l.dylibs.len // 13 fixed commands + 1 LC_LOAD_DYLIB per dylib
295 pagezero_cmd_size := 72
296 text_cmd_size := 72 + (80 * 2) // __text + __stubs
297 data_cmd_size := 72 + (80 * 2) // __data + __got
298 linkedit_cmd_size := 72
299 dyld_info_cmd_size := 48
300 symtab_cmd_size := 24
301 dysymtab_cmd_size := 80
302 dylinker_cmd_size := 32
303 // Each LC_LOAD_DYLIB: 24 bytes header + path padded to 8-byte alignment
304 mut dylib_cmd_size := 0
305 for dylib_path in l.dylibs {
306 path_len := dylib_path.len + 1 // +1 for null terminator
307 padded_path := (path_len + 7) & ~7
308 dylib_cmd_size += 24 + padded_path
309 }
310 main_cmd_size := 24
311 uuid_cmd_size := 24
312 build_version_cmd_size := 24
313 source_version_cmd_size := 16
314 code_signature_cmd_size := 16
315
316 load_cmds_size := pagezero_cmd_size + text_cmd_size + data_cmd_size + linkedit_cmd_size +
317 dyld_info_cmd_size + symtab_cmd_size + dysymtab_cmd_size + dylinker_cmd_size +
318 dylib_cmd_size + main_cmd_size + uuid_cmd_size + build_version_cmd_size +
319 source_version_cmd_size + code_signature_cmd_size
320
321 // __TEXT starts at file offset 0 and vmaddr base_addr
322 l.text_fileoff = 0
323 l.text_vmaddr = base_addr
324
325 // Code starts after header + load commands, aligned to 16 bytes
326 // Leave ~600 bytes extra for codesign to add LC_CODE_SIGNATURE
327 // Header (32) + load_cmds (~700) + codesign reserve (600) ≈ 1332, align to 2048
328 header_size := 32
329 code_start_min := header_size + load_cmds_size + 600 // Reserve for codesign
330 l.code_start = (code_start_min + 15) & ~15 // Align to 16 bytes
331
332 // Calculate where stubs will be (after code and cstrings)
333 l.stubs_offset = l.code_start + l.macho.text_data.len + l.macho.str_data.len
334 // Align to 4 bytes
335 for l.stubs_offset % 4 != 0 {
336 l.stubs_offset++
337 }
338
339 // Text segment size includes header, load commands, code, cstrings, stubs
340 text_content_end := l.stubs_offset + l.stubs_size
341 l.text_size = (text_content_end + page_size - 1) & ~(page_size - 1)
342
343 // Data segment follows text
344 l.data_fileoff = l.text_size
345 l.data_vmaddr = base_addr + u64(l.text_size)
346
347 // GOT offset within data section
348 mut got_offset := l.macho.data_data.len
349 // Align GOT to 8 bytes
350 for got_offset % 8 != 0 {
351 got_offset++
352 }
353 l.got_offset = got_offset
354
355 data_content_size := got_offset + l.got_size
356 l.data_size = (data_content_size + page_size - 1) & ~(page_size - 1)
357 if l.data_size == 0 {
358 l.data_size = page_size
359 }
360
361 bind_info := l.generate_bind_info(got_offset)
362 bind_size := bind_info.len
363
364 // Write header
365 l.write_header(n_load_cmds, load_cmds_size)
366
367 // Write load commands
368 l.write_pagezero_segment()
369 l.write_text_segment()
370 l.write_data_segment()
371 linkedit_start := l.buf.len
372 l.write_linkedit_segment() // Will patch later
373
374 // Bind info position (in LINKEDIT)
375 bind_off := l.data_fileoff + l.data_size
376
377 // Build symbol table for internal function names (visible in objdump -d)
378 mut symtab_data := []u8{}
379 mut strtab_data := []u8{}
380 strtab_data << 0 // First byte of string table must be null
381
382 sym_code_vmaddr := l.text_vmaddr + u64(l.code_start)
383
384 // Find data section base address (minimum symbol value in sect 3)
385 mut sym_data_base := u64(0xFFFFFFFFFFFFFFFF)
386 for sym in l.macho.symbols {
387 if (sym.type_ & 0x0E) == 0x0E && sym.sect == 3 {
388 if sym.value < sym_data_base {
389 sym_data_base = sym.value
390 }
391 }
392 }
393 if sym_data_base == 0xFFFFFFFFFFFFFFFF {
394 sym_data_base = u64(l.macho.text_data.len + l.macho.str_data.len)
395 }
396
397 for sym in l.macho.symbols {
398 if (sym.type_ & 0x0E) != 0x0E {
399 continue // Skip undefined symbols
400 }
401 if sym.name in force_external_syms {
402 continue
403 }
404
405 mut vm_addr := u64(0)
406 mut out_sect := u8(0)
407 if sym.sect == 1 {
408 // __text section
409 vm_addr = sym_code_vmaddr + sym.value
410 out_sect = 1
411 } else if sym.sect == 3 {
412 // __data section
413 vm_addr = l.data_vmaddr + (sym.value - sym_data_base)
414 out_sect = 3
415 } else {
416 continue
417 }
418
419 str_idx := strtab_data.len
420 strtab_data << sym.name.bytes()
421 strtab_data << 0
422
423 write_u32_le(mut symtab_data, u32(str_idx)) // n_strx
424 symtab_data << sym.type_ // n_type
425 symtab_data << out_sect // n_sect
426 write_u16_le(mut symtab_data, sym.desc) // n_desc
427 write_u64_le(mut symtab_data, vm_addr) // n_value
428 }
429
430 // Symbol table follows bind info and must be aligned in LINKEDIT.
431 symtab_unaligned_off := bind_off + bind_size
432 symtab_off := (symtab_unaligned_off + 7) & ~7
433 symtab_pad := symtab_off - symtab_unaligned_off
434 n_syms := symtab_data.len / 16
435 strtab_off := symtab_off + symtab_data.len
436 strtab_size := strtab_data.len
437
438 // Code signature follows string table and should be aligned in LINKEDIT.
439 code_limit_unaligned := strtab_off + strtab_size
440 cs_off := (code_limit_unaligned + 15) & ~15
441 cs_pad := cs_off - code_limit_unaligned
442 // code_limit is where the signature starts (everything before is hashed)
443 code_limit := cs_off
444 // Signature size: SuperBlob(12) + 2*BlobIndex(8) + CodeDirectory header + identifier
445 // + hashes + Requirements blob
446 ident := output_path.all_after_last('/') // Use filename as identifier
447 cs_size := l.estimate_signature_size(code_limit, ident)
448
449 l.linkedit_off = bind_off
450 l.linkedit_size = bind_size + symtab_pad + symtab_data.len + strtab_size + cs_pad + cs_size
451
452 l.write_dyld_info(bind_off, bind_size)
453 l.write_symtab(symtab_off, n_syms, strtab_off, strtab_size)
454 l.write_dysymtab(n_syms)
455 l.write_load_dylinker()
456 l.write_load_dylibs()
457
458 // Find entry point
459 entry_off := l.find_entry_offset(entry_name)
460 l.write_main_cmd(entry_off)
461
462 l.write_uuid()
463 l.write_build_version()
464 l.write_source_version()
465
466 // Write LC_CODE_SIGNATURE (will be at cs_off with size cs_size)
467 codesig_cmd_start := l.buf.len
468 l.write_code_signature_cmd(cs_off, cs_size)
469
470 // Patch LINKEDIT segment with actual values (including signature)
471 l.patch_linkedit(linkedit_start, bind_off, l.linkedit_size)
472
473 println(' headers+cmds: ${time.since(t)}')
474 t = time.now()
475
476 // Pad to code start (after header + load commands)
477 l.pad_to(l.code_start)
478
479 // Write text section with relocations applied
480 l.write_text_with_relocations()
481
482 println(' text+relocs: ${time.since(t)}')
483 t = time.now()
484
485 // Write cstring section
486 l.buf << l.macho.str_data
487
488 // Pad and write stubs
489 l.pad_to(l.stubs_offset)
490 l.write_stubs()
491
492 // Pad to data start
493 l.pad_to(l.data_fileoff)
494
495 // Write data section
496 l.buf << l.macho.data_data
497
498 // Pad to GOT offset and write GOT (initially zeros, dyld will fill)
499 l.pad_to(l.data_fileoff + got_offset)
500 l.write_zeros(l.extern_syms.len * 8)
501
502 // Pad data segment
503 l.pad_to(l.data_fileoff + l.data_size)
504
505 // Write LINKEDIT content
506 l.buf << bind_info
507 l.write_zeros(symtab_pad)
508
509 // Write symbol table nlist entries
510 l.buf << symtab_data
511
512 // Write string table
513 l.buf << strtab_data
514
515 // Align code signature start in LINKEDIT.
516 l.write_zeros(cs_pad)
517
518 println(' padding+data: ${time.since(t)}')
519 t = time.now()
520
521 // Generate and write code signature (ad-hoc signing)
522 signature := l.generate_code_signature(ident)
523 l.buf << signature
524
525 // Patch LC_CODE_SIGNATURE if size differs from estimate
526 actual_cs_size := signature.len
527 if actual_cs_size != cs_size {
528 // Patch datasize in LC_CODE_SIGNATURE command
529 write_u32_le_at(mut l.buf, codesig_cmd_start + 12, u32(actual_cs_size))
530 }
531
532 println(' codesign: ${time.since(t)}')
533 t = time.now()
534
535 tmp_output_path := '${output_path}.tmp.${os.getpid()}'
536 if !write_file_array_raw(tmp_output_path, l.buf) {
537 panic('failed to write output file')
538 }
539 if C.chmod(tmp_output_path.str, 0o755) != 0 {
540 panic('failed to chmod output file')
541 }
542 if C.rename(tmp_output_path.str, output_path.str) != 0 {
543 panic('failed to rename output file')
544 }
545
546 println(' file write: ${time.since(t)}')
547 println(' TOTAL linker: ${time.since(t_total)}')
548}
549
550// write_file_array_raw writes file array raw output for arm64.
551fn write_file_array_raw(path string, data []u8) bool {
552 fd := C.open(path.str, o_wronly_creat_trunc, 0o755)
553 if fd < 0 {
554 return false
555 }
556 if data.len > 0 {
557 written := C.write(fd, data.data, data.len)
558 if written != data.len {
559 C.close(fd)
560 return false
561 }
562 }
563 return C.close(fd) == 0
564}
565
566// write_header writes header output for arm64.
567fn (mut l Linker) write_header(ncmds int, cmdsize int) {
568 write_mh_magic_64(mut l.buf)
569 write_u32_le(mut l.buf, u32(cpu_type_arm64))
570 write_u32_le(mut l.buf, u32(cpu_subtype_arm64_all))
571 write_u32_le(mut l.buf, mh_execute)
572 write_u32_le(mut l.buf, u32(ncmds))
573 write_u32_le(mut l.buf, u32(cmdsize))
574 write_u32_le(mut l.buf, 0x00200085) // MH_NOUNDEFS | MH_DYLDLINK | MH_TWOLEVEL | MH_PIE
575 write_u32_le(mut l.buf, 0) // reserved
576}
577
578// write_pagezero_segment writes pagezero segment output for arm64.
579fn (mut l Linker) write_pagezero_segment() {
580 write_u32_le(mut l.buf, u32(lc_segment_64))
581 write_u32_le(mut l.buf, 72)
582 write_string_fixed(mut l.buf, '__PAGEZERO', 16)
583 write_u64_le(mut l.buf, 0) // vmaddr
584 write_u64_le(mut l.buf, base_addr) // vmsize
585 write_u64_le(mut l.buf, 0) // fileoff
586 write_u64_le(mut l.buf, 0) // filesize
587 write_u32_le(mut l.buf, 0) // maxprot
588 write_u32_le(mut l.buf, 0) // initprot
589 write_u32_le(mut l.buf, 0) // nsects
590 write_u32_le(mut l.buf, 0) // flags
591}
592
593// write_text_segment writes text segment output for arm64.
594fn (mut l Linker) write_text_segment() {
595 write_u32_le(mut l.buf, u32(lc_segment_64))
596 write_u32_le(mut l.buf, 72 + 80 * 2) // cmd size with 2 sections
597 write_string_fixed(mut l.buf, '__TEXT', 16)
598
599 write_u64_le(mut l.buf, l.text_vmaddr) // vmaddr = base_addr
600 write_u64_le(mut l.buf, u64(l.text_size)) // vmsize
601 write_u64_le(mut l.buf, 0) // fileoff MUST be 0
602 write_u64_le(mut l.buf, u64(l.text_size)) // filesize
603 write_u32_le(mut l.buf, 5) // maxprot (r-x)
604 write_u32_le(mut l.buf, 5) // initprot (r-x)
605 write_u32_le(mut l.buf, 2) // nsects
606 write_u32_le(mut l.buf, 0) // flags
607
608 // __text section (code starts at code_start offset)
609 write_string_fixed(mut l.buf, '__text', 16)
610 write_string_fixed(mut l.buf, '__TEXT', 16)
611 write_u64_le(mut l.buf, l.text_vmaddr + u64(l.code_start)) // addr
612 write_u64_le(mut l.buf, u64(l.macho.text_data.len)) // size
613 write_u32_le(mut l.buf, u32(l.code_start)) // offset
614 write_u32_le(mut l.buf, 4) // align (16 bytes = 2^4)
615 write_u32_le(mut l.buf, 0) // reloff
616 write_u32_le(mut l.buf, 0) // nreloc
617 write_u32_le(mut l.buf, 0x80000400) // flags: S_ATTR_PURE_INSTRUCTIONS | S_ATTR_SOME_INSTRUCTIONS
618 write_u32_le(mut l.buf, 0) // reserved1
619 write_u32_le(mut l.buf, 0) // reserved2
620 write_u32_le(mut l.buf, 0) // reserved3
621
622 // __stubs section - using regular code section flags since we use immediate binding
623 write_string_fixed(mut l.buf, '__stubs', 16)
624 write_string_fixed(mut l.buf, '__TEXT', 16)
625 write_u64_le(mut l.buf, l.text_vmaddr + u64(l.stubs_offset)) // addr
626 write_u64_le(mut l.buf, u64(l.stubs_size)) // size
627 write_u32_le(mut l.buf, u32(l.stubs_offset)) // offset
628 write_u32_le(mut l.buf, 2) // align
629 write_u32_le(mut l.buf, 0) // reloff
630 write_u32_le(mut l.buf, 0) // nreloc
631 write_u32_le(mut l.buf, 0x80000400) // S_ATTR_PURE_INSTRUCTIONS | S_ATTR_SOME_INSTRUCTIONS (no S_SYMBOL_STUBS)
632 write_u32_le(mut l.buf, 0) // reserved1
633 write_u32_le(mut l.buf, 0) // reserved2
634 write_u32_le(mut l.buf, 0) // reserved3
635}
636
637// write_data_segment writes data segment output for arm64.
638fn (mut l Linker) write_data_segment() {
639 write_u32_le(mut l.buf, u32(lc_segment_64))
640 write_u32_le(mut l.buf, 72 + 80 * 2) // cmd size with 2 sections
641 write_string_fixed(mut l.buf, '__DATA', 16)
642 write_u64_le(mut l.buf, l.data_vmaddr) // vmaddr
643 write_u64_le(mut l.buf, u64(l.data_size)) // vmsize
644 write_u64_le(mut l.buf, u64(l.data_fileoff)) // fileoff
645 write_u64_le(mut l.buf, u64(l.data_size)) // filesize
646 write_u32_le(mut l.buf, 3) // maxprot (rw-)
647 write_u32_le(mut l.buf, 3) // initprot (rw-)
648 write_u32_le(mut l.buf, 2) // nsects
649 write_u32_le(mut l.buf, 0) // flags
650
651 // __data section
652 write_string_fixed(mut l.buf, '__data', 16)
653 write_string_fixed(mut l.buf, '__DATA', 16)
654 write_u64_le(mut l.buf, l.data_vmaddr) // addr
655 write_u64_le(mut l.buf, u64(l.macho.data_data.len)) // size
656 write_u32_le(mut l.buf, u32(l.data_fileoff)) // offset
657 write_u32_le(mut l.buf, 3) // align (8 bytes = 2^3)
658 write_u32_le(mut l.buf, 0) // reloff
659 write_u32_le(mut l.buf, 0) // nreloc
660 write_u32_le(mut l.buf, 0) // flags
661 write_u32_le(mut l.buf, 0) // reserved1
662 write_u32_le(mut l.buf, 0) // reserved2
663 write_u32_le(mut l.buf, 0) // reserved3
664
665 // __got section - using regular data section since we use immediate binding via bind info
666 write_string_fixed(mut l.buf, '__got', 16)
667 write_string_fixed(mut l.buf, '__DATA', 16)
668 write_u64_le(mut l.buf, l.data_vmaddr + u64(l.got_offset)) // addr
669 write_u64_le(mut l.buf, u64(l.got_size)) // size
670 write_u32_le(mut l.buf, u32(l.data_fileoff + l.got_offset)) // offset
671 write_u32_le(mut l.buf, 3) // align
672 write_u32_le(mut l.buf, 0) // reloff
673 write_u32_le(mut l.buf, 0) // nreloc
674 write_u32_le(mut l.buf, 0x00) // S_REGULAR (no special flags - dyld will fill via bind info)
675 write_u32_le(mut l.buf, 0) // reserved1
676 write_u32_le(mut l.buf, 0) // reserved2
677 write_u32_le(mut l.buf, 0) // reserved3
678}
679
680// write_linkedit_segment writes linkedit segment output for arm64.
681fn (mut l Linker) write_linkedit_segment() {
682 write_u32_le(mut l.buf, u32(lc_segment_64))
683 write_u32_le(mut l.buf, 72)
684 write_string_fixed(mut l.buf, '__LINKEDIT', 16)
685 write_u64_le(mut l.buf, 0) // vmaddr - patched later
686 write_u64_le(mut l.buf, 0) // vmsize - patched later
687 write_u64_le(mut l.buf, 0) // fileoff - patched later
688 write_u64_le(mut l.buf, 0) // filesize - patched later
689 write_u32_le(mut l.buf, 1) // maxprot (r--)
690 write_u32_le(mut l.buf, 1) // initprot (r--)
691 write_u32_le(mut l.buf, 0) // nsects
692 write_u32_le(mut l.buf, 0) // flags
693}
694
695// patch_linkedit supports patch linkedit handling for Linker.
696fn (mut l Linker) patch_linkedit(cmd_start int, fileoff int, filesize int) {
697 linkedit_vmaddr := l.data_vmaddr + u64(l.data_size)
698 mut linkedit_vmsize := u64((filesize + page_size - 1) & ~(page_size - 1))
699 if linkedit_vmsize == 0 {
700 linkedit_vmsize = u64(page_size)
701 }
702
703 // Patch vmaddr, vmsize, fileoff, filesize at known offsets within LINKEDIT cmd
704 off := cmd_start + 8 + 16 // after cmd, cmdsize, segname
705 write_u64_le_at(mut l.buf, off, linkedit_vmaddr)
706 write_u64_le_at(mut l.buf, off + 8, linkedit_vmsize)
707 write_u64_le_at(mut l.buf, off + 16, u64(fileoff))
708 write_u64_le_at(mut l.buf, off + 24, u64(filesize))
709}
710
711// write_dyld_info writes dyld info output for arm64.
712fn (mut l Linker) write_dyld_info(bind_off int, bind_size int) {
713 write_u32_le(mut l.buf, u32(lc_dyld_info_only))
714 write_u32_le(mut l.buf, 48)
715 write_u32_le(mut l.buf, 0) // rebase_off
716 write_u32_le(mut l.buf, 0) // rebase_size
717 write_u32_le(mut l.buf, u32(bind_off)) // bind_off
718 write_u32_le(mut l.buf, u32(bind_size)) // bind_size
719 write_u32_le(mut l.buf, 0) // weak_bind_off
720 write_u32_le(mut l.buf, 0) // weak_bind_size
721 write_u32_le(mut l.buf, 0) // lazy_bind_off
722 write_u32_le(mut l.buf, 0) // lazy_bind_size
723 write_u32_le(mut l.buf, 0) // export_off
724 write_u32_le(mut l.buf, 0) // export_size
725}
726
727// write_symtab writes symtab output for arm64.
728fn (mut l Linker) write_symtab(symoff int, nsyms int, stroff int, strsize int) {
729 write_u32_le(mut l.buf, u32(lc_symtab))
730 write_u32_le(mut l.buf, 24)
731 write_u32_le(mut l.buf, u32(symoff))
732 write_u32_le(mut l.buf, u32(nsyms))
733 write_u32_le(mut l.buf, u32(stroff))
734 write_u32_le(mut l.buf, u32(strsize))
735}
736
737// write_dysymtab writes dysymtab output for arm64.
738fn (mut l Linker) write_dysymtab(_nsyms int) {
739 write_u32_le(mut l.buf, u32(lc_dysymtab))
740 write_u32_le(mut l.buf, 80)
741 write_u32_le(mut l.buf, 0) // ilocalsym
742 write_u32_le(mut l.buf, 0) // nlocalsym
743 write_u32_le(mut l.buf, 0) // iextdefsym
744 write_u32_le(mut l.buf, 0) // nextdefsym
745 write_u32_le(mut l.buf, 0) // iundefsym
746 write_u32_le(mut l.buf, 0) // nundefsym
747 write_u32_le(mut l.buf, 0) // tocoff
748 write_u32_le(mut l.buf, 0) // ntoc
749 write_u32_le(mut l.buf, 0) // modtaboff
750 write_u32_le(mut l.buf, 0) // nmodtab
751 write_u32_le(mut l.buf, 0) // extrefsymoff
752 write_u32_le(mut l.buf, 0) // nextrefsyms
753 write_u32_le(mut l.buf, 0) // indirectsymoff
754 write_u32_le(mut l.buf, 0) // nindirectsyms
755 write_u32_le(mut l.buf, 0) // extreloff
756 write_u32_le(mut l.buf, 0) // nextrel
757 write_u32_le(mut l.buf, 0) // locreloff
758 write_u32_le(mut l.buf, 0) // nlocrel
759}
760
761// write_load_dylinker writes load dylinker output for arm64.
762fn (mut l Linker) write_load_dylinker() {
763 write_u32_le(mut l.buf, u32(lc_load_dylinker))
764 write_u32_le(mut l.buf, 32)
765 write_u32_le(mut l.buf, 12) // offset to string
766 write_string_fixed(mut l.buf, '/usr/lib/dyld', 20)
767}
768
769// write_load_dylibs writes load dylibs output for arm64.
770fn (mut l Linker) write_load_dylibs() {
771 for dylib_path in l.dylibs {
772 path_len := dylib_path.len + 1 // +1 for null terminator
773 padded_path := (path_len + 7) & ~7
774 cmd_size := 24 + padded_path // header (24) + padded path
775 write_u32_le(mut l.buf, u32(lc_load_dylib))
776 write_u32_le(mut l.buf, u32(cmd_size))
777 write_u32_le(mut l.buf, 24) // offset to string (always 24 in header)
778 write_u32_le(mut l.buf, 0) // timestamp
779 write_u32_le(mut l.buf, 0x10000) // current version
780 write_u32_le(mut l.buf, 0x10000) // compatibility version
781 write_string_fixed(mut l.buf, dylib_path, padded_path)
782 }
783}
784
785// write_main_cmd writes main cmd output for arm64.
786fn (mut l Linker) write_main_cmd(entry_off int) {
787 write_u32_le(mut l.buf, u32(lc_main))
788 write_u32_le(mut l.buf, 24)
789 write_u64_le(mut l.buf, u64(entry_off)) // entryoff (offset from __TEXT start)
790 write_u64_le(mut l.buf, 0) // stacksize
791}
792
793// write_uuid writes uuid output for arm64.
794fn (mut l Linker) write_uuid() {
795 write_u32_le(mut l.buf, u32(lc_uuid))
796 write_u32_le(mut l.buf, 24)
797 // Random UUID
798 for _ in 0 .. 16 {
799 l.buf << 0
800 }
801}
802
803// write_build_version writes build version output for arm64.
804fn (mut l Linker) write_build_version() {
805 write_u32_le(mut l.buf, u32(lc_build_version))
806 write_u32_le(mut l.buf, 24)
807 write_u32_le(mut l.buf, 1) // platform: MACOS
808 write_u32_le(mut l.buf, 0x000b0000) // minos: 11.0.0
809 write_u32_le(mut l.buf, 0x000b0000) // sdk: 11.0.0
810 write_u32_le(mut l.buf, 0) // ntools
811}
812
813// write_source_version writes source version output for arm64.
814fn (mut l Linker) write_source_version() {
815 write_u32_le(mut l.buf, u32(lc_source_version))
816 write_u32_le(mut l.buf, 16)
817 write_u64_le(mut l.buf, 0) // version
818}
819
820// write_code_signature_cmd writes code signature cmd output for arm64.
821fn (mut l Linker) write_code_signature_cmd(dataoff int, datasize int) {
822 write_u32_le(mut l.buf, u32(lc_code_signature))
823 write_u32_le(mut l.buf, 16) // cmdsize
824 write_u32_le(mut l.buf, u32(dataoff))
825 write_u32_le(mut l.buf, u32(datasize))
826}
827
828// estimate_signature_size supports estimate signature size handling for Linker.
829fn (l &Linker) estimate_signature_size(code_limit int, ident string) int {
830 // Calculate pages using ARM64 16KB page size
831 n_pages := (code_limit + cs_page_size_arm64 - 1) / cs_page_size_arm64
832
833 // SuperBlob header (12) + 3 BlobIndex entries (24)
834 // + CodeDirectory + Requirements blob + CMS blob
835 ident_len := ident.len + 1 // null terminated
836 n_special_slots := 2
837 special_hashes_size := n_special_slots * cs_hash_size
838 // CodeDirectory: header (88 for version 0x20400) + ident + special hashes + code hashes
839 cd_size := 88 + ident_len + special_hashes_size + (n_pages * cs_hash_size)
840 // Round up to 4-byte alignment
841 cd_size_aligned := (cd_size + 3) & ~3
842 // Requirements blob: minimal empty requirements (12 bytes)
843 req_size := 12
844 // CMS blob: empty wrapper (8 bytes)
845 cms_size := 8
846 // Total: SuperBlob(12) + 3*BlobIndex(8) + CodeDirectory + Requirements + CMS
847 return 12 + 24 + cd_size_aligned + req_size + cms_size
848}
849
850// generate_code_signature supports generate code signature handling for Linker.
851fn (l &Linker) generate_code_signature(ident string) []u8 {
852 mut sig := []u8{}
853
854 // Calculate sizes using ARM64 16KB pages
855 code_limit := l.buf.len // Current buffer is the code to hash
856 n_pages := (code_limit + cs_page_size_arm64 - 1) / cs_page_size_arm64
857 ident_bytes := ident.bytes()
858 ident_len := ident_bytes.len + 1 // null terminated
859
860 // Special slots: we need at least slot for requirements (-2)
861 n_special_slots := 2 // Slots -1 (info.plist) and -2 (requirements)
862 special_hashes_size := n_special_slots * cs_hash_size
863
864 // CodeDirectory layout for version 0x20400:
865 // - Base header (44 bytes): magic, length, version, flags, hashOffset, identOffset,
866 // nSpecialSlots, nCodeSlots, codeLimit, hashSize, hashType, platform, pageSize, spare2
867 // - scatterOffset (4 bytes)
868 // - teamOffset (4 bytes)
869 // - spare3 (4 bytes)
870 // - codeLimit64 (8 bytes)
871 // - execSegBase (8 bytes)
872 // - execSegLimit (8 bytes)
873 // - execSegFlags (8 bytes)
874 // Total header: 88 bytes
875 cd_header_size := 88
876 ident_offset := cd_header_size
877 hash_offset := ident_offset + ident_len + special_hashes_size
878 cd_size := hash_offset + (n_pages * cs_hash_size)
879 cd_size_aligned := (cd_size + 3) & ~3
880
881 // Requirements blob (empty)
882 req_size := 12
883 // CMS signature blob (empty wrapper for ad-hoc)
884 cms_size := 8
885
886 // SuperBlob layout with 3 blobs
887 blob_count := 3 // CodeDirectory + Requirements + CMS
888 super_blob_header := 12
889 blob_index_size := blob_count * 8
890
891 cd_blob_offset := super_blob_header + blob_index_size
892 req_blob_offset := cd_blob_offset + cd_size_aligned
893 cms_blob_offset := req_blob_offset + req_size
894 total_size := cms_blob_offset + cms_size
895
896 // Write SuperBlob header (big-endian)
897 write_u32_be(mut sig, csmagic_embedded_signature)
898 write_u32_be(mut sig, u32(total_size))
899 write_u32_be(mut sig, u32(blob_count))
900
901 // BlobIndex for CodeDirectory (type = 0 = CSSLOT_CODEDIRECTORY)
902 write_u32_be(mut sig, csslot_codedirectory)
903 write_u32_be(mut sig, u32(cd_blob_offset))
904
905 // BlobIndex for Requirements (type = 2 = CSSLOT_REQUIREMENTS)
906 write_u32_be(mut sig, csslot_requirements)
907 write_u32_be(mut sig, u32(req_blob_offset))
908
909 // BlobIndex for CMS signature (type = 0x10000)
910 write_u32_be(mut sig, csslot_cms_signature)
911 write_u32_be(mut sig, u32(cms_blob_offset))
912
913 // Write CodeDirectory (big-endian) - version 0x20400
914 write_u32_be(mut sig, csmagic_codedirectory)
915 write_u32_be(mut sig, u32(cd_size))
916 write_u32_be(mut sig, 0x20400) // version
917 write_u32_be(mut sig, cs_adhoc) // flags (ad-hoc)
918 write_u32_be(mut sig, u32(hash_offset)) // hashOffset
919 write_u32_be(mut sig, u32(ident_offset)) // identOffset
920 write_u32_be(mut sig, u32(n_special_slots)) // nSpecialSlots
921 write_u32_be(mut sig, u32(n_pages)) // nCodeSlots
922 write_u32_be(mut sig, u32(code_limit)) // codeLimit
923 sig << cs_hash_size // hashSize
924 sig << cs_hashtype_sha256 // hashType
925 sig << 0 // platform
926 sig << cs_page_shift_arm64 // pageSize (log2 of 16384 = 14)
927 write_u32_be(mut sig, 0) // spare2
928 // Version 0x20400 additional fields:
929 write_u32_be(mut sig, 0) // scatterOffset (0 = none)
930 write_u32_be(mut sig, 0) // teamOffset (0 = none)
931 write_u32_be(mut sig, 0) // spare3
932 write_u64_be(mut sig, 0) // codeLimit64 (0 = use codeLimit)
933 write_u64_be(mut sig, 0) // execSegBase (0 = __TEXT starts at 0)
934 write_u64_be(mut sig, u64(l.text_size)) // execSegLimit (size of __TEXT segment)
935 write_u64_be(mut sig, 1) // execSegFlags (CS_EXECSEG_MAIN_BINARY = 1)
936
937 // Write identifier (null-terminated)
938 for i in 0 .. ident_bytes.len {
939 sig << ident_bytes[i]
940 }
941 sig << 0
942
943 // Write special slot hashes (slots -2, -1 in that order)
944
945 // Build the requirements blob first so we can hash it
946 mut req_blob := []u8{}
947 write_u32_be(mut req_blob, csmagic_requirements)
948 write_u32_be(mut req_blob, u32(req_size))
949 write_u32_be(mut req_blob, 0) // count = 0
950
951 // Slot -2: Hash of requirements blob
952 mut hash_buf := [32]u8{}
953 sha256_hash(req_blob.data, req_blob.len, &hash_buf[0])
954 for i in 0 .. cs_hash_size {
955 sig << hash_buf[i]
956 }
957
958 // Slot -1: Info.plist (zeros = no Info.plist)
959 for _ in 0 .. cs_hash_size {
960 sig << 0
961 }
962
963 // Compute page hashes in parallel (16KB pages)
964 mut all_hashes := []u8{len: n_pages * 32}
965 data_ptr := unsafe { &u8(l.buf.data) }
966 // Hash all pages sequentially. V's `spawn` is not supported on the native
967 // ARM64 backend, so we avoid threads here for self-hosting compatibility.
968 sha256_hash_pages(data_ptr, mut all_hashes, 0, n_pages, code_limit)
969 for i in 0 .. all_hashes.len {
970 sig << all_hashes[i]
971 }
972
973 // Pad CodeDirectory to alignment
974 for sig.len < cd_blob_offset + cd_size_aligned {
975 sig << 0
976 }
977
978 // Write Requirements blob
979 for i in 0 .. req_blob.len {
980 sig << req_blob[i]
981 }
982
983 // Write empty CMS signature blob (for ad-hoc signing)
984 write_u32_be(mut sig, csmagic_blobwrapper)
985 write_u32_be(mut sig, u32(cms_size))
986
987 return sig
988}
989
990// find_entry_offset resolves find entry offset information for arm64.
991fn (mut l Linker) find_entry_offset(entry_name string) int {
992 // Find the _main symbol
993 // LC_MAIN entryoff is relative to __TEXT segment vmaddr
994 // Code section starts at code_start within __TEXT
995 for sym in l.macho.symbols {
996 if sym.name == entry_name && sym.sect == 1 {
997 return l.code_start + int(sym.value)
998 }
999 }
1000 return l.code_start // Default to start of code section
1001}
1002
1003// generate_bind_info supports generate bind info handling for Linker.
1004fn (mut l Linker) generate_bind_info(got_offset int) []u8 {
1005 mut info := []u8{}
1006
1007 // Data segment index (segment 2: __PAGEZERO=0, __TEXT=1, __DATA=2)
1008 data_seg_idx := u8(2)
1009
1010 mut got_entry_offset := got_offset
1011 for sym_name in l.extern_syms {
1012 // Internal runtime callback names can appear as unresolved function refs in
1013 // bootstrap builds. Bind them as weak imports so dyld does not abort load
1014 // when they are absent from libSystem; unresolved weak symbols become NULL.
1015 mut bind_flags := u8(0)
1016 if sym_name.contains('__') && sym_name !in force_external_syms {
1017 bind_flags = bind_symbol_flags_weak_import
1018 }
1019
1020 // Set dylib ordinal (1-based: 1 = first dylib)
1021 mut ordinal_idx := 0
1022 if idx := l.sym_to_dylib[sym_name] {
1023 ordinal_idx = idx
1024 }
1025 ordinal := u8(ordinal_idx + 1)
1026 info << (bind_opcode_set_dylib_ordinal_imm | ordinal)
1027
1028 // Set symbol name
1029 info << (bind_opcode_set_symbol_flags_imm | bind_flags)
1030 info << sym_name.bytes()
1031 info << 0 // null terminator
1032
1033 // Set type (pointer)
1034 info << (bind_opcode_set_type_imm | bind_type_pointer)
1035
1036 // Set segment and offset
1037 info << (bind_opcode_set_segment_and_offset_uleb | data_seg_idx)
1038 info << l.encode_uleb128_int(got_entry_offset)
1039
1040 // Do bind
1041 info << bind_opcode_do_bind
1042 got_entry_offset += 8
1043 }
1044
1045 // Done
1046 info << bind_opcode_done
1047
1048 return info
1049}
1050
1051// encode_uleb128_int converts encode uleb128 int data for arm64.
1052fn (l Linker) encode_uleb128_int(val int) []u8 {
1053 mut result := []u8{}
1054 if val < 0x80 {
1055 result << u8(val)
1056 return result
1057 }
1058 if val < 0x4000 {
1059 result << (u8(val & 0x7f) | 0x80)
1060 result << u8((val >> 7) & 0x7f)
1061 return result
1062 }
1063 if val < 0x200000 {
1064 result << (u8(val & 0x7f) | 0x80)
1065 result << (u8((val >> 7) & 0x7f) | 0x80)
1066 result << u8((val >> 14) & 0x7f)
1067 return result
1068 }
1069 return l.encode_uleb128(u64(u32(val)))
1070}
1071
1072// encode_uleb128 converts encode uleb128 data for arm64.
1073fn (l Linker) encode_uleb128(val u64) []u8 {
1074 mut result := []u8{}
1075 mut v := val
1076 for {
1077 mut b := u8(v & 0x7f)
1078 v >>= 7
1079 if v != 0 {
1080 b |= 0x80
1081 }
1082 result << b
1083 if v == 0 {
1084 break
1085 }
1086 }
1087 return result
1088}
1089
1090// write_text_with_relocations writes text with relocations output for arm64.
1091fn (mut l Linker) write_text_with_relocations() {
1092 // Copy text data
1093 mut text := l.macho.text_data.clone()
1094
1095 // Build symbol address map
1096 // Note: code section vmaddr = text_vmaddr + code_start
1097 // Symbol values are offsets from segment start, so we use code_vmaddr for all __TEXT symbols
1098 code_vmaddr := l.text_vmaddr + u64(l.code_start)
1099 stubs_vmaddr := l.text_vmaddr + u64(l.stubs_offset)
1100
1101 // In the object file, __data section starts at text_len + cstring_len + alignment_padding
1102 // We need to find the actual base address of data symbols (minimum symbol value in sect 3)
1103 // to correctly compute offsets within the data_data array
1104 mut data_base_addr := u64(0xFFFFFFFFFFFFFFFF) // Start with max, find minimum
1105 for sym in l.macho.symbols {
1106 if (sym.type_ & 0x0E) == 0x0E && sym.sect == 3 {
1107 if sym.value < data_base_addr {
1108 data_base_addr = sym.value
1109 }
1110 }
1111 }
1112 // If no data symbols, use section start
1113 if data_base_addr == 0xFFFFFFFFFFFFFFFF {
1114 data_base_addr = u64(l.macho.text_data.len + l.macho.str_data.len)
1115 }
1116
1117 mut sym_addrs := map[int]u64{}
1118 // Map symbol names to their defined addresses (for resolving undefined references)
1119 mut sym_name_to_addr := map[string]u64{}
1120
1121 // First pass: collect all defined symbol addresses (except external syms)
1122 for i, sym in l.macho.symbols {
1123 // N_SECT (0x0E) means symbol is defined in a section
1124 if (sym.type_ & 0x0E) == 0x0E {
1125 // Skip external symbols - they should always resolve to libc
1126 is_external := sym.name in force_external_syms
1127 if sym.sect == 1 {
1128 // Text section symbol (code)
1129 addr := code_vmaddr + sym.value
1130 sym_addrs[i] = addr
1131 if !is_external {
1132 sym_name_to_addr[sym.name] = addr
1133 }
1134 } else if sym.sect == 2 {
1135 // Cstring section symbol
1136 addr := code_vmaddr + sym.value
1137 sym_addrs[i] = addr
1138 if !is_external {
1139 sym_name_to_addr[sym.name] = addr
1140 }
1141 } else if sym.sect == 3 {
1142 // Data section symbol
1143 // Subtract data base address to get offset within data_data array
1144 addr := l.data_vmaddr + (sym.value - data_base_addr)
1145 sym_addrs[i] = addr
1146 if !is_external {
1147 sym_name_to_addr[sym.name] = addr
1148 }
1149 }
1150 }
1151 }
1152
1153 // Second pass: handle external symbols and resolve undefined references to local symbols
1154 for i, sym in l.macho.symbols {
1155 if sym.type_ == 0x01 { // N_UNDF | N_EXT
1156 // Check if this symbol is defined locally
1157 if addr := sym_name_to_addr[sym.name] {
1158 // Resolve to local definition
1159 sym_addrs[i] = addr
1160 } else if sym.name in l.sym_to_got {
1161 // External symbol - address is in stub
1162 got_idx := l.sym_to_got[sym.name]
1163 sym_addrs[i] = stubs_vmaddr + u64(got_idx * 12)
1164 }
1165 }
1166 }
1167
1168 // Apply relocations
1169 for r in l.macho.relocs {
1170 // Check if this relocation references an external symbol
1171 // If so, redirect it to use the stub instead of the local definition
1172 sym_name := l.macho.symbols[r.sym_idx].name
1173 mut sym_addr := sym_addrs[r.sym_idx]
1174 if sym_addr == 0 && r.sym_idx !in sym_addrs {
1175 eprintln('LINKER: unresolved symbol "${sym_name}" (idx=${r.sym_idx}) at text offset ${r.addr}')
1176 exit(1)
1177 }
1178 if sym_name in force_external_syms {
1179 // Use stub address for external symbols
1180 if sym_name in l.sym_to_got {
1181 got_idx := l.sym_to_got[sym_name]
1182 sym_addr = stubs_vmaddr + u64(got_idx * 12)
1183 }
1184 }
1185 pc := code_vmaddr + u64(r.addr)
1186
1187 match r.type_ {
1188 arm64_reloc_branch26 {
1189 // BL instruction: PC-relative branch
1190 if sym_addr == 0 {
1191 eprintln('LINKER: unresolved BL to "${sym_name}" (idx=${r.sym_idx}) at text offset ${r.addr}')
1192 exit(1)
1193 }
1194 rel := i64(sym_addr) - i64(pc)
1195 imm26 := (rel >> 2) & 0x3FFFFFF
1196 instr := read_u32_le(text, r.addr)
1197 new_instr := (instr & 0xFC000000) | u32(imm26)
1198 write_u32_le_at_arr(mut text, r.addr, new_instr)
1199 }
1200 arm64_reloc_page21 {
1201 // ADRP instruction: PC-relative page address
1202 sym_page := i64(sym_addr) & ~0xFFF
1203 pc_page := i64(pc) & ~0xFFF
1204 page_off := (sym_page - pc_page) >> 12
1205
1206 immlo := u32(page_off & 0x3) << 29
1207 immhi := u32((page_off >> 2) & 0x7FFFF) << 5
1208 instr := read_u32_le(text, r.addr)
1209 new_instr := (instr & 0x9F00001F) | immlo | immhi
1210 write_u32_le_at_arr(mut text, r.addr, new_instr)
1211 }
1212 arm64_reloc_pageoff12 {
1213 // ADD/LDR instruction: page offset
1214 page_off := sym_addr & 0xFFF
1215 instr := read_u32_le(text, r.addr)
1216
1217 // Check if this is ADD or LDR
1218 if (instr & 0xFF800000) == 0x91000000 {
1219 // ADD immediate
1220 new_instr := (instr & 0xFFC003FF) | (u32(page_off) << 10)
1221 write_u32_le_at_arr(mut text, r.addr, new_instr)
1222 } else {
1223 // LDR with scaled offset
1224 // Determine scale from instruction encoding
1225 scale := (instr >> 30) & 0x3
1226 scaled_off := page_off >> scale
1227 new_instr := (instr & 0xFFC003FF) | (u32(scaled_off) << 10)
1228 write_u32_le_at_arr(mut text, r.addr, new_instr)
1229 }
1230 }
1231 arm64_reloc_got_load_page21 {
1232 // ADRP instruction: PC-relative page address to GOT entry
1233 mut got_idx1 := 0
1234 if idx := l.sym_to_got[sym_name] {
1235 got_idx1 = idx
1236 }
1237 got_entry_addr1 := l.data_vmaddr + u64(l.got_offset) + u64(got_idx1 * 8)
1238
1239 got_page := i64(got_entry_addr1) & ~0xFFF
1240 pc_page := i64(pc) & ~0xFFF
1241 page_off := (got_page - pc_page) >> 12
1242
1243 immlo := u32(page_off & 0x3) << 29
1244 immhi := u32((page_off >> 2) & 0x7FFFF) << 5
1245 instr := read_u32_le(text, r.addr)
1246 new_instr := (instr & 0x9F00001F) | immlo | immhi
1247 write_u32_le_at_arr(mut text, r.addr, new_instr)
1248 }
1249 arm64_reloc_got_load_pageoff12 {
1250 // LDR instruction: page offset to GOT entry
1251 mut got_idx2 := 0
1252 if idx := l.sym_to_got[sym_name] {
1253 got_idx2 = idx
1254 }
1255 got_entry_addr2 := l.data_vmaddr + u64(l.got_offset) + u64(got_idx2 * 8)
1256
1257 page_off := got_entry_addr2 & 0xFFF
1258 instr := read_u32_le(text, r.addr)
1259 // LDR with scaled offset (8-byte scale for 64-bit load)
1260 scaled_off := page_off >> 3
1261 new_instr := (instr & 0xFFC003FF) | (u32(scaled_off) << 10)
1262 write_u32_le_at_arr(mut text, r.addr, new_instr)
1263 }
1264 else {}
1265 }
1266 }
1267
1268 l.buf << text
1269}
1270
1271// write_stubs writes stubs output for arm64.
1272fn (mut l Linker) write_stubs() {
1273 // Generate stub for each external symbol
1274 // Each stub: ADRP x16, GOT@PAGE; LDR x16, [x16, GOT@PAGEOFF]; BR x16
1275 mut got_entry_offset := l.macho.data_data.len
1276 for got_entry_offset % 8 != 0 {
1277 got_entry_offset++
1278 }
1279 mut stub_offset := l.stubs_offset
1280 for _ in l.extern_syms {
1281 got_entry_addr := l.data_vmaddr + u64(u32(got_entry_offset))
1282 stub_addr := l.text_vmaddr + u64(stub_offset)
1283
1284 // ADRP x16, got_entry@PAGE
1285 got_page := i64(got_entry_addr) & ~0xFFF
1286 stub_page := i64(stub_addr) & ~0xFFF
1287 page_off := (got_page - stub_page) >> 12
1288 immlo := u32(page_off & 0x3) << 29
1289 immhi := u32((page_off >> 2) & 0x7FFFF) << 5
1290 adrp := u32(0x90000010) | immlo | immhi
1291 write_u32_le(mut l.buf, adrp)
1292
1293 // LDR x16, [x16, got_entry@PAGEOFF]
1294 pageoff := (got_entry_addr & 0xFFF) >> 3 // Scale by 8 for 64-bit load
1295 ldr := u32(0xF9400210) | (u32(pageoff) << 10)
1296 write_u32_le(mut l.buf, ldr)
1297
1298 // BR x16
1299 write_u32_le(mut l.buf, 0xD61F0200)
1300 got_entry_offset += 8
1301 stub_offset += 12
1302 }
1303}
1304
1305// read_u32_le reads read u32 le input for arm64.
1306fn read_u32_le(data []u8, off int) u32 {
1307 b0 := u32(data[off]) & u32(0xff)
1308 b1 := (u32(data[off + 1]) & u32(0xff)) << 8
1309 b2 := (u32(data[off + 2]) & u32(0xff)) << 16
1310 b3 := (u32(data[off + 3]) & u32(0xff)) << 24
1311 return b0 | b1 | b2 | b3
1312}
1313
1314// write_u32_le_at_arr writes u32 le at arr output for arm64.
1315fn write_u32_le_at_arr(mut data []u8, off int, v u32) {
1316 data[off] = u8(v)
1317 data[off + 1] = u8(v >> 8)
1318 data[off + 2] = u8(v >> 16)
1319 data[off + 3] = u8(v >> 24)
1320}
1321
1322// write_u32_le_at writes u32 le at output for arm64.
1323fn write_u32_le_at(mut data []u8, off int, v u32) {
1324 data[off] = u8(v)
1325 data[off + 1] = u8(v >> 8)
1326 data[off + 2] = u8(v >> 16)
1327 data[off + 3] = u8(v >> 24)
1328}
1329
1330// Big-endian write for code signature (Mach-O signatures use big-endian)
1331fn write_u32_be(mut b []u8, v u32) {
1332 b << u8(v >> 24)
1333 b << u8(v >> 16)
1334 b << u8(v >> 8)
1335 b << u8(v)
1336}
1337
1338// write_u64_be writes u64 be output for arm64.
1339fn write_u64_be(mut b []u8, v u64) {
1340 b << u8(v >> 56)
1341 b << u8(v >> 48)
1342 b << u8(v >> 40)
1343 b << u8(v >> 32)
1344 b << u8(v >> 24)
1345 b << u8(v >> 16)
1346 b << u8(v >> 8)
1347 b << u8(v)
1348}
1349
1350// write_u64_le_at writes u64 le at output for arm64.
1351fn write_u64_le_at(mut b []u8, off int, v u64) {
1352 b[off] = u8(v)
1353 b[off + 1] = u8(v >> 8)
1354 b[off + 2] = u8(v >> 16)
1355 b[off + 3] = u8(v >> 24)
1356 b[off + 4] = u8(v >> 32)
1357 b[off + 5] = u8(v >> 40)
1358 b[off + 6] = u8(v >> 48)
1359 b[off + 7] = u8(v >> 56)
1360}
1361
1362// Pad buffer to target size with zeros (efficient bulk write)
1363fn (mut l Linker) pad_to(target int) {
1364 if l.buf.len >= target {
1365 return
1366 }
1367 count := target - l.buf.len
1368 for _ in 0 .. count {
1369 l.buf << u8(0)
1370 }
1371}
1372
1373// Write n zero bytes (efficient)
1374fn (mut l Linker) write_zeros(n int) {
1375 if n <= 0 {
1376 return
1377 }
1378 for _ in 0 .. n {
1379 l.buf << u8(0)
1380 }
1381}
1382
1383// Self-contained SHA-256 implementation. Zero heap allocations —
1384// uses fixed-size arrays and operates on raw pointers.
1385
1386const sha256_k = [
1387 u32(0x428a2f98),
1388 0x71374491,
1389 0xb5c0fbcf,
1390 0xe9b5dba5,
1391 0x3956c25b,
1392 0x59f111f1,
1393 0x923f82a4,
1394 0xab1c5ed5,
1395 0xd807aa98,
1396 0x12835b01,
1397 0x243185be,
1398 0x550c7dc3,
1399 0x72be5d74,
1400 0x80deb1fe,
1401 0x9bdc06a7,
1402 0xc19bf174,
1403 0xe49b69c1,
1404 0xefbe4786,
1405 0x0fc19dc6,
1406 0x240ca1cc,
1407 0x2de92c6f,
1408 0x4a7484aa,
1409 0x5cb0a9dc,
1410 0x76f988da,
1411 0x983e5152,
1412 0xa831c66d,
1413 0xb00327c8,
1414 0xbf597fc7,
1415 0xc6e00bf3,
1416 0xd5a79147,
1417 0x06ca6351,
1418 0x14292967,
1419 0x27b70a85,
1420 0x2e1b2138,
1421 0x4d2c6dfc,
1422 0x53380d13,
1423 0x650a7354,
1424 0x766a0abb,
1425 0x81c2c92e,
1426 0x92722c85,
1427 0xa2bfe8a1,
1428 0xa81a664b,
1429 0xc24b8b70,
1430 0xc76c51a3,
1431 0xd192e819,
1432 0xd6990624,
1433 0xf40e3585,
1434 0x106aa070,
1435 0x19a4c116,
1436 0x1e376c08,
1437 0x2748774c,
1438 0x34b0bcb5,
1439 0x391c0cb3,
1440 0x4ed8aa4a,
1441 0x5b9cca4f,
1442 0x682e6ff3,
1443 0x748f82ee,
1444 0x78a5636f,
1445 0x84c87814,
1446 0x8cc70208,
1447 0x90befffa,
1448 0xa4506ceb,
1449 0xbef9a3f7,
1450 0xc67178f2,
1451]!
1452
1453// rotr32 supports rotr32 handling for arm64.
1454@[inline]
1455fn rotr32(x u32, n u32) u32 {
1456 return (x >> n) | (x << (32 - n))
1457}
1458
1459// sha256_hash_pages hashes a range of 16KB pages in a worker thread.
1460// Each thread writes 32-byte hashes into disjoint slots of the pre-allocated output buffer.
1461fn sha256_hash_pages(data &u8, mut hashes []u8, page_start int, page_end int, code_limit int) {
1462 mut hash_buf := [32]u8{}
1463 for page := page_start; page < page_end; page++ {
1464 start := page * cs_page_size_arm64
1465 mut end := start + cs_page_size_arm64
1466 if end > code_limit {
1467 end = code_limit
1468 }
1469 unsafe {
1470 sha256_hash(data + start, end - start, &hash_buf[0])
1471 }
1472 hash_offset := page * 32
1473 for i in 0 .. 32 {
1474 hashes[hash_offset + i] = hash_buf[i]
1475 }
1476 }
1477}
1478
1479// sha256_hash computes SHA-256 of data[0..data_len] into out[0..32].
1480// No heap allocations — uses fixed-size arrays on the stack.
1481@[direct_array_access]
1482fn sha256_hash(data &u8, data_len int, out_ptr &u8) {
1483 mut state := [u32(0x6A09E667), 0xBB67AE85, 0x3C6EF372, 0xA54FF53A, 0x510E527F, 0x9B05688C,
1484 0x1F83D9AB, 0x5BE0CD19]!
1485 mut w := [64]u32{}
1486
1487 // Process complete 64-byte blocks directly from input
1488 n_full_blocks := data_len / 64
1489 for blk := 0; blk < n_full_blocks; blk++ {
1490 off := blk * 64
1491 for i in 0 .. 16 {
1492 j := off + i * 4
1493 unsafe {
1494 w[i] = (u32(data[j]) << 24) | (u32(data[j + 1]) << 16) | (u32(data[j + 2]) << 8) | u32(data[
1495 j + 3])
1496 }
1497 }
1498 sha256_compress(&state[0], &w[0])
1499 }
1500
1501 // Build final padded block(s): remaining data + 0x80 + zeros + 64-bit big-endian length
1502 remaining := data_len - n_full_blocks * 64
1503 mut pad := [128]u8{} // At most 2 final blocks
1504 for i in 0 .. remaining {
1505 unsafe {
1506 pad[i] = data[n_full_blocks * 64 + i]
1507 }
1508 }
1509 pad[remaining] = 0x80
1510
1511 // Need room for 8-byte length at end of last 64-byte block
1512 mut pad_blocks := 1
1513 if remaining >= 56 {
1514 pad_blocks = 2
1515 }
1516
1517 // Write bit length (big-endian u64) at end of last padding block
1518 bit_len := u64(data_len) * 8
1519 pad_end := pad_blocks * 64
1520 pad[pad_end - 8] = u8(bit_len >> 56)
1521 pad[pad_end - 7] = u8(bit_len >> 48)
1522 pad[pad_end - 6] = u8(bit_len >> 40)
1523 pad[pad_end - 5] = u8(bit_len >> 32)
1524 pad[pad_end - 4] = u8(bit_len >> 24)
1525 pad[pad_end - 3] = u8(bit_len >> 16)
1526 pad[pad_end - 2] = u8(bit_len >> 8)
1527 pad[pad_end - 1] = u8(bit_len)
1528
1529 for blk in 0 .. pad_blocks {
1530 off := blk * 64
1531 for i in 0 .. 16 {
1532 j := off + i * 4
1533 w[i] = (u32(pad[j]) << 24) | (u32(pad[j + 1]) << 16) | (u32(pad[j + 2]) << 8) | u32(pad[
1534 j + 3])
1535 }
1536 sha256_compress(&state[0], &w[0])
1537 }
1538
1539 // Write result big-endian
1540 mut out := unsafe { &u8(out_ptr) }
1541 for i in 0 .. 8 {
1542 unsafe {
1543 out[i * 4] = u8(state[i] >> 24)
1544 out[i * 4 + 1] = u8(state[i] >> 16)
1545 out[i * 4 + 2] = u8(state[i] >> 8)
1546 out[i * 4 + 3] = u8(state[i])
1547 }
1548 }
1549}
1550
1551// sha256_compress supports sha256 compress handling for arm64.
1552@[direct_array_access]
1553fn sha256_compress(state_ptr &u32, w_ptr &u32) {
1554 mut state := unsafe { &u32(state_ptr) }
1555 mut w := unsafe { &u32(w_ptr) }
1556 // Extend the first 16 words into the remaining 48
1557 for i := 16; i < 64; i++ {
1558 unsafe {
1559 s0 := rotr32(w[i - 15], 7) ^ rotr32(w[i - 15], 18) ^ (w[i - 15] >> 3)
1560 s1 := rotr32(w[i - 2], 17) ^ rotr32(w[i - 2], 19) ^ (w[i - 2] >> 10)
1561 w[i] = w[i - 16] + s0 + w[i - 7] + s1
1562 }
1563 }
1564
1565 mut a := unsafe { state[0] }
1566 mut b := unsafe { state[1] }
1567 mut c := unsafe { state[2] }
1568 mut d := unsafe { state[3] }
1569 mut e := unsafe { state[4] }
1570 mut f := unsafe { state[5] }
1571 mut g := unsafe { state[6] }
1572 mut h := unsafe { state[7] }
1573
1574 for i in 0 .. 64 {
1575 s1 := rotr32(e, 6) ^ rotr32(e, 11) ^ rotr32(e, 25)
1576 ch := (e & f) ^ (~e & g)
1577 t1 := h + s1 + ch + sha256_k[i] + unsafe { w[i] }
1578 s0 := rotr32(a, 2) ^ rotr32(a, 13) ^ rotr32(a, 22)
1579 maj := (a & b) ^ (a & c) ^ (b & c)
1580 t2 := s0 + maj
1581
1582 h = g
1583 g = f
1584 f = e
1585 e = d + t1
1586 d = c
1587 c = b
1588 b = a
1589 a = t1 + t2
1590 }
1591
1592 unsafe {
1593 state[0] += a
1594 state[1] += b
1595 state[2] += c
1596 state[3] += d
1597 state[4] += e
1598 state[5] += f
1599 state[6] += g
1600 state[7] += h
1601 }
1602}
1603