1558 lines
48 KiB
V
1558 lines
48 KiB
V
// Copyright (c) 2026 Alexander Medvednikov. All rights reserved.
|
|
// Use of this source code is governed by an MIT license
|
|
// that can be found in the LICENSE file.
|
|
|
|
module arm64
|
|
|
|
import os
|
|
import time
|
|
|
|
fn C.open(charptr, int, int) int
|
|
fn C.write(int, voidptr, int) int
|
|
fn C.close(int) int
|
|
fn C.chmod(charptr, int) int
|
|
fn C.rename(charptr, charptr) int
|
|
|
|
// Mach-O executable constants
|
|
const mh_execute = 2
|
|
const lc_load_dylinker = 0xe
|
|
const lc_load_dylib = 0xc
|
|
const lc_main = u32(0x80000028)
|
|
const lc_dyld_info_only = u32(0x80000022)
|
|
const lc_dysymtab = 0xb
|
|
const lc_uuid = 0x1b
|
|
const lc_build_version = 0x32
|
|
const lc_source_version = 0x2a
|
|
const lc_code_signature = 0x1d
|
|
|
|
// Code signing constants (big-endian magic numbers)
|
|
const csmagic_embedded_signature = u32(0xfade0cc0)
|
|
const csmagic_codedirectory = u32(0xfade0c02)
|
|
const csmagic_requirements = u32(0xfade0c01)
|
|
const csmagic_blobwrapper = u32(0xfade0b01)
|
|
const csslot_codedirectory = u32(0)
|
|
const csslot_requirements = u32(2)
|
|
const csslot_cms_signature = u32(0x10000)
|
|
const cs_adhoc = u32(0x2) // Ad-hoc signing flag
|
|
const cs_hashtype_sha256 = u8(2)
|
|
const cs_hash_size = 32 // SHA256 = 32 bytes
|
|
const cs_page_size_arm64 = 16384 // Code signing page size for ARM64 macOS
|
|
const cs_page_shift_arm64 = 14 // log2(16384)
|
|
const o_wronly_creat_trunc = 0x601 // O_WRONLY | O_CREAT | O_TRUNC on Darwin
|
|
|
|
// ARM64 page size on macOS
|
|
const page_size = 0x4000 // 16KB
|
|
|
|
// Base address for executables
|
|
const base_addr = u64(0x100000000)
|
|
|
|
// Bind opcodes for dyld
|
|
const bind_opcode_done = 0x00
|
|
const bind_opcode_set_dylib_ordinal_imm = 0x10
|
|
const bind_opcode_set_symbol_flags_imm = 0x40
|
|
const bind_opcode_set_type_imm = 0x50
|
|
const bind_opcode_set_segment_and_offset_uleb = 0x70
|
|
const bind_opcode_do_bind = 0x90
|
|
const bind_type_pointer = 1
|
|
const bind_symbol_flags_weak_import = 0x01
|
|
|
|
// vfmt off
|
|
|
|
// Libc symbols that should ALWAYS resolve to the external system library,
|
|
// never to local V wrappers. This prevents infinite recursion where
|
|
// V's malloc() wrapper calls C.malloc() which would otherwise resolve
|
|
// back to the V wrapper.
|
|
const force_external_syms = ['_malloc', '_free', '_calloc', '_realloc', '_exit', '_abort', '_memcpy',
|
|
'_memmove', '_memset', '_memcmp', '___stdoutp', '___stderrp', '_puts', '_printf', '_write',
|
|
'_read', '_open', '_close', '_fwrite', '_fflush', '_fopen', '_fclose', '_putchar', '_sprintf',
|
|
'_snprintf', '_fprintf', '_sscanf', '_mmap', '_munmap', '_getcwd', '_access', '_readlink',
|
|
'_getenv', '_strlen',
|
|
// Filesystem/directory operations
|
|
'_opendir', '_readdir', '_closedir', '_mkdir', '_rmdir',
|
|
'_unlink', '_rename', '_remove', '_stat', '_lstat', '_fstat', '_chmod', '_chdir', '_realpath',
|
|
'_symlink', '_link',
|
|
// Process/system
|
|
'_getpid', '_getuid', '_geteuid', '_fork', '_execve', '_execvp', '_waitpid',
|
|
'_kill', '_system', '_posix_spawn', '_signal', '_atexit',
|
|
// I/O
|
|
'_fgets', '_fputs', '_fread', '_fseek', '_ftell', '_rewind', '_fileno', '_popen',
|
|
'_pclose', '_dup', '_dup2', '_pipe', '_isatty', '_freopen', '_dprintf', '_getc',
|
|
// String/memory
|
|
'_strdup', '_strcmp', '_strncmp', '_strchr', '_strrchr', '_strerror',
|
|
'_strncasecmp', '_strcasecmp', '_atoi', '_atof', '_qsort',
|
|
// Time
|
|
'_time', '_localtime_r', '_gmtime_r', '_mktime', '_gettimeofday', '_clock',
|
|
'_clock_gettime_nsec_np', '_mach_absolute_time', '_mach_timebase_info', '_nanosleep', '_sleep',
|
|
'_usleep', '_strftime',
|
|
'_task_info', '_mach_task_self_',
|
|
// Other
|
|
'_rand', '_srand', '_isdigit', '_isspace', '_tolower', '_toupper', '_setenv',
|
|
'_unsetenv', '_sysconf', '_uname', '_gethostname', '_pthread_mutex_init', '_pthread_mutex_lock',
|
|
'_pthread_mutex_unlock', '_pthread_mutex_destroy', '_pthread_self', '_pthread_create',
|
|
'_pthread_join', '_pthread_attr_init', '_pthread_attr_setstacksize', '_pthread_attr_destroy',
|
|
'_arc4random_buf',
|
|
'_proc_pidpath', '_backtrace', '_backtrace_symbols', '_backtrace_symbols_fd',
|
|
// macOS specific
|
|
'_dispatch_semaphore_create', '_dispatch_semaphore_signal',
|
|
'_dispatch_semaphore_wait', '_dispatch_time', '_dispatch_release', '_setvbuf', '_setbuf',
|
|
'_memchr', '_getlogin_r', '_getppid', '_getgid', '_getegid', '_ftruncate', '_mkstemp', '_statvfs',
|
|
'_chown', '_sigaction', '_sigemptyset', '_sigaddset', '_sigprocmask', '_select', '_kqueue',
|
|
'_abs',
|
|
// Terminal I/O
|
|
'_tcgetattr', '_tcsetattr', '_ioctl', '_getchar', '_getline',
|
|
// File I/O
|
|
'_fdopen', '_feof', '_ferror',
|
|
// Process
|
|
'_setpgid', '_ptrace', '_wait',
|
|
// Time
|
|
'_timegm', '_clock_gettime',
|
|
// Memory
|
|
'_aligned_alloc',
|
|
// System
|
|
'_utime', '_getlogin', '_environ',
|
|
// macOS errno: __error() returns int*
|
|
'___error',
|
|
// macOS stdin
|
|
'___stdinp',
|
|
// macOS dyld
|
|
'__dyld_get_image_name', '__dyld_get_image_header',
|
|
// Math
|
|
'_cos', '_sin', '_tan', '_acos', '_asin', '_atan', '_atan2',
|
|
'_cosh', '_sinh', '_tanh', '_acosh', '_asinh', '_atanh',
|
|
'_exp', '_exp2', '_log', '_log2', '_log10', '_pow', '_sqrt', '_cbrt',
|
|
'_ceil', '_floor', '_round', '_trunc', '_fmod', '_remainder',
|
|
'_fabs', '_copysign', '_fmax', '_fmin', '_hypot',
|
|
'_ldexp', '_frexp', '_modf', '_scalbn', '_ilogb', '_logb',
|
|
'_erf', '_erfc', '_lgamma', '_tgamma',
|
|
'_j0', '_j1', '_jn', '_y0', '_y1', '_yn',
|
|
// Memory protection and cache (hot code reloading)
|
|
'_mprotect', '_sys_icache_invalidate',
|
|
// Objective-C runtime (from libobjc.A.dylib)
|
|
'_objc_msgSend', '_objc_getClass', '_sel_registerName', '_objc_alloc_init',
|
|
'_objc_autoreleasePoolPush', '_objc_autoreleasePoolPop',
|
|
// Metal framework
|
|
'_MTLCreateSystemDefaultDevice',
|
|
// Dynamic loading
|
|
'_dlopen', '_dlsym']
|
|
|
|
// vfmt on
|
|
|
|
// Symbols that live in libobjc.A.dylib (not libSystem).
|
|
const objc_syms = ['_objc_msgSend', '_objc_getClass', '_sel_registerName', '_objc_alloc_init',
|
|
'_objc_autoreleasePoolPush', '_objc_autoreleasePoolPop']
|
|
|
|
// Symbols that live in Metal.framework.
|
|
const metal_syms = ['_MTLCreateSystemDefaultDevice']
|
|
|
|
pub struct Linker {
|
|
macho &MachOObject
|
|
pub mut:
|
|
// Frameworks to link (e.g. ['Metal', 'Cocoa', 'QuartzCore'])
|
|
frameworks []string
|
|
mut:
|
|
// Output buffer
|
|
buf []u8
|
|
|
|
// Segment/section info
|
|
text_vmaddr u64
|
|
text_fileoff int
|
|
text_size int
|
|
data_vmaddr u64
|
|
data_fileoff int
|
|
data_size int
|
|
linkedit_off int
|
|
linkedit_size int
|
|
|
|
// External symbols needing binding
|
|
extern_syms []string
|
|
|
|
// GOT entries for external symbols
|
|
got_offset int // Offset within __DATA segment
|
|
got_size int
|
|
|
|
// Stubs for external function calls
|
|
stubs_offset int
|
|
stubs_size int
|
|
|
|
// Symbol to GOT index mapping
|
|
sym_to_got map[string]int
|
|
|
|
// Multi-dylib support: dylib paths and per-symbol ordinal mapping
|
|
dylibs []string // ['/usr/lib/libSystem.B.dylib', '/usr/lib/libobjc.A.dylib', ...]
|
|
sym_to_dylib map[string]int // symbol name → index into dylibs[] (ordinal = idx + 1)
|
|
|
|
// Code start offset (after header + load commands)
|
|
code_start int
|
|
}
|
|
|
|
pub fn Linker.new(macho &MachOObject) &Linker {
|
|
return unsafe {
|
|
&Linker{
|
|
macho: macho
|
|
frameworks: []string{}
|
|
buf: []u8{}
|
|
extern_syms: []string{}
|
|
sym_to_got: map[string]int{}
|
|
dylibs: []string{}
|
|
sym_to_dylib: map[string]int{}
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn (mut l Linker) link(output_path string, entry_name string) {
|
|
// Pre-allocate buffer with estimated size to avoid reallocations
|
|
estimated_size := l.macho.text_data.len + l.macho.str_data.len + l.macho.data_data.len + 0x10000
|
|
l.buf = []u8{cap: estimated_size}
|
|
mut t := time.now()
|
|
mut t_total := time.now()
|
|
|
|
// First pass: collect all defined symbols (except external ones)
|
|
mut defined_syms := map[string]bool{}
|
|
for sym in l.macho.symbols {
|
|
// N_SECT (0x0E) means symbol is defined in a section
|
|
if (sym.type_ & 0x0E) == 0x0E {
|
|
// Don't track external symbols as defined - they should come from libc
|
|
if sym.name !in force_external_syms {
|
|
defined_syms[sym.name] = true
|
|
}
|
|
}
|
|
}
|
|
|
|
// Second pass: collect truly external symbols.
|
|
// force_external_syms should go through GOT/stubs.
|
|
// All other undefined symbols are internal V functions or V-embedded C functions
|
|
// (like wyhash) that resolve to local stubs.
|
|
for sym in l.macho.symbols {
|
|
if sym.name in force_external_syms && sym.name !in l.extern_syms {
|
|
l.extern_syms << sym.name
|
|
l.sym_to_got[sym.name] = l.extern_syms.len - 1
|
|
}
|
|
}
|
|
|
|
l.got_size = l.extern_syms.len * 8
|
|
l.stubs_size = l.extern_syms.len * 12 // Each stub is 12 bytes on ARM64
|
|
|
|
// Build dylib list: libSystem always first, then libobjc + frameworks as needed.
|
|
l.dylibs = ['/usr/lib/libSystem.B.dylib']
|
|
// Map all existing symbols to libSystem (ordinal 0 = index into dylibs)
|
|
for sym_name in l.extern_syms {
|
|
l.sym_to_dylib[sym_name] = 0 // default: libSystem
|
|
}
|
|
// Check if any objc symbols are used → add libobjc
|
|
mut need_objc := false
|
|
for sym_name in l.extern_syms {
|
|
if sym_name in objc_syms {
|
|
need_objc = true
|
|
break
|
|
}
|
|
}
|
|
if need_objc {
|
|
objc_idx := l.dylibs.len
|
|
l.dylibs << '/usr/lib/libobjc.A.dylib'
|
|
for sym_name in l.extern_syms {
|
|
if sym_name in objc_syms {
|
|
l.sym_to_dylib[sym_name] = objc_idx
|
|
}
|
|
}
|
|
}
|
|
// Check if any framework symbols are used → add framework dylibs
|
|
for sym_name in l.extern_syms {
|
|
if sym_name in metal_syms {
|
|
if 'Metal' !in l.frameworks {
|
|
l.frameworks << 'Metal'
|
|
}
|
|
}
|
|
}
|
|
for fw in l.frameworks {
|
|
fw_idx := l.dylibs.len
|
|
l.dylibs << '/System/Library/Frameworks/${fw}.framework/${fw}'
|
|
// Map framework symbols to this dylib index
|
|
if fw == 'Metal' {
|
|
for sym_name in l.extern_syms {
|
|
if sym_name in metal_syms {
|
|
l.sym_to_dylib[sym_name] = fw_idx
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Calculate layout
|
|
// On macOS, __TEXT segment MUST start at fileoff 0
|
|
// The header and load commands are inside the __TEXT segment
|
|
n_load_cmds := 13 + l.dylibs.len // 13 fixed commands + 1 LC_LOAD_DYLIB per dylib
|
|
pagezero_cmd_size := 72
|
|
text_cmd_size := 72 + (80 * 2) // __text + __stubs
|
|
data_cmd_size := 72 + (80 * 2) // __data + __got
|
|
linkedit_cmd_size := 72
|
|
dyld_info_cmd_size := 48
|
|
symtab_cmd_size := 24
|
|
dysymtab_cmd_size := 80
|
|
dylinker_cmd_size := 32
|
|
// Each LC_LOAD_DYLIB: 24 bytes header + path padded to 8-byte alignment
|
|
mut dylib_cmd_size := 0
|
|
for dylib_path in l.dylibs {
|
|
path_len := dylib_path.len + 1 // +1 for null terminator
|
|
padded_path := (path_len + 7) & ~7
|
|
dylib_cmd_size += 24 + padded_path
|
|
}
|
|
main_cmd_size := 24
|
|
uuid_cmd_size := 24
|
|
build_version_cmd_size := 24
|
|
source_version_cmd_size := 16
|
|
code_signature_cmd_size := 16
|
|
|
|
load_cmds_size := pagezero_cmd_size + text_cmd_size + data_cmd_size + linkedit_cmd_size +
|
|
dyld_info_cmd_size + symtab_cmd_size + dysymtab_cmd_size + dylinker_cmd_size +
|
|
dylib_cmd_size + main_cmd_size + uuid_cmd_size + build_version_cmd_size +
|
|
source_version_cmd_size + code_signature_cmd_size
|
|
|
|
// __TEXT starts at file offset 0 and vmaddr base_addr
|
|
l.text_fileoff = 0
|
|
l.text_vmaddr = base_addr
|
|
|
|
// Code starts after header + load commands, aligned to 16 bytes
|
|
// Leave ~600 bytes extra for codesign to add LC_CODE_SIGNATURE
|
|
// Header (32) + load_cmds (~700) + codesign reserve (600) ≈ 1332, align to 2048
|
|
header_size := 32
|
|
code_start_min := header_size + load_cmds_size + 600 // Reserve for codesign
|
|
l.code_start = (code_start_min + 15) & ~15 // Align to 16 bytes
|
|
|
|
// Calculate where stubs will be (after code and cstrings)
|
|
l.stubs_offset = l.code_start + l.macho.text_data.len + l.macho.str_data.len
|
|
// Align to 4 bytes
|
|
for l.stubs_offset % 4 != 0 {
|
|
l.stubs_offset++
|
|
}
|
|
|
|
// Text segment size includes header, load commands, code, cstrings, stubs
|
|
text_content_end := l.stubs_offset + l.stubs_size
|
|
l.text_size = (text_content_end + page_size - 1) & ~(page_size - 1)
|
|
|
|
// Data segment follows text
|
|
l.data_fileoff = l.text_size
|
|
l.data_vmaddr = base_addr + u64(l.text_size)
|
|
|
|
// GOT offset within data section
|
|
mut got_offset := l.macho.data_data.len
|
|
// Align GOT to 8 bytes
|
|
for got_offset % 8 != 0 {
|
|
got_offset++
|
|
}
|
|
l.got_offset = got_offset
|
|
|
|
data_content_size := got_offset + l.got_size
|
|
l.data_size = (data_content_size + page_size - 1) & ~(page_size - 1)
|
|
if l.data_size == 0 {
|
|
l.data_size = page_size
|
|
}
|
|
|
|
bind_info := l.generate_bind_info(got_offset)
|
|
bind_size := bind_info.len
|
|
|
|
// Write header
|
|
l.write_header(n_load_cmds, load_cmds_size)
|
|
|
|
// Write load commands
|
|
l.write_pagezero_segment()
|
|
l.write_text_segment()
|
|
l.write_data_segment()
|
|
linkedit_start := l.buf.len
|
|
l.write_linkedit_segment() // Will patch later
|
|
|
|
// Bind info position (in LINKEDIT)
|
|
bind_off := l.data_fileoff + l.data_size
|
|
|
|
// Build symbol table for internal function names (visible in objdump -d)
|
|
mut symtab_data := []u8{}
|
|
mut strtab_data := []u8{}
|
|
strtab_data << 0 // First byte of string table must be null
|
|
|
|
sym_code_vmaddr := l.text_vmaddr + u64(l.code_start)
|
|
|
|
// Find data section base address (minimum symbol value in sect 3)
|
|
mut sym_data_base := u64(0xFFFFFFFFFFFFFFFF)
|
|
for sym in l.macho.symbols {
|
|
if (sym.type_ & 0x0E) == 0x0E && sym.sect == 3 {
|
|
if sym.value < sym_data_base {
|
|
sym_data_base = sym.value
|
|
}
|
|
}
|
|
}
|
|
if sym_data_base == 0xFFFFFFFFFFFFFFFF {
|
|
sym_data_base = u64(l.macho.text_data.len + l.macho.str_data.len)
|
|
}
|
|
|
|
for sym in l.macho.symbols {
|
|
if (sym.type_ & 0x0E) != 0x0E {
|
|
continue // Skip undefined symbols
|
|
}
|
|
if sym.name in force_external_syms {
|
|
continue
|
|
}
|
|
|
|
mut vm_addr := u64(0)
|
|
mut out_sect := u8(0)
|
|
if sym.sect == 1 {
|
|
// __text section
|
|
vm_addr = sym_code_vmaddr + sym.value
|
|
out_sect = 1
|
|
} else if sym.sect == 3 {
|
|
// __data section
|
|
vm_addr = l.data_vmaddr + (sym.value - sym_data_base)
|
|
out_sect = 3
|
|
} else {
|
|
continue
|
|
}
|
|
|
|
str_idx := strtab_data.len
|
|
strtab_data << sym.name.bytes()
|
|
strtab_data << 0
|
|
|
|
write_u32_le(mut symtab_data, u32(str_idx)) // n_strx
|
|
symtab_data << sym.type_ // n_type
|
|
symtab_data << out_sect // n_sect
|
|
write_u16_le(mut symtab_data, sym.desc) // n_desc
|
|
write_u64_le(mut symtab_data, vm_addr) // n_value
|
|
}
|
|
|
|
// Symbol table follows bind info and must be aligned in LINKEDIT.
|
|
symtab_unaligned_off := bind_off + bind_size
|
|
symtab_off := (symtab_unaligned_off + 7) & ~7
|
|
symtab_pad := symtab_off - symtab_unaligned_off
|
|
n_syms := symtab_data.len / 16
|
|
strtab_off := symtab_off + symtab_data.len
|
|
strtab_size := strtab_data.len
|
|
|
|
// Code signature follows string table and should be aligned in LINKEDIT.
|
|
code_limit_unaligned := strtab_off + strtab_size
|
|
cs_off := (code_limit_unaligned + 15) & ~15
|
|
cs_pad := cs_off - code_limit_unaligned
|
|
// code_limit is where the signature starts (everything before is hashed)
|
|
code_limit := cs_off
|
|
// Signature size: SuperBlob(12) + 2*BlobIndex(8) + CodeDirectory header + identifier + hashes + Requirements blob
|
|
ident := output_path.all_after_last('/') // Use filename as identifier
|
|
cs_size := l.estimate_signature_size(code_limit, ident)
|
|
|
|
l.linkedit_off = bind_off
|
|
l.linkedit_size = bind_size + symtab_pad + symtab_data.len + strtab_size + cs_pad + cs_size
|
|
|
|
l.write_dyld_info(bind_off, bind_size)
|
|
l.write_symtab(symtab_off, n_syms, strtab_off, strtab_size)
|
|
l.write_dysymtab(n_syms)
|
|
l.write_load_dylinker()
|
|
l.write_load_dylibs()
|
|
|
|
// Find entry point
|
|
entry_off := l.find_entry_offset(entry_name)
|
|
l.write_main_cmd(entry_off)
|
|
|
|
l.write_uuid()
|
|
l.write_build_version()
|
|
l.write_source_version()
|
|
|
|
// Write LC_CODE_SIGNATURE (will be at cs_off with size cs_size)
|
|
codesig_cmd_start := l.buf.len
|
|
l.write_code_signature_cmd(cs_off, cs_size)
|
|
|
|
// Patch LINKEDIT segment with actual values (including signature)
|
|
l.patch_linkedit(linkedit_start, bind_off, l.linkedit_size)
|
|
|
|
println(' headers+cmds: ${time.since(t)}')
|
|
t = time.now()
|
|
|
|
// Pad to code start (after header + load commands)
|
|
l.pad_to(l.code_start)
|
|
|
|
// Write text section with relocations applied
|
|
l.write_text_with_relocations()
|
|
|
|
println(' text+relocs: ${time.since(t)}')
|
|
t = time.now()
|
|
|
|
// Write cstring section
|
|
l.buf << l.macho.str_data
|
|
|
|
// Pad and write stubs
|
|
l.pad_to(l.stubs_offset)
|
|
l.write_stubs()
|
|
|
|
// Pad to data start
|
|
l.pad_to(l.data_fileoff)
|
|
|
|
// Write data section
|
|
l.buf << l.macho.data_data
|
|
|
|
// Pad to GOT offset and write GOT (initially zeros, dyld will fill)
|
|
l.pad_to(l.data_fileoff + got_offset)
|
|
l.write_zeros(l.extern_syms.len * 8)
|
|
|
|
// Pad data segment
|
|
l.pad_to(l.data_fileoff + l.data_size)
|
|
|
|
// Write LINKEDIT content
|
|
l.buf << bind_info
|
|
l.write_zeros(symtab_pad)
|
|
|
|
// Write symbol table nlist entries
|
|
l.buf << symtab_data
|
|
|
|
// Write string table
|
|
l.buf << strtab_data
|
|
|
|
// Align code signature start in LINKEDIT.
|
|
l.write_zeros(cs_pad)
|
|
|
|
println(' padding+data: ${time.since(t)}')
|
|
t = time.now()
|
|
|
|
// Generate and write code signature (ad-hoc signing)
|
|
signature := l.generate_code_signature(ident)
|
|
l.buf << signature
|
|
|
|
// Patch LC_CODE_SIGNATURE if size differs from estimate
|
|
actual_cs_size := signature.len
|
|
if actual_cs_size != cs_size {
|
|
// Patch datasize in LC_CODE_SIGNATURE command
|
|
write_u32_le_at(mut l.buf, codesig_cmd_start + 12, u32(actual_cs_size))
|
|
}
|
|
|
|
println(' codesign: ${time.since(t)}')
|
|
t = time.now()
|
|
|
|
tmp_output_path := '${output_path}.tmp.${os.getpid()}'
|
|
if !write_file_array_raw(tmp_output_path, l.buf) {
|
|
panic('failed to write output file')
|
|
}
|
|
if C.chmod(tmp_output_path.str, 0o755) != 0 {
|
|
panic('failed to chmod output file')
|
|
}
|
|
if C.rename(tmp_output_path.str, output_path.str) != 0 {
|
|
panic('failed to rename output file')
|
|
}
|
|
|
|
println(' file write: ${time.since(t)}')
|
|
println(' TOTAL linker: ${time.since(t_total)}')
|
|
}
|
|
|
|
fn write_file_array_raw(path string, data []u8) bool {
|
|
fd := C.open(path.str, o_wronly_creat_trunc, 0o755)
|
|
if fd < 0 {
|
|
return false
|
|
}
|
|
if data.len > 0 {
|
|
written := C.write(fd, data.data, data.len)
|
|
if written != data.len {
|
|
C.close(fd)
|
|
return false
|
|
}
|
|
}
|
|
return C.close(fd) == 0
|
|
}
|
|
|
|
fn (mut l Linker) write_header(ncmds int, cmdsize int) {
|
|
write_mh_magic_64(mut l.buf)
|
|
write_u32_le(mut l.buf, u32(cpu_type_arm64))
|
|
write_u32_le(mut l.buf, u32(cpu_subtype_arm64_all))
|
|
write_u32_le(mut l.buf, mh_execute)
|
|
write_u32_le(mut l.buf, u32(ncmds))
|
|
write_u32_le(mut l.buf, u32(cmdsize))
|
|
write_u32_le(mut l.buf, 0x00200085) // MH_NOUNDEFS | MH_DYLDLINK | MH_TWOLEVEL | MH_PIE
|
|
write_u32_le(mut l.buf, 0) // reserved
|
|
}
|
|
|
|
fn (mut l Linker) write_pagezero_segment() {
|
|
write_u32_le(mut l.buf, u32(lc_segment_64))
|
|
write_u32_le(mut l.buf, 72)
|
|
write_string_fixed(mut l.buf, '__PAGEZERO', 16)
|
|
write_u64_le(mut l.buf, 0) // vmaddr
|
|
write_u64_le(mut l.buf, base_addr) // vmsize
|
|
write_u64_le(mut l.buf, 0) // fileoff
|
|
write_u64_le(mut l.buf, 0) // filesize
|
|
write_u32_le(mut l.buf, 0) // maxprot
|
|
write_u32_le(mut l.buf, 0) // initprot
|
|
write_u32_le(mut l.buf, 0) // nsects
|
|
write_u32_le(mut l.buf, 0) // flags
|
|
}
|
|
|
|
fn (mut l Linker) write_text_segment() {
|
|
write_u32_le(mut l.buf, u32(lc_segment_64))
|
|
write_u32_le(mut l.buf, 72 + 80 * 2) // cmd size with 2 sections
|
|
write_string_fixed(mut l.buf, '__TEXT', 16)
|
|
|
|
write_u64_le(mut l.buf, l.text_vmaddr) // vmaddr = base_addr
|
|
write_u64_le(mut l.buf, u64(l.text_size)) // vmsize
|
|
write_u64_le(mut l.buf, 0) // fileoff MUST be 0
|
|
write_u64_le(mut l.buf, u64(l.text_size)) // filesize
|
|
write_u32_le(mut l.buf, 5) // maxprot (r-x)
|
|
write_u32_le(mut l.buf, 5) // initprot (r-x)
|
|
write_u32_le(mut l.buf, 2) // nsects
|
|
write_u32_le(mut l.buf, 0) // flags
|
|
|
|
// __text section (code starts at code_start offset)
|
|
write_string_fixed(mut l.buf, '__text', 16)
|
|
write_string_fixed(mut l.buf, '__TEXT', 16)
|
|
write_u64_le(mut l.buf, l.text_vmaddr + u64(l.code_start)) // addr
|
|
write_u64_le(mut l.buf, u64(l.macho.text_data.len)) // size
|
|
write_u32_le(mut l.buf, u32(l.code_start)) // offset
|
|
write_u32_le(mut l.buf, 4) // align (16 bytes = 2^4)
|
|
write_u32_le(mut l.buf, 0) // reloff
|
|
write_u32_le(mut l.buf, 0) // nreloc
|
|
write_u32_le(mut l.buf, 0x80000400) // flags: S_ATTR_PURE_INSTRUCTIONS | S_ATTR_SOME_INSTRUCTIONS
|
|
write_u32_le(mut l.buf, 0) // reserved1
|
|
write_u32_le(mut l.buf, 0) // reserved2
|
|
write_u32_le(mut l.buf, 0) // reserved3
|
|
|
|
// __stubs section - using regular code section flags since we use immediate binding
|
|
write_string_fixed(mut l.buf, '__stubs', 16)
|
|
write_string_fixed(mut l.buf, '__TEXT', 16)
|
|
write_u64_le(mut l.buf, l.text_vmaddr + u64(l.stubs_offset)) // addr
|
|
write_u64_le(mut l.buf, u64(l.stubs_size)) // size
|
|
write_u32_le(mut l.buf, u32(l.stubs_offset)) // offset
|
|
write_u32_le(mut l.buf, 2) // align
|
|
write_u32_le(mut l.buf, 0) // reloff
|
|
write_u32_le(mut l.buf, 0) // nreloc
|
|
write_u32_le(mut l.buf, 0x80000400) // S_ATTR_PURE_INSTRUCTIONS | S_ATTR_SOME_INSTRUCTIONS (no S_SYMBOL_STUBS)
|
|
write_u32_le(mut l.buf, 0) // reserved1
|
|
write_u32_le(mut l.buf, 0) // reserved2
|
|
write_u32_le(mut l.buf, 0) // reserved3
|
|
}
|
|
|
|
fn (mut l Linker) write_data_segment() {
|
|
write_u32_le(mut l.buf, u32(lc_segment_64))
|
|
write_u32_le(mut l.buf, 72 + 80 * 2) // cmd size with 2 sections
|
|
write_string_fixed(mut l.buf, '__DATA', 16)
|
|
write_u64_le(mut l.buf, l.data_vmaddr) // vmaddr
|
|
write_u64_le(mut l.buf, u64(l.data_size)) // vmsize
|
|
write_u64_le(mut l.buf, u64(l.data_fileoff)) // fileoff
|
|
write_u64_le(mut l.buf, u64(l.data_size)) // filesize
|
|
write_u32_le(mut l.buf, 3) // maxprot (rw-)
|
|
write_u32_le(mut l.buf, 3) // initprot (rw-)
|
|
write_u32_le(mut l.buf, 2) // nsects
|
|
write_u32_le(mut l.buf, 0) // flags
|
|
|
|
// __data section
|
|
write_string_fixed(mut l.buf, '__data', 16)
|
|
write_string_fixed(mut l.buf, '__DATA', 16)
|
|
write_u64_le(mut l.buf, l.data_vmaddr) // addr
|
|
write_u64_le(mut l.buf, u64(l.macho.data_data.len)) // size
|
|
write_u32_le(mut l.buf, u32(l.data_fileoff)) // offset
|
|
write_u32_le(mut l.buf, 3) // align (8 bytes = 2^3)
|
|
write_u32_le(mut l.buf, 0) // reloff
|
|
write_u32_le(mut l.buf, 0) // nreloc
|
|
write_u32_le(mut l.buf, 0) // flags
|
|
write_u32_le(mut l.buf, 0) // reserved1
|
|
write_u32_le(mut l.buf, 0) // reserved2
|
|
write_u32_le(mut l.buf, 0) // reserved3
|
|
|
|
// __got section - using regular data section since we use immediate binding via bind info
|
|
write_string_fixed(mut l.buf, '__got', 16)
|
|
write_string_fixed(mut l.buf, '__DATA', 16)
|
|
write_u64_le(mut l.buf, l.data_vmaddr + u64(l.got_offset)) // addr
|
|
write_u64_le(mut l.buf, u64(l.got_size)) // size
|
|
write_u32_le(mut l.buf, u32(l.data_fileoff + l.got_offset)) // offset
|
|
write_u32_le(mut l.buf, 3) // align
|
|
write_u32_le(mut l.buf, 0) // reloff
|
|
write_u32_le(mut l.buf, 0) // nreloc
|
|
write_u32_le(mut l.buf, 0x00) // S_REGULAR (no special flags - dyld will fill via bind info)
|
|
write_u32_le(mut l.buf, 0) // reserved1
|
|
write_u32_le(mut l.buf, 0) // reserved2
|
|
write_u32_le(mut l.buf, 0) // reserved3
|
|
}
|
|
|
|
fn (mut l Linker) write_linkedit_segment() {
|
|
write_u32_le(mut l.buf, u32(lc_segment_64))
|
|
write_u32_le(mut l.buf, 72)
|
|
write_string_fixed(mut l.buf, '__LINKEDIT', 16)
|
|
write_u64_le(mut l.buf, 0) // vmaddr - patched later
|
|
write_u64_le(mut l.buf, 0) // vmsize - patched later
|
|
write_u64_le(mut l.buf, 0) // fileoff - patched later
|
|
write_u64_le(mut l.buf, 0) // filesize - patched later
|
|
write_u32_le(mut l.buf, 1) // maxprot (r--)
|
|
write_u32_le(mut l.buf, 1) // initprot (r--)
|
|
write_u32_le(mut l.buf, 0) // nsects
|
|
write_u32_le(mut l.buf, 0) // flags
|
|
}
|
|
|
|
fn (mut l Linker) patch_linkedit(cmd_start int, fileoff int, filesize int) {
|
|
linkedit_vmaddr := l.data_vmaddr + u64(l.data_size)
|
|
mut linkedit_vmsize := u64((filesize + page_size - 1) & ~(page_size - 1))
|
|
if linkedit_vmsize == 0 {
|
|
linkedit_vmsize = u64(page_size)
|
|
}
|
|
|
|
// Patch vmaddr, vmsize, fileoff, filesize at known offsets within LINKEDIT cmd
|
|
off := cmd_start + 8 + 16 // after cmd, cmdsize, segname
|
|
write_u64_le_at(mut l.buf, off, linkedit_vmaddr)
|
|
write_u64_le_at(mut l.buf, off + 8, linkedit_vmsize)
|
|
write_u64_le_at(mut l.buf, off + 16, u64(fileoff))
|
|
write_u64_le_at(mut l.buf, off + 24, u64(filesize))
|
|
}
|
|
|
|
fn (mut l Linker) write_dyld_info(bind_off int, bind_size int) {
|
|
write_u32_le(mut l.buf, u32(lc_dyld_info_only))
|
|
write_u32_le(mut l.buf, 48)
|
|
write_u32_le(mut l.buf, 0) // rebase_off
|
|
write_u32_le(mut l.buf, 0) // rebase_size
|
|
write_u32_le(mut l.buf, u32(bind_off)) // bind_off
|
|
write_u32_le(mut l.buf, u32(bind_size)) // bind_size
|
|
write_u32_le(mut l.buf, 0) // weak_bind_off
|
|
write_u32_le(mut l.buf, 0) // weak_bind_size
|
|
write_u32_le(mut l.buf, 0) // lazy_bind_off
|
|
write_u32_le(mut l.buf, 0) // lazy_bind_size
|
|
write_u32_le(mut l.buf, 0) // export_off
|
|
write_u32_le(mut l.buf, 0) // export_size
|
|
}
|
|
|
|
fn (mut l Linker) write_symtab(symoff int, nsyms int, stroff int, strsize int) {
|
|
write_u32_le(mut l.buf, u32(lc_symtab))
|
|
write_u32_le(mut l.buf, 24)
|
|
write_u32_le(mut l.buf, u32(symoff))
|
|
write_u32_le(mut l.buf, u32(nsyms))
|
|
write_u32_le(mut l.buf, u32(stroff))
|
|
write_u32_le(mut l.buf, u32(strsize))
|
|
}
|
|
|
|
fn (mut l Linker) write_dysymtab(_nsyms int) {
|
|
write_u32_le(mut l.buf, u32(lc_dysymtab))
|
|
write_u32_le(mut l.buf, 80)
|
|
write_u32_le(mut l.buf, 0) // ilocalsym
|
|
write_u32_le(mut l.buf, 0) // nlocalsym
|
|
write_u32_le(mut l.buf, 0) // iextdefsym
|
|
write_u32_le(mut l.buf, 0) // nextdefsym
|
|
write_u32_le(mut l.buf, 0) // iundefsym
|
|
write_u32_le(mut l.buf, 0) // nundefsym
|
|
write_u32_le(mut l.buf, 0) // tocoff
|
|
write_u32_le(mut l.buf, 0) // ntoc
|
|
write_u32_le(mut l.buf, 0) // modtaboff
|
|
write_u32_le(mut l.buf, 0) // nmodtab
|
|
write_u32_le(mut l.buf, 0) // extrefsymoff
|
|
write_u32_le(mut l.buf, 0) // nextrefsyms
|
|
write_u32_le(mut l.buf, 0) // indirectsymoff
|
|
write_u32_le(mut l.buf, 0) // nindirectsyms
|
|
write_u32_le(mut l.buf, 0) // extreloff
|
|
write_u32_le(mut l.buf, 0) // nextrel
|
|
write_u32_le(mut l.buf, 0) // locreloff
|
|
write_u32_le(mut l.buf, 0) // nlocrel
|
|
}
|
|
|
|
fn (mut l Linker) write_load_dylinker() {
|
|
write_u32_le(mut l.buf, u32(lc_load_dylinker))
|
|
write_u32_le(mut l.buf, 32)
|
|
write_u32_le(mut l.buf, 12) // offset to string
|
|
write_string_fixed(mut l.buf, '/usr/lib/dyld', 20)
|
|
}
|
|
|
|
fn (mut l Linker) write_load_dylibs() {
|
|
for dylib_path in l.dylibs {
|
|
path_len := dylib_path.len + 1 // +1 for null terminator
|
|
padded_path := (path_len + 7) & ~7
|
|
cmd_size := 24 + padded_path // header (24) + padded path
|
|
write_u32_le(mut l.buf, u32(lc_load_dylib))
|
|
write_u32_le(mut l.buf, u32(cmd_size))
|
|
write_u32_le(mut l.buf, 24) // offset to string (always 24 in header)
|
|
write_u32_le(mut l.buf, 0) // timestamp
|
|
write_u32_le(mut l.buf, 0x10000) // current version
|
|
write_u32_le(mut l.buf, 0x10000) // compatibility version
|
|
write_string_fixed(mut l.buf, dylib_path, padded_path)
|
|
}
|
|
}
|
|
|
|
fn (mut l Linker) write_main_cmd(entry_off int) {
|
|
write_u32_le(mut l.buf, u32(lc_main))
|
|
write_u32_le(mut l.buf, 24)
|
|
write_u64_le(mut l.buf, u64(entry_off)) // entryoff (offset from __TEXT start)
|
|
write_u64_le(mut l.buf, 0) // stacksize
|
|
}
|
|
|
|
fn (mut l Linker) write_uuid() {
|
|
write_u32_le(mut l.buf, u32(lc_uuid))
|
|
write_u32_le(mut l.buf, 24)
|
|
// Random UUID
|
|
for _ in 0 .. 16 {
|
|
l.buf << 0
|
|
}
|
|
}
|
|
|
|
fn (mut l Linker) write_build_version() {
|
|
write_u32_le(mut l.buf, u32(lc_build_version))
|
|
write_u32_le(mut l.buf, 24)
|
|
write_u32_le(mut l.buf, 1) // platform: MACOS
|
|
write_u32_le(mut l.buf, 0x000b0000) // minos: 11.0.0
|
|
write_u32_le(mut l.buf, 0x000b0000) // sdk: 11.0.0
|
|
write_u32_le(mut l.buf, 0) // ntools
|
|
}
|
|
|
|
fn (mut l Linker) write_source_version() {
|
|
write_u32_le(mut l.buf, u32(lc_source_version))
|
|
write_u32_le(mut l.buf, 16)
|
|
write_u64_le(mut l.buf, 0) // version
|
|
}
|
|
|
|
fn (mut l Linker) write_code_signature_cmd(dataoff int, datasize int) {
|
|
write_u32_le(mut l.buf, u32(lc_code_signature))
|
|
write_u32_le(mut l.buf, 16) // cmdsize
|
|
write_u32_le(mut l.buf, u32(dataoff))
|
|
write_u32_le(mut l.buf, u32(datasize))
|
|
}
|
|
|
|
fn (l &Linker) estimate_signature_size(code_limit int, ident string) int {
|
|
// Calculate pages using ARM64 16KB page size
|
|
n_pages := (code_limit + cs_page_size_arm64 - 1) / cs_page_size_arm64
|
|
|
|
// SuperBlob header (12) + 3 BlobIndex entries (24)
|
|
// + CodeDirectory + Requirements blob + CMS blob
|
|
ident_len := ident.len + 1 // null terminated
|
|
n_special_slots := 2
|
|
special_hashes_size := n_special_slots * cs_hash_size
|
|
// CodeDirectory: header (88 for version 0x20400) + ident + special hashes + code hashes
|
|
cd_size := 88 + ident_len + special_hashes_size + (n_pages * cs_hash_size)
|
|
// Round up to 4-byte alignment
|
|
cd_size_aligned := (cd_size + 3) & ~3
|
|
// Requirements blob: minimal empty requirements (12 bytes)
|
|
req_size := 12
|
|
// CMS blob: empty wrapper (8 bytes)
|
|
cms_size := 8
|
|
// Total: SuperBlob(12) + 3*BlobIndex(8) + CodeDirectory + Requirements + CMS
|
|
return 12 + 24 + cd_size_aligned + req_size + cms_size
|
|
}
|
|
|
|
fn (l &Linker) generate_code_signature(ident string) []u8 {
|
|
mut sig := []u8{}
|
|
|
|
// Calculate sizes using ARM64 16KB pages
|
|
code_limit := l.buf.len // Current buffer is the code to hash
|
|
n_pages := (code_limit + cs_page_size_arm64 - 1) / cs_page_size_arm64
|
|
ident_bytes := ident.bytes()
|
|
ident_len := ident_bytes.len + 1 // null terminated
|
|
|
|
// Special slots: we need at least slot for requirements (-2)
|
|
n_special_slots := 2 // Slots -1 (info.plist) and -2 (requirements)
|
|
special_hashes_size := n_special_slots * cs_hash_size
|
|
|
|
// CodeDirectory layout for version 0x20400:
|
|
// - Base header (44 bytes): magic, length, version, flags, hashOffset, identOffset,
|
|
// nSpecialSlots, nCodeSlots, codeLimit, hashSize, hashType, platform, pageSize, spare2
|
|
// - scatterOffset (4 bytes)
|
|
// - teamOffset (4 bytes)
|
|
// - spare3 (4 bytes)
|
|
// - codeLimit64 (8 bytes)
|
|
// - execSegBase (8 bytes)
|
|
// - execSegLimit (8 bytes)
|
|
// - execSegFlags (8 bytes)
|
|
// Total header: 88 bytes
|
|
cd_header_size := 88
|
|
ident_offset := cd_header_size
|
|
hash_offset := ident_offset + ident_len + special_hashes_size
|
|
cd_size := hash_offset + (n_pages * cs_hash_size)
|
|
cd_size_aligned := (cd_size + 3) & ~3
|
|
|
|
// Requirements blob (empty)
|
|
req_size := 12
|
|
// CMS signature blob (empty wrapper for ad-hoc)
|
|
cms_size := 8
|
|
|
|
// SuperBlob layout with 3 blobs
|
|
blob_count := 3 // CodeDirectory + Requirements + CMS
|
|
super_blob_header := 12
|
|
blob_index_size := blob_count * 8
|
|
|
|
cd_blob_offset := super_blob_header + blob_index_size
|
|
req_blob_offset := cd_blob_offset + cd_size_aligned
|
|
cms_blob_offset := req_blob_offset + req_size
|
|
total_size := cms_blob_offset + cms_size
|
|
|
|
// Write SuperBlob header (big-endian)
|
|
write_u32_be(mut sig, csmagic_embedded_signature)
|
|
write_u32_be(mut sig, u32(total_size))
|
|
write_u32_be(mut sig, u32(blob_count))
|
|
|
|
// BlobIndex for CodeDirectory (type = 0 = CSSLOT_CODEDIRECTORY)
|
|
write_u32_be(mut sig, csslot_codedirectory)
|
|
write_u32_be(mut sig, u32(cd_blob_offset))
|
|
|
|
// BlobIndex for Requirements (type = 2 = CSSLOT_REQUIREMENTS)
|
|
write_u32_be(mut sig, csslot_requirements)
|
|
write_u32_be(mut sig, u32(req_blob_offset))
|
|
|
|
// BlobIndex for CMS signature (type = 0x10000)
|
|
write_u32_be(mut sig, csslot_cms_signature)
|
|
write_u32_be(mut sig, u32(cms_blob_offset))
|
|
|
|
// Write CodeDirectory (big-endian) - version 0x20400
|
|
write_u32_be(mut sig, csmagic_codedirectory)
|
|
write_u32_be(mut sig, u32(cd_size))
|
|
write_u32_be(mut sig, 0x20400) // version
|
|
write_u32_be(mut sig, cs_adhoc) // flags (ad-hoc)
|
|
write_u32_be(mut sig, u32(hash_offset)) // hashOffset
|
|
write_u32_be(mut sig, u32(ident_offset)) // identOffset
|
|
write_u32_be(mut sig, u32(n_special_slots)) // nSpecialSlots
|
|
write_u32_be(mut sig, u32(n_pages)) // nCodeSlots
|
|
write_u32_be(mut sig, u32(code_limit)) // codeLimit
|
|
sig << cs_hash_size // hashSize
|
|
sig << cs_hashtype_sha256 // hashType
|
|
sig << 0 // platform
|
|
sig << cs_page_shift_arm64 // pageSize (log2 of 16384 = 14)
|
|
write_u32_be(mut sig, 0) // spare2
|
|
// Version 0x20400 additional fields:
|
|
write_u32_be(mut sig, 0) // scatterOffset (0 = none)
|
|
write_u32_be(mut sig, 0) // teamOffset (0 = none)
|
|
write_u32_be(mut sig, 0) // spare3
|
|
write_u64_be(mut sig, 0) // codeLimit64 (0 = use codeLimit)
|
|
write_u64_be(mut sig, 0) // execSegBase (0 = __TEXT starts at 0)
|
|
write_u64_be(mut sig, u64(l.text_size)) // execSegLimit (size of __TEXT segment)
|
|
write_u64_be(mut sig, 1) // execSegFlags (CS_EXECSEG_MAIN_BINARY = 1)
|
|
|
|
// Write identifier (null-terminated)
|
|
for i in 0 .. ident_bytes.len {
|
|
sig << ident_bytes[i]
|
|
}
|
|
sig << 0
|
|
|
|
// Write special slot hashes (slots -2, -1 in that order)
|
|
|
|
// Build the requirements blob first so we can hash it
|
|
mut req_blob := []u8{}
|
|
write_u32_be(mut req_blob, csmagic_requirements)
|
|
write_u32_be(mut req_blob, u32(req_size))
|
|
write_u32_be(mut req_blob, 0) // count = 0
|
|
|
|
// Slot -2: Hash of requirements blob
|
|
mut hash_buf := [32]u8{}
|
|
sha256_hash(req_blob.data, req_blob.len, &hash_buf[0])
|
|
for i in 0 .. cs_hash_size {
|
|
sig << hash_buf[i]
|
|
}
|
|
|
|
// Slot -1: Info.plist (zeros = no Info.plist)
|
|
for _ in 0 .. cs_hash_size {
|
|
sig << 0
|
|
}
|
|
|
|
// Compute page hashes in parallel (16KB pages)
|
|
mut all_hashes := []u8{len: n_pages * 32}
|
|
data_ptr := unsafe { &u8(l.buf.data) }
|
|
// Hash all pages sequentially. V's `spawn` is not supported on the native
|
|
// ARM64 backend, so we avoid threads here for self-hosting compatibility.
|
|
sha256_hash_pages(data_ptr, mut all_hashes, 0, n_pages, code_limit)
|
|
for i in 0 .. all_hashes.len {
|
|
sig << all_hashes[i]
|
|
}
|
|
|
|
// Pad CodeDirectory to alignment
|
|
for sig.len < cd_blob_offset + cd_size_aligned {
|
|
sig << 0
|
|
}
|
|
|
|
// Write Requirements blob
|
|
for i in 0 .. req_blob.len {
|
|
sig << req_blob[i]
|
|
}
|
|
|
|
// Write empty CMS signature blob (for ad-hoc signing)
|
|
write_u32_be(mut sig, csmagic_blobwrapper)
|
|
write_u32_be(mut sig, u32(cms_size))
|
|
|
|
return sig
|
|
}
|
|
|
|
fn (mut l Linker) find_entry_offset(entry_name string) int {
|
|
// Find the _main symbol
|
|
// LC_MAIN entryoff is relative to __TEXT segment vmaddr
|
|
// Code section starts at code_start within __TEXT
|
|
for sym in l.macho.symbols {
|
|
if sym.name == entry_name && sym.sect == 1 {
|
|
return l.code_start + int(sym.value)
|
|
}
|
|
}
|
|
return l.code_start // Default to start of code section
|
|
}
|
|
|
|
fn (mut l Linker) generate_bind_info(got_offset int) []u8 {
|
|
mut info := []u8{}
|
|
|
|
// Data segment index (segment 2: __PAGEZERO=0, __TEXT=1, __DATA=2)
|
|
data_seg_idx := u8(2)
|
|
|
|
mut got_entry_offset := got_offset
|
|
for sym_name in l.extern_syms {
|
|
// Internal runtime callback names can appear as unresolved function refs in
|
|
// bootstrap builds. Bind them as weak imports so dyld does not abort load
|
|
// when they are absent from libSystem; unresolved weak symbols become NULL.
|
|
mut bind_flags := u8(0)
|
|
if sym_name.contains('__') && sym_name !in force_external_syms {
|
|
bind_flags = bind_symbol_flags_weak_import
|
|
}
|
|
|
|
// Set dylib ordinal (1-based: 1 = first dylib)
|
|
mut ordinal_idx := 0
|
|
if idx := l.sym_to_dylib[sym_name] {
|
|
ordinal_idx = idx
|
|
}
|
|
ordinal := u8(ordinal_idx + 1)
|
|
info << (bind_opcode_set_dylib_ordinal_imm | ordinal)
|
|
|
|
// Set symbol name
|
|
info << (bind_opcode_set_symbol_flags_imm | bind_flags)
|
|
info << sym_name.bytes()
|
|
info << 0 // null terminator
|
|
|
|
// Set type (pointer)
|
|
info << (bind_opcode_set_type_imm | bind_type_pointer)
|
|
|
|
// Set segment and offset
|
|
info << (bind_opcode_set_segment_and_offset_uleb | data_seg_idx)
|
|
info << l.encode_uleb128_int(got_entry_offset)
|
|
|
|
// Do bind
|
|
info << bind_opcode_do_bind
|
|
got_entry_offset += 8
|
|
}
|
|
|
|
// Done
|
|
info << bind_opcode_done
|
|
|
|
return info
|
|
}
|
|
|
|
fn (l Linker) encode_uleb128_int(val int) []u8 {
|
|
mut result := []u8{}
|
|
if val < 0x80 {
|
|
result << u8(val)
|
|
return result
|
|
}
|
|
if val < 0x4000 {
|
|
result << (u8(val & 0x7f) | 0x80)
|
|
result << u8((val >> 7) & 0x7f)
|
|
return result
|
|
}
|
|
if val < 0x200000 {
|
|
result << (u8(val & 0x7f) | 0x80)
|
|
result << (u8((val >> 7) & 0x7f) | 0x80)
|
|
result << u8((val >> 14) & 0x7f)
|
|
return result
|
|
}
|
|
return l.encode_uleb128(u64(u32(val)))
|
|
}
|
|
|
|
fn (l Linker) encode_uleb128(val u64) []u8 {
|
|
mut result := []u8{}
|
|
mut v := val
|
|
for {
|
|
mut b := u8(v & 0x7f)
|
|
v >>= 7
|
|
if v != 0 {
|
|
b |= 0x80
|
|
}
|
|
result << b
|
|
if v == 0 {
|
|
break
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
fn (mut l Linker) write_text_with_relocations() {
|
|
// Copy text data
|
|
mut text := l.macho.text_data.clone()
|
|
|
|
// Build symbol address map
|
|
// Note: code section vmaddr = text_vmaddr + code_start
|
|
// Symbol values are offsets from segment start, so we use code_vmaddr for all __TEXT symbols
|
|
code_vmaddr := l.text_vmaddr + u64(l.code_start)
|
|
stubs_vmaddr := l.text_vmaddr + u64(l.stubs_offset)
|
|
|
|
// In the object file, __data section starts at text_len + cstring_len + alignment_padding
|
|
// We need to find the actual base address of data symbols (minimum symbol value in sect 3)
|
|
// to correctly compute offsets within the data_data array
|
|
mut data_base_addr := u64(0xFFFFFFFFFFFFFFFF) // Start with max, find minimum
|
|
for sym in l.macho.symbols {
|
|
if (sym.type_ & 0x0E) == 0x0E && sym.sect == 3 {
|
|
if sym.value < data_base_addr {
|
|
data_base_addr = sym.value
|
|
}
|
|
}
|
|
}
|
|
// If no data symbols, use section start
|
|
if data_base_addr == 0xFFFFFFFFFFFFFFFF {
|
|
data_base_addr = u64(l.macho.text_data.len + l.macho.str_data.len)
|
|
}
|
|
|
|
mut sym_addrs := map[int]u64{}
|
|
// Map symbol names to their defined addresses (for resolving undefined references)
|
|
mut sym_name_to_addr := map[string]u64{}
|
|
|
|
// First pass: collect all defined symbol addresses (except external syms)
|
|
for i, sym in l.macho.symbols {
|
|
// N_SECT (0x0E) means symbol is defined in a section
|
|
if (sym.type_ & 0x0E) == 0x0E {
|
|
// Skip external symbols - they should always resolve to libc
|
|
is_external := sym.name in force_external_syms
|
|
if sym.sect == 1 {
|
|
// Text section symbol (code)
|
|
addr := code_vmaddr + sym.value
|
|
sym_addrs[i] = addr
|
|
if !is_external {
|
|
sym_name_to_addr[sym.name] = addr
|
|
}
|
|
} else if sym.sect == 2 {
|
|
// Cstring section symbol
|
|
addr := code_vmaddr + sym.value
|
|
sym_addrs[i] = addr
|
|
if !is_external {
|
|
sym_name_to_addr[sym.name] = addr
|
|
}
|
|
} else if sym.sect == 3 {
|
|
// Data section symbol
|
|
// Subtract data base address to get offset within data_data array
|
|
addr := l.data_vmaddr + (sym.value - data_base_addr)
|
|
sym_addrs[i] = addr
|
|
if !is_external {
|
|
sym_name_to_addr[sym.name] = addr
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Second pass: handle external symbols and resolve undefined references to local symbols
|
|
for i, sym in l.macho.symbols {
|
|
if sym.type_ == 0x01 { // N_UNDF | N_EXT
|
|
// Check if this symbol is defined locally
|
|
if addr := sym_name_to_addr[sym.name] {
|
|
// Resolve to local definition
|
|
sym_addrs[i] = addr
|
|
} else if sym.name in l.sym_to_got {
|
|
// External symbol - address is in stub
|
|
got_idx := l.sym_to_got[sym.name]
|
|
sym_addrs[i] = stubs_vmaddr + u64(got_idx * 12)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Apply relocations
|
|
for r in l.macho.relocs {
|
|
// Check if this relocation references an external symbol
|
|
// If so, redirect it to use the stub instead of the local definition
|
|
sym_name := l.macho.symbols[r.sym_idx].name
|
|
mut sym_addr := sym_addrs[r.sym_idx]
|
|
if sym_addr == 0 && r.sym_idx !in sym_addrs {
|
|
eprintln('LINKER: unresolved symbol "${sym_name}" (idx=${r.sym_idx}) at text offset ${r.addr}')
|
|
exit(1)
|
|
}
|
|
if sym_name in force_external_syms {
|
|
// Use stub address for external symbols
|
|
if sym_name in l.sym_to_got {
|
|
got_idx := l.sym_to_got[sym_name]
|
|
sym_addr = stubs_vmaddr + u64(got_idx * 12)
|
|
}
|
|
}
|
|
pc := code_vmaddr + u64(r.addr)
|
|
|
|
match r.type_ {
|
|
arm64_reloc_branch26 {
|
|
// BL instruction: PC-relative branch
|
|
if sym_addr == 0 {
|
|
eprintln('LINKER: unresolved BL to "${sym_name}" (idx=${r.sym_idx}) at text offset ${r.addr}')
|
|
exit(1)
|
|
}
|
|
rel := i64(sym_addr) - i64(pc)
|
|
imm26 := (rel >> 2) & 0x3FFFFFF
|
|
instr := read_u32_le(text, r.addr)
|
|
new_instr := (instr & 0xFC000000) | u32(imm26)
|
|
write_u32_le_at_arr(mut text, r.addr, new_instr)
|
|
}
|
|
arm64_reloc_page21 {
|
|
// ADRP instruction: PC-relative page address
|
|
sym_page := i64(sym_addr) & ~0xFFF
|
|
pc_page := i64(pc) & ~0xFFF
|
|
page_off := (sym_page - pc_page) >> 12
|
|
|
|
immlo := u32(page_off & 0x3) << 29
|
|
immhi := u32((page_off >> 2) & 0x7FFFF) << 5
|
|
instr := read_u32_le(text, r.addr)
|
|
new_instr := (instr & 0x9F00001F) | immlo | immhi
|
|
write_u32_le_at_arr(mut text, r.addr, new_instr)
|
|
}
|
|
arm64_reloc_pageoff12 {
|
|
// ADD/LDR instruction: page offset
|
|
page_off := sym_addr & 0xFFF
|
|
instr := read_u32_le(text, r.addr)
|
|
|
|
// Check if this is ADD or LDR
|
|
if (instr & 0xFF800000) == 0x91000000 {
|
|
// ADD immediate
|
|
new_instr := (instr & 0xFFC003FF) | (u32(page_off) << 10)
|
|
write_u32_le_at_arr(mut text, r.addr, new_instr)
|
|
} else {
|
|
// LDR with scaled offset
|
|
// Determine scale from instruction encoding
|
|
scale := (instr >> 30) & 0x3
|
|
scaled_off := page_off >> scale
|
|
new_instr := (instr & 0xFFC003FF) | (u32(scaled_off) << 10)
|
|
write_u32_le_at_arr(mut text, r.addr, new_instr)
|
|
}
|
|
}
|
|
arm64_reloc_got_load_page21 {
|
|
// ADRP instruction: PC-relative page address to GOT entry
|
|
mut got_idx1 := 0
|
|
if idx := l.sym_to_got[sym_name] {
|
|
got_idx1 = idx
|
|
}
|
|
got_entry_addr1 := l.data_vmaddr + u64(l.got_offset) + u64(got_idx1 * 8)
|
|
|
|
got_page := i64(got_entry_addr1) & ~0xFFF
|
|
pc_page := i64(pc) & ~0xFFF
|
|
page_off := (got_page - pc_page) >> 12
|
|
|
|
immlo := u32(page_off & 0x3) << 29
|
|
immhi := u32((page_off >> 2) & 0x7FFFF) << 5
|
|
instr := read_u32_le(text, r.addr)
|
|
new_instr := (instr & 0x9F00001F) | immlo | immhi
|
|
write_u32_le_at_arr(mut text, r.addr, new_instr)
|
|
}
|
|
arm64_reloc_got_load_pageoff12 {
|
|
// LDR instruction: page offset to GOT entry
|
|
mut got_idx2 := 0
|
|
if idx := l.sym_to_got[sym_name] {
|
|
got_idx2 = idx
|
|
}
|
|
got_entry_addr2 := l.data_vmaddr + u64(l.got_offset) + u64(got_idx2 * 8)
|
|
|
|
page_off := got_entry_addr2 & 0xFFF
|
|
instr := read_u32_le(text, r.addr)
|
|
// LDR with scaled offset (8-byte scale for 64-bit load)
|
|
scaled_off := page_off >> 3
|
|
new_instr := (instr & 0xFFC003FF) | (u32(scaled_off) << 10)
|
|
write_u32_le_at_arr(mut text, r.addr, new_instr)
|
|
}
|
|
else {}
|
|
}
|
|
}
|
|
|
|
l.buf << text
|
|
}
|
|
|
|
fn (mut l Linker) write_stubs() {
|
|
// Generate stub for each external symbol
|
|
// Each stub: ADRP x16, GOT@PAGE; LDR x16, [x16, GOT@PAGEOFF]; BR x16
|
|
mut got_entry_offset := l.macho.data_data.len
|
|
for got_entry_offset % 8 != 0 {
|
|
got_entry_offset++
|
|
}
|
|
mut stub_offset := l.stubs_offset
|
|
for _ in l.extern_syms {
|
|
got_entry_addr := l.data_vmaddr + u64(u32(got_entry_offset))
|
|
stub_addr := l.text_vmaddr + u64(stub_offset)
|
|
|
|
// ADRP x16, got_entry@PAGE
|
|
got_page := i64(got_entry_addr) & ~0xFFF
|
|
stub_page := i64(stub_addr) & ~0xFFF
|
|
page_off := (got_page - stub_page) >> 12
|
|
immlo := u32(page_off & 0x3) << 29
|
|
immhi := u32((page_off >> 2) & 0x7FFFF) << 5
|
|
adrp := u32(0x90000010) | immlo | immhi
|
|
write_u32_le(mut l.buf, adrp)
|
|
|
|
// LDR x16, [x16, got_entry@PAGEOFF]
|
|
pageoff := (got_entry_addr & 0xFFF) >> 3 // Scale by 8 for 64-bit load
|
|
ldr := u32(0xF9400210) | (u32(pageoff) << 10)
|
|
write_u32_le(mut l.buf, ldr)
|
|
|
|
// BR x16
|
|
write_u32_le(mut l.buf, 0xD61F0200)
|
|
got_entry_offset += 8
|
|
stub_offset += 12
|
|
}
|
|
}
|
|
|
|
fn read_u32_le(data []u8, off int) u32 {
|
|
b0 := u32(data[off]) & u32(0xff)
|
|
b1 := (u32(data[off + 1]) & u32(0xff)) << 8
|
|
b2 := (u32(data[off + 2]) & u32(0xff)) << 16
|
|
b3 := (u32(data[off + 3]) & u32(0xff)) << 24
|
|
return b0 | b1 | b2 | b3
|
|
}
|
|
|
|
fn write_u32_le_at_arr(mut data []u8, off int, v u32) {
|
|
data[off] = u8(v)
|
|
data[off + 1] = u8(v >> 8)
|
|
data[off + 2] = u8(v >> 16)
|
|
data[off + 3] = u8(v >> 24)
|
|
}
|
|
|
|
fn write_u32_le_at(mut data []u8, off int, v u32) {
|
|
data[off] = u8(v)
|
|
data[off + 1] = u8(v >> 8)
|
|
data[off + 2] = u8(v >> 16)
|
|
data[off + 3] = u8(v >> 24)
|
|
}
|
|
|
|
// Big-endian write for code signature (Mach-O signatures use big-endian)
|
|
fn write_u32_be(mut b []u8, v u32) {
|
|
b << u8(v >> 24)
|
|
b << u8(v >> 16)
|
|
b << u8(v >> 8)
|
|
b << u8(v)
|
|
}
|
|
|
|
fn write_u64_be(mut b []u8, v u64) {
|
|
b << u8(v >> 56)
|
|
b << u8(v >> 48)
|
|
b << u8(v >> 40)
|
|
b << u8(v >> 32)
|
|
b << u8(v >> 24)
|
|
b << u8(v >> 16)
|
|
b << u8(v >> 8)
|
|
b << u8(v)
|
|
}
|
|
|
|
fn write_u64_le_at(mut b []u8, off int, v u64) {
|
|
b[off] = u8(v)
|
|
b[off + 1] = u8(v >> 8)
|
|
b[off + 2] = u8(v >> 16)
|
|
b[off + 3] = u8(v >> 24)
|
|
b[off + 4] = u8(v >> 32)
|
|
b[off + 5] = u8(v >> 40)
|
|
b[off + 6] = u8(v >> 48)
|
|
b[off + 7] = u8(v >> 56)
|
|
}
|
|
|
|
// Pad buffer to target size with zeros (efficient bulk write)
|
|
fn (mut l Linker) pad_to(target int) {
|
|
if l.buf.len >= target {
|
|
return
|
|
}
|
|
count := target - l.buf.len
|
|
for _ in 0 .. count {
|
|
l.buf << u8(0)
|
|
}
|
|
}
|
|
|
|
// Write n zero bytes (efficient)
|
|
fn (mut l Linker) write_zeros(n int) {
|
|
if n <= 0 {
|
|
return
|
|
}
|
|
for _ in 0 .. n {
|
|
l.buf << u8(0)
|
|
}
|
|
}
|
|
|
|
// Self-contained SHA-256 implementation. Zero heap allocations —
|
|
// uses fixed-size arrays and operates on raw pointers.
|
|
|
|
const sha256_k = [
|
|
u32(0x428a2f98),
|
|
0x71374491,
|
|
0xb5c0fbcf,
|
|
0xe9b5dba5,
|
|
0x3956c25b,
|
|
0x59f111f1,
|
|
0x923f82a4,
|
|
0xab1c5ed5,
|
|
0xd807aa98,
|
|
0x12835b01,
|
|
0x243185be,
|
|
0x550c7dc3,
|
|
0x72be5d74,
|
|
0x80deb1fe,
|
|
0x9bdc06a7,
|
|
0xc19bf174,
|
|
0xe49b69c1,
|
|
0xefbe4786,
|
|
0x0fc19dc6,
|
|
0x240ca1cc,
|
|
0x2de92c6f,
|
|
0x4a7484aa,
|
|
0x5cb0a9dc,
|
|
0x76f988da,
|
|
0x983e5152,
|
|
0xa831c66d,
|
|
0xb00327c8,
|
|
0xbf597fc7,
|
|
0xc6e00bf3,
|
|
0xd5a79147,
|
|
0x06ca6351,
|
|
0x14292967,
|
|
0x27b70a85,
|
|
0x2e1b2138,
|
|
0x4d2c6dfc,
|
|
0x53380d13,
|
|
0x650a7354,
|
|
0x766a0abb,
|
|
0x81c2c92e,
|
|
0x92722c85,
|
|
0xa2bfe8a1,
|
|
0xa81a664b,
|
|
0xc24b8b70,
|
|
0xc76c51a3,
|
|
0xd192e819,
|
|
0xd6990624,
|
|
0xf40e3585,
|
|
0x106aa070,
|
|
0x19a4c116,
|
|
0x1e376c08,
|
|
0x2748774c,
|
|
0x34b0bcb5,
|
|
0x391c0cb3,
|
|
0x4ed8aa4a,
|
|
0x5b9cca4f,
|
|
0x682e6ff3,
|
|
0x748f82ee,
|
|
0x78a5636f,
|
|
0x84c87814,
|
|
0x8cc70208,
|
|
0x90befffa,
|
|
0xa4506ceb,
|
|
0xbef9a3f7,
|
|
0xc67178f2,
|
|
]!
|
|
|
|
@[inline]
|
|
fn rotr32(x u32, n u32) u32 {
|
|
return (x >> n) | (x << (32 - n))
|
|
}
|
|
|
|
// sha256_hash_pages hashes a range of 16KB pages in a worker thread.
|
|
// Each thread writes 32-byte hashes into disjoint slots of the pre-allocated output buffer.
|
|
fn sha256_hash_pages(data &u8, mut hashes []u8, page_start int, page_end int, code_limit int) {
|
|
mut hash_buf := [32]u8{}
|
|
for page := page_start; page < page_end; page++ {
|
|
start := page * cs_page_size_arm64
|
|
mut end := start + cs_page_size_arm64
|
|
if end > code_limit {
|
|
end = code_limit
|
|
}
|
|
unsafe {
|
|
sha256_hash(data + start, end - start, &hash_buf[0])
|
|
}
|
|
hash_offset := page * 32
|
|
for i in 0 .. 32 {
|
|
hashes[hash_offset + i] = hash_buf[i]
|
|
}
|
|
}
|
|
}
|
|
|
|
// sha256_hash computes SHA-256 of data[0..data_len] into out[0..32].
|
|
// No heap allocations — uses fixed-size arrays on the stack.
|
|
@[direct_array_access]
|
|
fn sha256_hash(data &u8, data_len int, out_ptr &u8) {
|
|
mut state := [u32(0x6A09E667), 0xBB67AE85, 0x3C6EF372, 0xA54FF53A, 0x510E527F, 0x9B05688C,
|
|
0x1F83D9AB, 0x5BE0CD19]!
|
|
mut w := [64]u32{}
|
|
|
|
// Process complete 64-byte blocks directly from input
|
|
n_full_blocks := data_len / 64
|
|
for blk := 0; blk < n_full_blocks; blk++ {
|
|
off := blk * 64
|
|
for i in 0 .. 16 {
|
|
j := off + i * 4
|
|
unsafe {
|
|
w[i] = (u32(data[j]) << 24) | (u32(data[j + 1]) << 16) | (u32(data[j + 2]) << 8) | u32(data[
|
|
j + 3])
|
|
}
|
|
}
|
|
sha256_compress(&state[0], &w[0])
|
|
}
|
|
|
|
// Build final padded block(s): remaining data + 0x80 + zeros + 64-bit big-endian length
|
|
remaining := data_len - n_full_blocks * 64
|
|
mut pad := [128]u8{} // At most 2 final blocks
|
|
for i in 0 .. remaining {
|
|
unsafe {
|
|
pad[i] = data[n_full_blocks * 64 + i]
|
|
}
|
|
}
|
|
pad[remaining] = 0x80
|
|
|
|
// Need room for 8-byte length at end of last 64-byte block
|
|
mut pad_blocks := 1
|
|
if remaining >= 56 {
|
|
pad_blocks = 2
|
|
}
|
|
|
|
// Write bit length (big-endian u64) at end of last padding block
|
|
bit_len := u64(data_len) * 8
|
|
pad_end := pad_blocks * 64
|
|
pad[pad_end - 8] = u8(bit_len >> 56)
|
|
pad[pad_end - 7] = u8(bit_len >> 48)
|
|
pad[pad_end - 6] = u8(bit_len >> 40)
|
|
pad[pad_end - 5] = u8(bit_len >> 32)
|
|
pad[pad_end - 4] = u8(bit_len >> 24)
|
|
pad[pad_end - 3] = u8(bit_len >> 16)
|
|
pad[pad_end - 2] = u8(bit_len >> 8)
|
|
pad[pad_end - 1] = u8(bit_len)
|
|
|
|
for blk in 0 .. pad_blocks {
|
|
off := blk * 64
|
|
for i in 0 .. 16 {
|
|
j := off + i * 4
|
|
w[i] = (u32(pad[j]) << 24) | (u32(pad[j + 1]) << 16) | (u32(pad[j + 2]) << 8) | u32(pad[
|
|
j + 3])
|
|
}
|
|
sha256_compress(&state[0], &w[0])
|
|
}
|
|
|
|
// Write result big-endian
|
|
mut out := unsafe { &u8(out_ptr) }
|
|
for i in 0 .. 8 {
|
|
unsafe {
|
|
out[i * 4] = u8(state[i] >> 24)
|
|
out[i * 4 + 1] = u8(state[i] >> 16)
|
|
out[i * 4 + 2] = u8(state[i] >> 8)
|
|
out[i * 4 + 3] = u8(state[i])
|
|
}
|
|
}
|
|
}
|
|
|
|
@[direct_array_access]
|
|
fn sha256_compress(state_ptr &u32, w_ptr &u32) {
|
|
mut state := unsafe { &u32(state_ptr) }
|
|
mut w := unsafe { &u32(w_ptr) }
|
|
// Extend the first 16 words into the remaining 48
|
|
for i := 16; i < 64; i++ {
|
|
unsafe {
|
|
s0 := rotr32(w[i - 15], 7) ^ rotr32(w[i - 15], 18) ^ (w[i - 15] >> 3)
|
|
s1 := rotr32(w[i - 2], 17) ^ rotr32(w[i - 2], 19) ^ (w[i - 2] >> 10)
|
|
w[i] = w[i - 16] + s0 + w[i - 7] + s1
|
|
}
|
|
}
|
|
|
|
mut a := unsafe { state[0] }
|
|
mut b := unsafe { state[1] }
|
|
mut c := unsafe { state[2] }
|
|
mut d := unsafe { state[3] }
|
|
mut e := unsafe { state[4] }
|
|
mut f := unsafe { state[5] }
|
|
mut g := unsafe { state[6] }
|
|
mut h := unsafe { state[7] }
|
|
|
|
for i in 0 .. 64 {
|
|
s1 := rotr32(e, 6) ^ rotr32(e, 11) ^ rotr32(e, 25)
|
|
ch := (e & f) ^ (~e & g)
|
|
t1 := h + s1 + ch + sha256_k[i] + unsafe { w[i] }
|
|
s0 := rotr32(a, 2) ^ rotr32(a, 13) ^ rotr32(a, 22)
|
|
maj := (a & b) ^ (a & c) ^ (b & c)
|
|
t2 := s0 + maj
|
|
|
|
h = g
|
|
g = f
|
|
f = e
|
|
e = d + t1
|
|
d = c
|
|
c = b
|
|
b = a
|
|
a = t1 + t2
|
|
}
|
|
|
|
unsafe {
|
|
state[0] += a
|
|
state[1] += b
|
|
state[2] += c
|
|
state[3] += d
|
|
state[4] += e
|
|
state[5] += f
|
|
state[6] += g
|
|
state[7] += h
|
|
}
|
|
}
|