- patch_nso.py: three more x18 forms from IL2CPP code: an offset jump table (targets read at rewrite time), blr x18 (original return address kept for C++ unwinding) and an sp write from x18 (alloca). Mario's rewrite output stays byte-identical. - loader.c: symbols resolve globally in load order, also for a module's references to its own exports (local / non-default visibility excepted). Engage's main overrides operator new / delete; sdk bound its own copies and freed Unity's blocks into the SDK heap. - hle_svc.c: QueryMemory reports each module's Horizon layout (code, read-only, data, patch area) and other host memory as free, which the SDK's C++ unwinder (nn::ro::detail::GetExceptionInfo) needs. - New takeovers: nn::fs::IsMounted, nn::ro (hle_ro.c), prepo Initialize, SDK 12 audio device names and OpenDefaultAudioOut, an SDK 12 hid block (single GetNpadState, modes, touch, gestures, six-axis details, colors, applets), movie::Decoder with no impl (hle_movie.c) and failing nvdrv opens (hle_nvdrv.c), and NVN getters / no-ops Engage calls. nvn.h: TextureGetMemoryPool returns a pointer. - Makefile: a game stamp, so changing GAME rebuilds the host. - docs/agents/03-bringup.md: catalogue entries for all of the above. - Mario 1.2.1 scripted run still reaches the opening course with the same single red line.
413 lines
30 KiB
C
413 lines
30 KiB
C
// 多模块装载(扮演 Horizon 的 rtld):映射、占线程槽、门登记、跨模块重定位、按名接管、段权限。
|
||
// 代码区(原 text 与补丁区)一拷进来就设成只读+执行,此后装载器只写 data:iOS 上这两块要作为签名代码段原样内嵌。
|
||
// Multi-module loading (playing the role of Horizon's rtld): mapping, claiming thread slots, gate registration, cross-module relocation, take-over by name, segment permissions.
|
||
// The code area (original text and patch area) is set to read-only+execute as soon as it is copied in, and from then on the loader only writes data: on iOS these two blocks must be embedded as is as signed code segments.
|
||
#include "dm.h"
|
||
#include <TargetConditionals.h>
|
||
#include <fcntl.h>
|
||
#include <sys/mman.h>
|
||
#include <sys/stat.h>
|
||
#include <unistd.h>
|
||
#include <mach-o/getsect.h>
|
||
#include <mach-o/ldsyms.h>
|
||
#include "hle.h"
|
||
|
||
module_t g_mod[MAX_MOD];
|
||
int g_nmod;
|
||
pthread_key_t g_key18, g_keytls, g_keytlr, g_keygate;
|
||
pthread_key_t g_keyres[2], g_keyaspect, g_keyaspectui; // g_keyaspect:宽高比上限参数位(KASPECT) // 分辨率参数位(改写器 PARAMS,KRES0 掌机 / KRES1 主机);产物没有即 0、不占 | g_keyaspect: aspect ratio upper-limit parameter slot (KASPECT) // resolution parameter slots (rewriter PARAMS, KRES0 handheld / KRES1 docked); 0 and not allocated if the build output lacks them
|
||
enum { KEY_K18, KEY_KTLS, KEY_KTLR, KEY_KGATE, KEY_KRES0, KEY_KRES1, KEY_KASPECT, KEY_KASPECTUI, KEY_N };
|
||
|
||
uint64_t resolve_import(module_t *m, const char *name);
|
||
void gate_register(uint64_t ret_addr, const char *kind, int arg, uint64_t orig);
|
||
|
||
static pthread_key_t key_value(unsigned i) {
|
||
switch (i) {
|
||
case KEY_K18: return g_key18; case KEY_KTLS: return g_keytls; case KEY_KTLR: return g_keytlr;
|
||
case KEY_KGATE: return g_keygate; case KEY_KRES0: return g_keyres[0]; case KEY_KRES1: return g_keyres[1];
|
||
case KEY_KASPECT: return g_keyaspect; default: return g_keyaspectui;
|
||
}
|
||
}
|
||
static void create_keys(void) {
|
||
if (pthread_key_create(&g_key18, NULL) || pthread_key_create(&g_keytls, NULL) ||
|
||
pthread_key_create(&g_keytlr, NULL) || pthread_key_create(&g_keygate, NULL)) die("创建宿主键失败");
|
||
// 参数位键一律建(不按产物有没有用到判断:iOS 包里没有 params.tsv,按文件判断会漏建、键页写成 0 → 游戏读到 TSD 0 号槽)
|
||
// Parameter slot keys are always created (not decided by whether the build output uses them: an iOS app has no params.tsv, so deciding by file would miss creating them and the key page would be written as 0 → the game reads TSD slot 0)
|
||
if (pthread_key_create(&g_keyres[0], NULL) || pthread_key_create(&g_keyres[1], NULL) ||
|
||
pthread_key_create(&g_keyaspect, NULL) || pthread_key_create(&g_keyaspectui, NULL)) die("创建参数位键失败");
|
||
LOG("键号 K18=%lu KTLS=%lu KTLR=%lu KGATE=%lu KRES0=%lu KRES1=%lu KASPECT=%lu KASPECTUI=%lu",
|
||
g_key18, g_keytls, g_keytlr, g_keygate, g_keyres[0], g_keyres[1], g_keyaspect, g_keyaspectui);
|
||
}
|
||
|
||
// K 槽校验:pthread TSD 布局固定,槽 = tpidrro_el0 + 8*key
|
||
// K slot check: the pthread TSD layout is fixed, slot = tpidrro_el0 + 8*key
|
||
static void tsd_check(void) {
|
||
pthread_setspecific(g_key18, (void *)0x1234);
|
||
uint64_t base; __asm__ volatile("mrs %0, tpidrro_el0" : "=r"(base));
|
||
uint64_t off = *(uint64_t *)(g_mod[0].base + g_mod[0].key_page_off + g_mod[0].key_idx[KEY_K18] * 8);
|
||
if (((uint64_t *)(base & ~7ull))[off / 8] != 0x1234) die("TSD 布局不符");
|
||
pthread_setspecific(g_key18, 0);
|
||
}
|
||
|
||
static void build_symhash(module_t *m);
|
||
static void parse_dynamic(module_t *m) {
|
||
uint32_t mod0 = *(uint32_t *)(m->base + 4);
|
||
if (memcmp(m->base + mod0, "MOD0", 4)) die("%s: no MOD0", m->name);
|
||
m->dyn = (const Elf64_Dyn *)(m->base + mod0 + *(int32_t *)(m->base + mod0 + 4));
|
||
for (const Elf64_Dyn *d = m->dyn; d->d_tag; d++) switch (d->d_tag) {
|
||
case DT_SYMTAB: m->symtab = (const Elf64_Sym *)(m->base + d->d_un.d_ptr); break;
|
||
case DT_STRTAB: m->strtab = (const char *)(m->base + d->d_un.d_ptr); break;
|
||
case DT_INIT: m->init = d->d_un.d_ptr; break;
|
||
case DT_INIT_ARRAY: m->init_array = d->d_un.d_ptr; break;
|
||
case DT_INIT_ARRAYSZ: m->init_arraysz = d->d_un.d_val; break;
|
||
}
|
||
m->nsym = (unsigned)(((const uint8_t *)m->strtab - (const uint8_t *)m->symtab) / sizeof(Elf64_Sym));
|
||
build_symhash(m);
|
||
}
|
||
|
||
static void *module_sym_linear(module_t *m, const char *name) {
|
||
for (unsigned i = 1; i < m->nsym; i++) {
|
||
const Elf64_Sym *s = &m->symtab[i];
|
||
if (s->st_shndx && !strcmp(m->strtab + s->st_name, name)) return m->base + s->st_value;
|
||
}
|
||
return NULL;
|
||
}
|
||
// 符号名哈希表:按下标升序插入、同名已在则不覆盖 ⇒ 查询结果与线性扫描(首个已定义同名符号)逐条相同。
|
||
// ① 宿主装载 91–99 ms 里重定位 + 强制接管占 77–86 ms,主因是每条按名解析的重定位都线性扫两个模块的符号表
|
||
// Symbol name hash table: inserted in ascending index order, not overwritten if the same name is already there ⇒ lookup results are identical, entry by entry, to a linear scan (the first defined symbol with that name).
|
||
// Of the 91–99 ms of ① host loading, relocation + forced take-over took 77–86 ms, mainly because every by-name relocation scanned the symbol tables of two modules linearly
|
||
static uint32_t sym_hash(const char *s) { uint32_t h = 2166136261u; while (*s) h = (h ^ (uint8_t)*s++) * 16777619u; return h; }
|
||
static void build_symhash(module_t *m) {
|
||
unsigned cap = 16; while (cap < m->nsym * 2) cap <<= 1;
|
||
m->symh = calloc(cap, sizeof *m->symh); m->symh_mask = cap - 1;
|
||
for (unsigned i = 1; i < m->nsym; i++) {
|
||
const Elf64_Sym *s = &m->symtab[i]; if (!s->st_shndx) continue;
|
||
const char *nm = m->strtab + s->st_name;
|
||
for (unsigned j = sym_hash(nm) & m->symh_mask;; j = (j + 1) & m->symh_mask) {
|
||
if (!m->symh[j]) { m->symh[j] = i; break; }
|
||
if (!strcmp(m->strtab + m->symtab[m->symh[j]].st_name, nm)) break; // 同名已有更小下标:保留它 | The same name already exists with a smaller index: keep that one
|
||
}
|
||
}
|
||
}
|
||
static unsigned long g_symchk_n, g_symchk_bad;
|
||
void *module_sym(module_t *m, const char *name) {
|
||
unsigned long nq; (void)nq;
|
||
if (!m->symh) return module_sym_linear(m, name);
|
||
void *r = NULL;
|
||
for (unsigned j = sym_hash(name) & m->symh_mask; m->symh[j]; j = (j + 1) & m->symh_mask) {
|
||
const Elf64_Sym *s = &m->symtab[m->symh[j]];
|
||
if (!strcmp(m->strtab + s->st_name, name)) { r = m->base + s->st_value; break; }
|
||
}
|
||
static int chk = -1; if (chk < 0) chk = getenv("DM_SYMCHECK") != NULL;
|
||
if (chk) { void *l = module_sym_linear(m, name); g_symchk_n++; nq = g_symchk_n;
|
||
if (l != r) { g_symchk_bad++; LOG("【红】DM_SYMCHECK:%s 在 %s 哈希 %p ≠ 线性 %p", name, m->name, r, l); } }
|
||
return r;
|
||
}
|
||
void *guest_sym(const char *name) {
|
||
for (int i = 0; i < g_nmod; i++) { void *p = module_sym(&g_mod[i], name); if (p) return p; }
|
||
return NULL;
|
||
}
|
||
module_t *module_of(const void *p) {
|
||
for (int i = 0; i < g_nmod; i++)
|
||
if ((const uint8_t *)p >= g_mod[i].base && (const uint8_t *)p < g_mod[i].base + g_mod[i].size) return &g_mod[i];
|
||
return NULL;
|
||
}
|
||
|
||
// 跨模块符号解析:先在已装模块里找定义,找不到再交给 HLE / 桩
|
||
// Cross-module symbol resolution: first look for a definition in the loaded modules, and only if not found hand over to HLE / stubs
|
||
static uint64_t resolve_sym(module_t *m, const char *name) {
|
||
for (int i = 0; i < g_nmod; i++) { void *p = module_sym(&g_mod[i], name); if (p) return (uint64_t)p; }
|
||
return resolve_import(m, name);
|
||
}
|
||
|
||
// 强制接管表(hle.h HLE_FORCE,__DATA,swforce 段):命中即覆盖模块内定义
|
||
// Forced take-over table (hle.h HLE_FORCE, the __DATA,swforce section): a hit overrides the definition inside the module
|
||
static const hle_force_ent *g_force; static unsigned g_nforce; static unsigned *g_force_hits;
|
||
static void force_init(void) {
|
||
unsigned long sz; g_force = (const hle_force_ent *)getsectiondata(&_mh_execute_header, "__DATA", "swforce", &sz);
|
||
g_nforce = g_force ? (unsigned)(sz / sizeof *g_force) : 0;
|
||
g_force_hits = calloc(g_nforce + 1, sizeof *g_force_hits);
|
||
for (unsigned i = 0; i < g_nforce; i++)
|
||
for (unsigned j = i + 1; j < g_nforce; j++)
|
||
if (!strcmp(g_force[i].name, g_force[j].name)) die("强制接管清单重复:%s", g_force[i].name);
|
||
}
|
||
static void *force_lookup(const char *name) {
|
||
for (unsigned i = 0; i < g_nforce; i++)
|
||
if (!strcmp(g_force[i].name, name)) {
|
||
if (!g_force_hits[i]++ && g_flag.imports) LOG("force %s (%s)", name, g_force[i].why);
|
||
void *hlecount_wrap(const char *, const char *, void *); return hlecount_wrap("FORCE", name, g_force[i].fn); // DM_HLECOUNT 时为计数桩 | A counting stub when DM_HLECOUNT is set
|
||
}
|
||
return NULL;
|
||
}
|
||
// 通用宿主:清单服务多个游戏,某作没引用的条目只记一笔;DM_FORCE_STRICT=1 时照旧报红中止(核对清单写错用)
|
||
// Generic host: the list serves several games, so entries a game does not reference are only noted; with DM_FORCE_STRICT=1 it still fails loudly (for checking a mistyped list)
|
||
static void force_check(void) {
|
||
unsigned unused = 0; int strict = getenv("DM_FORCE_STRICT") != NULL;
|
||
for (unsigned i = 0; i < g_nforce; i++)
|
||
if (!g_force_hits[i]) { if (strict || g_flag.imports) LOG("强制接管条目无任何引用:%s", g_force[i].name); unused++; }
|
||
if (unused && strict) die("强制接管清单有误");
|
||
LOG("force: %u 条命中,%u 条本作没引用", g_nforce - unused, unused);
|
||
}
|
||
|
||
// 读改写器表头(段边界、槽号);表头以外有任何行即旧版产物(装载期改代码),拒绝
|
||
// Read the rewriter table header (segment boundaries, slot numbers); any row beyond the header means an old-format build output (code modified at load time), which is rejected
|
||
static void read_layout(module_t *m, const char *dir) {
|
||
char path[1024]; snprintf(path, sizeof path, "%s/%s.fixups.tsv", dir, m->name);
|
||
FILE *f = fopen(path, "r");
|
||
if (!f) die("open %s", path);
|
||
static const char *kn[8] = {"K18", "KTLS", "KTLR", "KGATE", "KRES0", "KRES1", "KASPECT", "KASPECTUI"};
|
||
char line[256], key[32]; unsigned long v; unsigned seen = 0;
|
||
while (fgets(line, sizeof line, f)) {
|
||
if (sscanf(line, "# %31s %lx", key, &v) != 2) die("%s: 产物是旧改写器出的,换成当前代号(dm/PRODUCT)的产物", path);
|
||
if (!strcmp(key, "PATCH_OFF")) m->patch_off = v;
|
||
else if (!strcmp(key, "KEY_PAGE_OFF")) m->key_page_off = v;
|
||
else if (!strcmp(key, "KEY_PAGE_SIZE")) m->key_page_size = v;
|
||
else if (!strcmp(key, "TEXT_END")) m->text_end = v;
|
||
else if (!strcmp(key, "RO_OFF")) m->ro_off = v;
|
||
else if (!strcmp(key, "DATA_OFF")) m->data_off = v;
|
||
else if (!strcmp(key, "BUILD_ID")) sscanf(line, "# %*s %32s", m->build_id);
|
||
else for (int k = 0; k < 8; k++) {
|
||
size_t n = strlen(kn[k]);
|
||
if (!strncmp(key, "KEY_", 4) && !strncmp(key + 4, kn[k], n) && !strcmp(key + 4 + n, "_IDX")) {
|
||
m->key_idx[k] = (unsigned)strtoul(strrchr(line, '\t') + 1, 0, 10); seen |= 1u << k;
|
||
}
|
||
}
|
||
}
|
||
fclose(f);
|
||
if (!m->patch_off || m->key_page_off % 0x4000 || m->key_page_size != 0x4000 || seen != 0xff)
|
||
die("%s: 产物是旧改写器出的,换成当前代号(dm/PRODUCT)的产物", path);
|
||
}
|
||
|
||
static void install_key_page(module_t *m) {
|
||
if (m->key_page_off + m->key_page_size > m->size) die("%s: 键页越界", m->name);
|
||
uint64_t *page = (uint64_t *)(m->base + m->key_page_off);
|
||
memset(page, 0, m->key_page_size);
|
||
for (unsigned i = 0; i < 4; i++) page[m->key_idx[i]] = (uint64_t)key_value(i) * 8;
|
||
page[m->key_idx[KEY_KRES0]] = (uint64_t)g_keyres[0] * 8; page[m->key_idx[KEY_KRES1]] = (uint64_t)g_keyres[1] * 8;
|
||
page[m->key_idx[KEY_KASPECT]] = (uint64_t)g_keyaspect * 8; page[m->key_idx[KEY_KASPECTUI]] = (uint64_t)g_keyaspectui * 8;
|
||
for (int k = 0; k < 8; k++) if (!page[m->key_idx[k]]) die("%s: 键页第 %d 项为 0(宿主键没建)——游戏会读到 TSD 0 号槽", m->name, k);
|
||
if (mprotect(m->base + m->key_page_off, m->key_page_size, PROT_READ)) die("%s 键页 mprotect", m->name);
|
||
}
|
||
|
||
static void register_gates(module_t *m, const char *dir) {
|
||
char path[1024], line[256]; snprintf(path, sizeof path, "%s/%s.gates.tsv", dir, m->name);
|
||
FILE *f = fopen(path, "r");
|
||
int ng = 0;
|
||
if (f) {
|
||
unsigned long ret, orig; char kind[16]; int arg;
|
||
while (fgets(line, sizeof line, f))
|
||
if (sscanf(line, "%lx %15s %d %lx", &ret, kind, &arg, &orig) == 4) { gate_register((uint64_t)m->base + ret, kind, arg, orig); ng++; }
|
||
fclose(f);
|
||
}
|
||
LOG("%s: gates %d patch_off %#llx keys %lu/%lu/%lu/%lu", m->name, ng, m->patch_off, g_key18, g_keytls, g_keytlr, g_keygate);
|
||
}
|
||
|
||
static void relocate(module_t *m) {
|
||
uint64_t rela = 0, relasz = 0, jmprel = 0, pltsz = 0;
|
||
for (const Elf64_Dyn *d = m->dyn; d->d_tag; d++) switch (d->d_tag) {
|
||
case DT_RELA: rela = d->d_un.d_ptr; break;
|
||
case DT_RELASZ: relasz = d->d_un.d_val; break;
|
||
case DT_JMPREL: jmprel = d->d_un.d_ptr; break;
|
||
case DT_PLTRELSZ: pltsz = d->d_un.d_val; break;
|
||
}
|
||
int cnt[3] = {0}, nint = 0;
|
||
const Elf64_Rela *tabs[2] = {(const Elf64_Rela *)(m->base + rela), (const Elf64_Rela *)(m->base + jmprel)};
|
||
uint64_t sizes[2] = {relasz, pltsz};
|
||
for (int t = 0; t < 2; t++)
|
||
for (uint64_t i = 0; i < sizes[t] / sizeof(Elf64_Rela); i++) {
|
||
const Elf64_Rela *r = &tabs[t][i];
|
||
uint64_t *where = (uint64_t *)(m->base + r->r_offset);
|
||
unsigned type = ELF64_R_TYPE(r->r_info), si = ELF64_R_SYM(r->r_info);
|
||
if (type == R_AARCH64_RELATIVE) { *where = (uint64_t)m->base + r->r_addend; cnt[0]++; continue; }
|
||
const Elf64_Sym *s = &m->symtab[si];
|
||
uint64_t v = (uint64_t)force_lookup(m->strtab + s->st_name);
|
||
// 本模块自己有定义的符号也按装载顺序全局查(ELF 缺省语义,Horizon 的 rtld 同样):先装的模块的定义覆盖后装的,局部 / 非缺省可见性的才绑自己。
|
||
// Engage 的 main 自定义 operator new / delete(Unity 的内存管理器),sdk 也有一份(转 malloc / free):原先 sdk 绑自己的 delete,
|
||
// main 的 new 出来的块被 sdk 里 libc++ 的 string 释放进 SDK 堆,CentralHeap::Free 报 NotNMallocPtr
|
||
// Symbols the module defines itself are also looked up globally in load order (the ELF default; Horizon's rtld does the same): the definition of an earlier-loaded module overrides a later one; only local / non-default visibility binds to itself.
|
||
// Engage's main defines its own operator new / delete (Unity's memory manager) and sdk has a copy too (forwarding to malloc / free): previously sdk bound its own delete,
|
||
// so blocks from main's new were freed by sdk's libc++ string code into the SDK heap, and CentralHeap::Free reported NotNMallocPtr
|
||
if (!v && s->st_shndx) {
|
||
uint64_t self = (uint64_t)m->base + s->st_value;
|
||
v = ELF64_ST_BIND(s->st_info) == STB_LOCAL || ELF64_ST_VISIBILITY(s->st_other) != STV_DEFAULT ? self : resolve_sym(m, m->strtab + s->st_name);
|
||
if (!v) v = self;
|
||
if (v != self && nint++ < 8) LOG("%s: %s 绑到先装模块的定义(%s) | bound to an earlier module's definition", m->name, m->strtab + s->st_name, module_of((void *)v) ? module_of((void *)v)->name : "?");
|
||
}
|
||
if (!v) v = resolve_sym(m, m->strtab + s->st_name);
|
||
if (type == R_AARCH64_JUMP_SLOT) { *where = v; cnt[2]++; }
|
||
else { *where = v + r->r_addend; cnt[1]++; } // ABS64 / GLOB_DAT
|
||
}
|
||
LOG("%s: reloc relative=%d abs/glob=%d jump=%d%s", m->name, cnt[0], cnt[1], cnt[2], nint ? "(含绑到先装模块的定义) | (some bound to an earlier module's definition)" : "");
|
||
if (nint > 8) LOG("%s: 共 %d 处绑到先装模块的定义 | %d bound to an earlier module's definition in all", m->name, nint, nint);
|
||
}
|
||
|
||
// DM_LOADHASH=1(诊断):装载完逐模块出地址无关 sha256,证「提前起跑 / 不提前」装载结果逐字节相同。
|
||
// 模块基址与宿主 ASLR 每局不同,只有重定位槽的值随之变:复制映像,按同一张重定位表把每个槽换成与地址无关的记号——
|
||
// RELATIVE → (0, 加数);符号目标落在某模块 → (1 + 模块号, 模块内偏移);否则(宿主强制接管 / 宿主符号)→ (名字 FNV, 加数);其余字节原样。
|
||
// DM_LOADHASH=1 (diagnostics): after loading, output an address-independent sha256 per module, to prove that the load results of "early start / no early start" are byte-identical.
|
||
// Module base addresses and host ASLR differ every run, and only the values of the relocation slots change with them: copy the image and, using the same relocation table, replace every slot with an address-independent token --
|
||
// RELATIVE → (0, addend); symbol target falls in some module → (1 + module number, offset within the module); otherwise (host forced take-over / host symbol) → (name FNV, addend); all other bytes as is.
|
||
#include <CommonCrypto/CommonDigest.h>
|
||
void loader_hash_log(void) {
|
||
if (!getenv("DM_LOADHASH")) return;
|
||
for (int k = 0; k < g_nmod; k++) {
|
||
module_t *m = &g_mod[k];
|
||
uint8_t *cp = malloc(m->size); memcpy(cp, m->base, m->size);
|
||
uint64_t rela = 0, relasz = 0, jmprel = 0, pltsz = 0;
|
||
for (const Elf64_Dyn *d = m->dyn; d->d_tag; d++) switch (d->d_tag) {
|
||
case DT_RELA: rela = d->d_un.d_ptr; break; case DT_RELASZ: relasz = d->d_un.d_val; break;
|
||
case DT_JMPREL: jmprel = d->d_un.d_ptr; break; case DT_PLTRELSZ: pltsz = d->d_un.d_val; break;
|
||
}
|
||
const Elf64_Rela *tabs[2] = {(const Elf64_Rela *)(m->base + rela), (const Elf64_Rela *)(m->base + jmprel)};
|
||
uint64_t sizes[2] = {relasz, pltsz}; unsigned nslot = 0;
|
||
for (int t = 0; t < 2; t++)
|
||
for (uint64_t i = 0; i < sizes[t] / sizeof(Elf64_Rela); i++) {
|
||
const Elf64_Rela *r = &tabs[t][i]; uint64_t tok[1] = {0};
|
||
if (ELF64_R_TYPE(r->r_info) != R_AARCH64_RELATIVE) {
|
||
uint64_t v = *(const uint64_t *)(m->base + r->r_offset); int hit = 0;
|
||
for (int j = 0; j < g_nmod; j++) if (v >= (uint64_t)g_mod[j].base && v < (uint64_t)g_mod[j].base + g_mod[j].size) { tok[0] = 1 + j; hit = 1; break; }
|
||
if (!hit) { const char *nm = m->strtab + m->symtab[ELF64_R_SYM(r->r_info)].st_name; uint64_t h = 0xcbf29ce484222325ull;
|
||
for (; *nm; nm++) h = (h ^ (uint8_t)*nm) * 0x100000001b3ull; tok[0] = h | 1ull << 63; }
|
||
}
|
||
memcpy(cp + r->r_offset, &tok[0], 8); // 槽 8 字节换成记号头;记号尾在下面按槽序追加 | The 8 slot bytes are replaced by the token head; the token tail is appended below in slot order
|
||
nslot++;
|
||
}
|
||
CC_SHA256_CTX c; CC_SHA256_Init(&c); CC_SHA256_Update(&c, cp, (CC_LONG)m->size);
|
||
for (int t = 0; t < 2; t++) // 记号尾(加数 / 偏移)按槽序追加,与槽头一起唯一确定槽值 | Token tails (addend / offset) are appended in slot order, and together with the slot head uniquely determine the slot value
|
||
for (uint64_t i = 0; i < sizes[t] / sizeof(Elf64_Rela); i++) {
|
||
const Elf64_Rela *r = &tabs[t][i]; uint64_t tail = (uint64_t)r->r_addend;
|
||
if (ELF64_R_TYPE(r->r_info) != R_AARCH64_RELATIVE) {
|
||
uint64_t v = *(const uint64_t *)(m->base + r->r_offset);
|
||
for (int j = 0; j < g_nmod; j++) if (v >= (uint64_t)g_mod[j].base && v < (uint64_t)g_mod[j].base + g_mod[j].size) { tail = v - (uint64_t)g_mod[j].base; break; }
|
||
}
|
||
CC_SHA256_Update(&c, &tail, 8);
|
||
}
|
||
uint8_t d[32]; CC_SHA256_Final(d, &c); free(cp);
|
||
char hx[65]; for (int i = 0; i < 32; i++) sprintf(hx + 2 * i, "%02x", d[i]);
|
||
LOG("DM_LOADHASH %s 大小 %#zx 重定位槽 %u sha256 %s", m->name, m->size, nslot, hx);
|
||
}
|
||
}
|
||
|
||
#if TARGET_OS_IPHONE
|
||
// iOS 不许运行时装入的代码执行,且苹果只许可执行文件有一个带执行权限的段(ITMS-90999):映像由 dm/ios/tools/gen_embed.py 编进本可执行文件——
|
||
// 代码两块(原 text、补丁区)是 __TEXT 里的节,W(ro 余部 + data + bss)是单独的 RW 段。三块在文件里不相邻;这里保留一块连续地址,
|
||
// 按原相对位置 vm_remap 回去:代码页 copy=FALSE(与 __TEXT 共享同一批已签名页,r-x),W copy=TRUE(写时复制,用到才分页)。
|
||
// 运行时布局与原映像逐字节相同。iOS 上把已签名页 remap 到新地址再执行,libffi 的跳板表(FFI_EXEC_TRAMPOLINE_TABLE)就是这么做的
|
||
// iOS does not allow code loaded at run time to execute, and Apple allows an executable only one segment with execute permission (ITMS-90999): the image is compiled into this executable by dm/ios/tools/gen_embed.py --
|
||
// the two code blocks (original text, patch area) are sections in __TEXT, and W (the rest of ro + data + bss) is a separate RW segment. The three blocks are not adjacent in the file; here one contiguous address range is reserved,
|
||
// and they are vm_remap'ed back at their original relative positions: code pages with copy=FALSE (sharing the same signed pages with __TEXT, r-x), W with copy=TRUE (copy-on-write, paged only when used).
|
||
// The run-time layout is byte-identical to the original image. Remapping signed pages to a new address and then executing them on iOS is exactly what libffi's trampoline table (FFI_EXEC_TRAMPOLINE_TABLE) does
|
||
#include <mach/mach.h>
|
||
static void remap_at(module_t *m, vm_address_t to, const void *from, unsigned long size, boolean_t copy, vm_prot_t need) {
|
||
vm_address_t a = to; vm_prot_t cur = 0, max = 0;
|
||
kern_return_t kr = vm_remap(mach_task_self(), &a, size, 0, VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE, mach_task_self(), (vm_address_t)from, copy, &cur, &max, VM_INHERIT_NONE);
|
||
if (kr != KERN_SUCCESS || a != to) die("%s vm_remap %p → %#lx(%#lx 字节)失败:%d", m->name, from, (unsigned long)to, size, kr);
|
||
if ((cur & need) != need) die("%s vm_remap 后权限 %#x,缺 %#x", m->name, cur, need);
|
||
}
|
||
#ifdef DM_SHELL
|
||
// 壳(ios/tools/shell_build.sh 出的、不含任何游戏内容的 App):模块映像不在主可执行文件里,在使用者自己用游戏文件做出来的
|
||
// Frameworks/Guest.framework/Guest 里——一个只装段的动态库(dm/tools/guest_dylib.py 写出),节名与内嵌做法相同,下面只换一个映像头去取。
|
||
// Shell (the App built by ios/tools/shell_build.sh, containing no game content at all): the module images are not in the main executable but in the
|
||
// Frameworks/Guest.framework/Guest that users build themselves from their game files -- a dynamic library that holds only segments (written by dm/tools/guest_dylib.py), with the same section names as the embedded approach; below only the image header to fetch from is swapped.
|
||
#include <dlfcn.h>
|
||
#include <mach-o/dyld.h>
|
||
static const struct mach_header_64 *image_mh(void) {
|
||
static const struct mach_header_64 *mh; if (mh) return mh;
|
||
char exe[4096]; uint32_t n = sizeof exe; if (_NSGetExecutablePath(exe, &n)) die("取不到可执行文件路径");
|
||
char *sl = strrchr(exe, '/'); if (!sl || (size_t)(sl - exe) + 40 > sizeof exe) die("可执行文件路径不对:%s", exe); strcpy(sl, "/Frameworks/Guest.framework/Guest");
|
||
if (!dlopen(exe, RTLD_NOW)) die("这个安装包里还没有游戏程序(%s):先用 dm/tools/d2rm_pack.py 把你自己的游戏文件做进安装包再装。系统的说法:%s", exe, dlerror());
|
||
for (uint32_t i = 0; i < _dyld_image_count(); i++) if (strstr(_dyld_get_image_name(i), "/Guest.framework/Guest")) mh = (const struct mach_header_64 *)_dyld_get_image_header(i);
|
||
if (!mh) die("Guest 动态库已加载但找不到它的映像头");
|
||
fprintf(stderr, "boot: 模块映像取自 Guest 动态库 %s\n", exe); return mh;
|
||
}
|
||
#define IMAGE_MH image_mh()
|
||
#else
|
||
#define IMAGE_MH (&_mh_execute_header)
|
||
#endif
|
||
static void map_image(module_t *m, const char *dir) {
|
||
(void)dir;
|
||
char sx0[17], sx1[17], dm[17]; snprintf(sx0, sizeof sx0, "__dm%sx0", m->name); snprintf(sx1, sizeof sx1, "__dm%sx1", m->name); snprintf(dm, sizeof dm, "__DM%sW", m->name);
|
||
unsigned long z0, zw, z1;
|
||
const uint8_t *x0 = getsectiondata(IMAGE_MH, "__TEXT", sx0, &z0), *w = getsectiondata(IMAGE_MH, dm, "__img", &zw), *x1 = getsectiondata(IMAGE_MH, "__TEXT", sx1, &z1);
|
||
if (!x0 || !w || !x1) die("内嵌节 %s / %s / %s 缺失", sx0, dm, sx1);
|
||
if (z0 + zw != m->patch_off || (z0 & 0x3FFF) || (zw & 0x3FFF) || (z1 & 0x3FFF) || ((uintptr_t)x0 & 0x3FFF) || ((uintptr_t)w & 0x3FFF) || ((uintptr_t)x1 & 0x3FFF))
|
||
die("%s 内嵌节布局不符(x0 %#lx w %#lx x1 %#lx,PATCH_OFF %#llx)", m->name, z0, zw, z1, (unsigned long long)m->patch_off);
|
||
m->size = m->patch_off + z1;
|
||
vm_address_t base = 0;
|
||
if (vm_allocate(mach_task_self(), &base, m->size, VM_FLAGS_ANYWHERE) != KERN_SUCCESS) die("%s 保留 %#zx 字节地址失败", m->name, m->size);
|
||
remap_at(m, base, x0, z0, FALSE, VM_PROT_READ | VM_PROT_EXECUTE);
|
||
remap_at(m, base + z0, w, zw, TRUE, VM_PROT_READ | VM_PROT_WRITE);
|
||
remap_at(m, base + m->patch_off, x1, z1, FALSE, VM_PROT_READ | VM_PROT_EXECUTE);
|
||
m->base = (uint8_t *)base; install_key_page(m);
|
||
}
|
||
#else
|
||
static uint8_t *slurp(const char *path, size_t *n) {
|
||
int fd = open(path, O_RDONLY);
|
||
if (fd < 0) die("open %s", path);
|
||
struct stat st; fstat(fd, &st);
|
||
uint8_t *p = malloc(st.st_size);
|
||
if (read(fd, p, st.st_size) != st.st_size) die("read %s", path);
|
||
close(fd); *n = st.st_size; return p;
|
||
}
|
||
|
||
// 拷入匿名页后立即封代码区(页 16 KB):[0, text_end) 与补丁区只读+执行,此后对代码区的任何写入都会当场崩;
|
||
// 之间(ro 与 data 同页只能一起)可写
|
||
// Seal the code area immediately after copying into anonymous pages (16 KB pages): [0, text_end) and the patch area are read-only+execute, and from then on any write to the code area crashes on the spot;
|
||
// what lies between (ro and data on the same page can only go together) is writable
|
||
static void map_image(module_t *m, const char *dir) {
|
||
char path[1024]; snprintf(path, sizeof path, "%s/%s.patched.bin", dir, m->name);
|
||
size_t sz; uint8_t *raw = slurp(path, &sz);
|
||
m->size = (sz + 0x3FFF) & ~0x3FFFull;
|
||
m->base = mmap(NULL, m->size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0);
|
||
if (m->base == MAP_FAILED) die("%s mmap", m->name);
|
||
memcpy(m->base, raw, sz); free(raw); install_key_page(m);
|
||
uint64_t rx = (m->text_end + 0x3FFF) & ~0x3FFFull;
|
||
if (mprotect(m->base, rx, PROT_READ | PROT_EXEC)) die("%s mprotect text", m->name);
|
||
uint64_t po = m->patch_off & ~0x3FFFull;
|
||
if (mprotect(m->base + po, m->size - po, PROT_READ | PROT_EXEC)) die("%s mprotect patch", m->name);
|
||
}
|
||
#endif
|
||
|
||
void load_modules(const char *dir, const char **names, int n) {
|
||
// 先全部映射并解析符号表,再统一重定位(跨模块符号要先都在位)
|
||
// DM_BOOTPROF:① 宿主装载细分(毫秒),定 ③ 预重定位 / mmap 的上限
|
||
// Map everything and parse the symbol tables first, then relocate all at once (cross-module symbols must all be in place first)
|
||
// DM_BOOTPROF: breakdown of ① host loading (milliseconds), to set the upper bound for ③ pre-relocation / mmap
|
||
uint64_t t0 = now_ns(), tm = 0, tp = 0, tg = 0, tr = 0, t;
|
||
for (int i = 0; i < n; i++) {
|
||
module_t *m = &g_mod[i];
|
||
strlcpy(m->name, names[i], sizeof m->name);
|
||
read_layout(m, dir);
|
||
}
|
||
create_keys();
|
||
for (int i = 0; i < n; i++) {
|
||
module_t *m = &g_mod[i];
|
||
t = now_ns(); map_image(m, dir); tm += now_ns() - t; // 返回时代码区已是只读+执行,此后装载器只写 data | On return the code area is already read-only+execute; from here on the loader only writes data
|
||
if (!i) tsd_check();
|
||
t = now_ns(); parse_dynamic(m); tp += now_ns() - t;
|
||
t = now_ns(); register_gates(m, dir); tg += now_ns() - t;
|
||
LOG("%s: base %p size %#zx", names[i], m->base, m->size);
|
||
g_nmod++;
|
||
}
|
||
force_init();
|
||
t = now_ns(); for (int i = 0; i < g_nmod; i++) relocate(&g_mod[i]); tr = now_ns() - t;
|
||
force_check();
|
||
{ void hlecount_seal(void); hlecount_seal(); } // DM_HLECOUNT:重定位完成,计数桩页封成只读可执行 | DM_HLECOUNT: relocation done, seal the counting stub pages as read-only executable
|
||
if (getenv("DM_SYMCHECK")) LOG("DM_SYMCHECK:装载期共核 %lu 次符号查询,哈希与线性不一致 %lu", g_symchk_n, g_symchk_bad);
|
||
if (g_flag.bootprof) LOG("① 细分:映射(读 + 拷 + 封页)%.1f ms、解析动态段 %.1f ms、门注册 %.1f ms、重定位 + 强制接管 %.1f ms,合计 %.1f ms",
|
||
tm / 1e6, tp / 1e6, tg / 1e6, tr / 1e6, (now_ns() - t0) / 1e6);
|
||
}
|
||
|
||
// 扮演 rtld 的 InitModules(rtld+0x830):按装载链表正序调各模块 DT_INIT。
|
||
// 时机由 nn::init::Start 决定:nninitStartup 与 _init_libc2 之后、nnMain 之前(Start 的第 4 个参数回调)。
|
||
// Plays the role of rtld's InitModules (rtld+0x830): call each module's DT_INIT in forward order of the load list.
|
||
// The timing is decided by nn::init::Start: after nninitStartup and _init_libc2, before nnMain (the 4th argument callback of Start).
|
||
void run_module_inits(void) {
|
||
for (int i = 0; i < g_nmod; i++) {
|
||
module_t *m = &g_mod[i];
|
||
if (!m->init) continue;
|
||
LOG("%s: DT_INIT +%#llx(init_array %llu 项)", m->name, m->init, m->init_arraysz / 8);
|
||
((void (*)(void))(m->base + m->init))();
|
||
}
|
||
}
|