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