mirror of
https://github.com/Lakr233/vphone-cli.git
synced 2026-04-05 13:09:06 +08:00
Fix C23: vnode_getattr string anchor resolved to wrong function (AppleImage4)
Root cause: find_string("vnode_getattr") matched "%s: vnode_getattr: %d"
format string inside an AppleImage4 function. The old code then took that
function as vnode_getattr itself, causing BL to call into AppleImage4 with
wrong arguments → PAC failure on indirect branch at a2+48.
Fix: _find_vnode_getattr_via_string() now scans backward from the string
ref for a BL instruction and extracts its target — the real vnode_getattr
(sub_FFFFFE0007CCD1B4 at foff 0xCC91B4).
Bisection confirmed: variants A (stack frame) and B (+ tpidr_el1) boot OK,
variant C (+ BL vnode_getattr) panics with old resolution, boots OK with fix.
Boot-tested: full C23 patch with corrected vnode_getattr — BOOTS OK.
This commit is contained in:
5
Makefile
5
Makefile
@@ -222,7 +222,7 @@ restore:
|
||||
# Ramdisk
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
.PHONY: ramdisk_build ramdisk_send testing_ramdisk_build testing_ramdisk_send testing_do testing_do_save testing_kernel_patch testing_do_patch
|
||||
.PHONY: ramdisk_build ramdisk_send testing_ramdisk_build testing_ramdisk_send testing_do testing_do_save testing_kernel_patch testing_do_patch testing_c23_bisect
|
||||
|
||||
ramdisk_build:
|
||||
cd $(VM_DIR) && $(PYTHON) "$(CURDIR)/$(SCRIPTS)/ramdisk_build.py" .
|
||||
@@ -251,6 +251,9 @@ testing_do_patch:
|
||||
testing_batch:
|
||||
zsh "$(CURDIR)/$(SCRIPTS)/testing_batch.sh" $(PATCHES)
|
||||
|
||||
testing_c23_bisect:
|
||||
cd $(VM_DIR) && $(PYTHON) "$(CURDIR)/$(SCRIPTS)/testing_c23_bisect.py" . $(VARIANT)
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
# CFW
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
@@ -43,7 +43,13 @@
|
||||
- [29]: `0xFE00093B0830` (696 bytes)
|
||||
|
||||
### vnode_getattr
|
||||
- String-related hit: xref `0xFE00084C08EC` → function start `0xFE00084C0718`
|
||||
- Real `vnode_getattr`: `sub_FFFFFE0007CCD1B4` (file offset `0xCC91B4`)
|
||||
- Signature: `int vnode_getattr(vnode_t vp, struct vnode_attr *vap, vfs_context_t ctx)`
|
||||
- Located in XNU kernel proper (not a kext)
|
||||
- **Bug 3 note**: The string `"vnode_getattr"` appears in format strings like
|
||||
`"%s: vnode_getattr: %d"` inside callers (e.g., AppleImage4 at `0xFE00084C0718`).
|
||||
The old string-anchor approach resolved to the AppleImage4 caller, not vnode_getattr.
|
||||
See Bug 3 below.
|
||||
|
||||
### Original hook prologue
|
||||
```
|
||||
@@ -102,6 +108,37 @@ Replace PACIBSP at function entry with `B cave`. The cave runs PACIBSP first
|
||||
resume the original function. Uses only PC-relative B/BL instructions —
|
||||
no PAC involvement, no chained fixup modification.
|
||||
|
||||
### Bug 3: BL to wrong function — string anchor misresolution (PAC PANIC)
|
||||
The string-anchor approach for finding `vnode_getattr` was:
|
||||
1. `find_string(b"vnode_getattr")` → finds `"%s: vnode_getattr: %d"` (format string)
|
||||
2. `find_string_refs()` → finds ADRP+ADD at `0xFE00084C08EC` (inside AppleImage4 function)
|
||||
3. `find_function_start()` → returns `0xFE00084C0718` (an **AppleImage4** function)
|
||||
|
||||
This function is NOT `vnode_getattr` — it is an AppleImage4 function that CALLS
|
||||
`vnode_getattr` and prints the error message when the call fails. The BL in
|
||||
our shellcode was calling into AppleImage4's function with wrong arguments.
|
||||
|
||||
At `0xFE00084C0774`, this function does:
|
||||
```
|
||||
v9 = (*(__int64 (**)(void))(a2 + 48))(); // indirect PAC-signed call
|
||||
```
|
||||
With our arguments, `a2` (vattr buffer) had garbage at offset +48, causing a
|
||||
PAC-authenticated branch to fail → same panic as Bug 2.
|
||||
|
||||
**Bisection results** (systematic boot tests):
|
||||
- Variant A (stack frame save/restore only): **BOOTS OK**
|
||||
- Variant B (+ mrs tpidr_el1 + vfs_context): **BOOTS OK**
|
||||
- Variant C (+ BL vnode_getattr): **PANICS** ← crash introduced here
|
||||
- Full shellcode: PANICS
|
||||
|
||||
**Fix**: Replaced the string-anchor resolution with `_find_vnode_getattr_via_string()`:
|
||||
1. Find the format string `"%s: vnode_getattr: %d"`
|
||||
2. Find the ADRP+ADD xref to it (inside the caller function)
|
||||
3. Scan backward from the xref for a BL instruction (the call to the real vnode_getattr)
|
||||
4. Extract the BL target → `sub_FFFFFE0007CCD1B4` = real `vnode_getattr`
|
||||
|
||||
The real `vnode_getattr` is at file offset `0xCC91B4`, not `0x14BC718`.
|
||||
|
||||
## 6) Current Implementation
|
||||
- 47 patches: 46 shellcode instructions in __TEXT_EXEC cave + 1 trampoline
|
||||
(B cave replacing PACIBSP at hook function entry).
|
||||
|
||||
@@ -6,6 +6,76 @@ PACIBSP = bytes([0x7F, 0x23, 0x03, 0xD5]) # 0xD503237F
|
||||
|
||||
|
||||
class KernelJBPatchHookCredLabelMixin:
|
||||
def _find_vnode_getattr_via_string(self):
|
||||
"""Find vnode_getattr by locating a caller function via string ref.
|
||||
|
||||
The string "vnode_getattr" appears in format strings like
|
||||
"%s: vnode_getattr: %d" inside functions that CALL vnode_getattr.
|
||||
We find such a caller, then extract the BL target near the string
|
||||
reference to get the real vnode_getattr address.
|
||||
|
||||
Previous approach: find_string → find_string_refs → find_function_start
|
||||
was wrong because it returned the CALLER (e.g. an AppleImage4 function)
|
||||
instead of vnode_getattr itself.
|
||||
"""
|
||||
str_off = self.find_string(b"vnode_getattr")
|
||||
if str_off < 0:
|
||||
return -1
|
||||
|
||||
refs = self.find_string_refs(str_off)
|
||||
if not refs:
|
||||
return -1
|
||||
|
||||
# The string ref is inside a function that calls vnode_getattr.
|
||||
# Scan backward from the string ref for a BL instruction — the
|
||||
# nearest preceding BL is very likely the BL vnode_getattr call
|
||||
# (the error message prints right after the call fails).
|
||||
ref_off = refs[0][0] # ADRP offset
|
||||
for scan_off in range(ref_off - 4, ref_off - 64, -4):
|
||||
if scan_off < 0:
|
||||
break
|
||||
insn = _rd32(self.raw, scan_off)
|
||||
if (insn >> 26) == 0x25: # BL opcode
|
||||
imm26 = insn & 0x3FFFFFF
|
||||
if imm26 & (1 << 25):
|
||||
imm26 -= 1 << 26 # sign extend
|
||||
target = scan_off + imm26 * 4
|
||||
if any(s <= target < e for s, e in self.code_ranges):
|
||||
self._log(
|
||||
f" [+] vnode_getattr at 0x{target:X} "
|
||||
f"(via BL at 0x{scan_off:X}, "
|
||||
f"near string ref at 0x{ref_off:X})"
|
||||
)
|
||||
return target
|
||||
|
||||
# Fallback: try additional string hits
|
||||
start = str_off + 1
|
||||
for _ in range(5):
|
||||
str_off2 = self.find_string(b"vnode_getattr", start)
|
||||
if str_off2 < 0:
|
||||
break
|
||||
refs2 = self.find_string_refs(str_off2)
|
||||
if refs2:
|
||||
ref_off2 = refs2[0][0]
|
||||
for scan_off in range(ref_off2 - 4, ref_off2 - 64, -4):
|
||||
if scan_off < 0:
|
||||
break
|
||||
insn = _rd32(self.raw, scan_off)
|
||||
if (insn >> 26) == 0x25: # BL
|
||||
imm26 = insn & 0x3FFFFFF
|
||||
if imm26 & (1 << 25):
|
||||
imm26 -= 1 << 26
|
||||
target = scan_off + imm26 * 4
|
||||
if any(s <= target < e for s, e in self.code_ranges):
|
||||
self._log(
|
||||
f" [+] vnode_getattr at 0x{target:X} "
|
||||
f"(via BL at 0x{scan_off:X})"
|
||||
)
|
||||
return target
|
||||
start = str_off2 + 1
|
||||
|
||||
return -1
|
||||
|
||||
def patch_hook_cred_label_update_execve(self):
|
||||
"""Inline-trampoline the sandbox cred_label_update_execve hook.
|
||||
|
||||
@@ -26,16 +96,7 @@ class KernelJBPatchHookCredLabelMixin:
|
||||
# ── 1. Find vnode_getattr via string anchor ──────────────
|
||||
vnode_getattr_off = self._resolve_symbol("_vnode_getattr")
|
||||
if vnode_getattr_off < 0:
|
||||
str_off = self.find_string(b"vnode_getattr")
|
||||
if str_off >= 0:
|
||||
refs = self.find_string_refs(str_off)
|
||||
if refs:
|
||||
vnode_getattr_off = self.find_function_start(refs[0][0])
|
||||
if vnode_getattr_off >= 0:
|
||||
self._log(
|
||||
f" [+] vnode_getattr at 0x"
|
||||
f"{vnode_getattr_off:X} (via string)"
|
||||
)
|
||||
vnode_getattr_off = self._find_vnode_getattr_via_string()
|
||||
|
||||
if vnode_getattr_off < 0:
|
||||
self._log(" [-] vnode_getattr not found")
|
||||
|
||||
258
scripts/testing_c23_bisect.py
Normal file
258
scripts/testing_c23_bisect.py
Normal file
@@ -0,0 +1,258 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
testing_c23_bisect.py — Bisect C23 shellcode to find which part causes PAC panic.
|
||||
|
||||
Usage:
|
||||
python3 testing_c23_bisect.py <vm_dir> <variant>
|
||||
|
||||
Variants (progressive complexity):
|
||||
A — PACIBSP + save/restore regs + B hook+4 (stack frame, no calls)
|
||||
B — A + mrs tpidr_el1 + vfs_context build (register reads, no calls)
|
||||
C — B + BL vnode_getattr (external function call)
|
||||
D — C + ownership propagation (uid/gid/csflags writes)
|
||||
E — full shellcode (same as kernel_jb_patch_hook_cred_label.py)
|
||||
|
||||
Each variant is strictly additive — if A boots, B adds only the next layer.
|
||||
"""
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
|
||||
from fw_patch import find_file, find_restore_dir, load_firmware, save_firmware
|
||||
from patchers.kernel_jb import KernelJBPatcher
|
||||
from patchers.kernel_jb_base import asm, _rd32, _rd64, NOP
|
||||
|
||||
PACIBSP = bytes([0x7F, 0x23, 0x03, 0xD5])
|
||||
|
||||
|
||||
def build_variant(kp, variant, cave, orig_hook, vnode_getattr_off):
|
||||
"""Build shellcode for the given variant, return list of 4-byte parts."""
|
||||
|
||||
# Helper: encode BL/B
|
||||
def bl(src, dst):
|
||||
return kp._encode_bl(src, dst)
|
||||
|
||||
def b(src, dst):
|
||||
return kp._encode_b(src, dst)
|
||||
|
||||
# B resume always at the last slot
|
||||
# We'll pad all variants to 46 slots for consistency.
|
||||
#
|
||||
# Variant A: stack frame only
|
||||
# Variant B: + tpidr_el1 / vfs_context
|
||||
# Variant C: + BL vnode_getattr
|
||||
# Variant D: + ownership propagation
|
||||
# Variant E: full (same as production)
|
||||
|
||||
parts = []
|
||||
|
||||
if variant in ("A", "B", "C", "D", "E"):
|
||||
parts.append(PACIBSP) # 0: relocated from hook
|
||||
# In full shellcode, slot 1 is: cbz x3, #0xb0 → slot 45
|
||||
# For variant A, skip the cbz (just NOP), so we always enter the frame
|
||||
if variant == "A":
|
||||
parts.append(NOP) # 1
|
||||
else:
|
||||
parts.append(asm("cbz x3, #0xb0")) # 1: if vp==NULL → slot 45
|
||||
parts.append(asm("sub sp, sp, #0x400")) # 2
|
||||
parts.append(asm("stp x29, x30, [sp]")) # 3
|
||||
parts.append(asm("stp x0, x1, [sp, #16]")) # 4
|
||||
parts.append(asm("stp x2, x3, [sp, #32]")) # 5
|
||||
parts.append(asm("stp x4, x5, [sp, #48]")) # 6
|
||||
parts.append(asm("stp x6, x7, [sp, #64]")) # 7
|
||||
|
||||
if variant in ("B", "C", "D", "E"):
|
||||
# Build vfs_context
|
||||
parts.append(asm("mrs x8, tpidr_el1")) # 8: current_thread
|
||||
parts.append(asm("stp x8, x0, [sp, #0x70]")) # 9: {thread, cred}
|
||||
parts.append(asm("add x2, sp, #0x70")) # 10: ctx = &vfs_ctx
|
||||
# Setup vnode_getattr args
|
||||
parts.append(asm("ldr x0, [sp, #0x28]")) # 11: x0 = vp (saved x3)
|
||||
parts.append(asm("add x1, sp, #0x80")) # 12: x1 = &vattr
|
||||
parts.append(asm("mov w8, #0x380")) # 13: vattr size
|
||||
parts.append(asm("stp xzr, x8, [x1]")) # 14: init vattr
|
||||
parts.append(asm("stp xzr, xzr, [x1, #0x10]")) # 15: init vattr+16
|
||||
parts.append(NOP) # 16
|
||||
parts.append(NOP) # 17
|
||||
elif variant == "A":
|
||||
# Pad slots 8-17 with NOP
|
||||
for _ in range(10):
|
||||
parts.append(NOP)
|
||||
|
||||
if variant in ("C", "D", "E"):
|
||||
# BL vnode_getattr
|
||||
vnode_bl_off = cave + 18 * 4
|
||||
vnode_bl = bl(vnode_bl_off, vnode_getattr_off)
|
||||
if not vnode_bl:
|
||||
print(" [-] BL to vnode_getattr out of range")
|
||||
return None
|
||||
parts.append(vnode_bl) # 18: BL vnode_getattr
|
||||
elif variant in ("A", "B"):
|
||||
parts.append(NOP) # 18
|
||||
|
||||
if variant in ("C", "D", "E"):
|
||||
# After BL, check result — jump to restore on error
|
||||
parts.append(asm("cbnz x0, #0x4c")) # 19: error → slot 38
|
||||
elif variant in ("A", "B"):
|
||||
parts.append(NOP) # 19
|
||||
|
||||
if variant in ("D", "E"):
|
||||
# Ownership propagation
|
||||
parts.append(asm("mov w2, #0")) # 20: changed = 0
|
||||
parts.append(asm("ldr w8, [sp, #0xCC]")) # 21: va_mode
|
||||
parts.append(bytes([0xA8, 0x00, 0x58, 0x36])) # 22: tbz w8,#11
|
||||
parts.append(asm("ldr w8, [sp, #0xC4]")) # 23: va_uid
|
||||
parts.append(asm("ldr x0, [sp, #0x18]")) # 24: new_cred
|
||||
parts.append(asm("str w8, [x0, #0x18]")) # 25: cred->uid
|
||||
parts.append(asm("mov w2, #1")) # 26: changed = 1
|
||||
parts.append(asm("ldr w8, [sp, #0xCC]")) # 27: va_mode
|
||||
parts.append(bytes([0xA8, 0x00, 0x50, 0x36])) # 28: tbz w8,#10
|
||||
parts.append(asm("mov w2, #1")) # 29: changed = 1
|
||||
parts.append(asm("ldr w8, [sp, #0xC8]")) # 30: va_gid
|
||||
parts.append(asm("ldr x0, [sp, #0x18]")) # 31: new_cred
|
||||
parts.append(asm("str w8, [x0, #0x28]")) # 32: cred->gid
|
||||
parts.append(asm("cbz w2, #0x14")) # 33: if !changed → slot 38
|
||||
parts.append(asm("ldr x0, [sp, #0x20]")) # 34: proc
|
||||
parts.append(asm("ldr w8, [x0, #0x454]")) # 35: p_csflags
|
||||
parts.append(asm("orr w8, w8, #0x100")) # 36: CS_VALID
|
||||
parts.append(asm("str w8, [x0, #0x454]")) # 37: store
|
||||
elif variant in ("A", "B", "C"):
|
||||
# Pad slots 20-37 with NOP
|
||||
for _ in range(18):
|
||||
parts.append(NOP)
|
||||
|
||||
# Restore and resume — always present (slots 38-45)
|
||||
if variant in ("A", "B", "C", "D", "E"):
|
||||
parts.append(asm("ldp x0, x1, [sp, #16]")) # 38
|
||||
parts.append(asm("ldp x2, x3, [sp, #32]")) # 39
|
||||
parts.append(asm("ldp x4, x5, [sp, #48]")) # 40
|
||||
parts.append(asm("ldp x6, x7, [sp, #64]")) # 41
|
||||
parts.append(asm("ldp x29, x30, [sp]")) # 42
|
||||
parts.append(asm("add sp, sp, #0x400")) # 43
|
||||
parts.append(NOP) # 44
|
||||
|
||||
# B hook+4
|
||||
b_resume_off = cave + 45 * 4
|
||||
b_resume = b(b_resume_off, orig_hook + 4)
|
||||
if not b_resume:
|
||||
print(" [-] B to hook+4 out of range")
|
||||
return None
|
||||
parts.append(b_resume) # 45
|
||||
|
||||
assert len(parts) == 46, f"Expected 46 parts, got {len(parts)}"
|
||||
return parts
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 3:
|
||||
print(f"Usage: {sys.argv[0]} <vm_dir> <variant>")
|
||||
print(f" Variants: A B C D E")
|
||||
sys.exit(1)
|
||||
|
||||
vm_dir = os.path.abspath(sys.argv[1])
|
||||
variant = sys.argv[2].upper()
|
||||
if variant not in ("A", "B", "C", "D", "E"):
|
||||
print(f"[-] Unknown variant: {variant}")
|
||||
sys.exit(1)
|
||||
|
||||
restore_dir = find_restore_dir(vm_dir)
|
||||
if not restore_dir:
|
||||
print(f"[-] No *Restore* directory found in {vm_dir}")
|
||||
sys.exit(1)
|
||||
|
||||
kernel_path = find_file(restore_dir, ["kernelcache.research.vphone600"], "kernelcache")
|
||||
backup_path = kernel_path + ".base_backup"
|
||||
|
||||
if not os.path.exists(backup_path):
|
||||
print(f"[-] No backup found: {backup_path}")
|
||||
sys.exit(1)
|
||||
|
||||
# Restore from backup
|
||||
shutil.copy2(backup_path, kernel_path)
|
||||
print(f"[*] Restored kernel from backup")
|
||||
|
||||
# Load
|
||||
im4p, data, was_im4p, original_raw = load_firmware(kernel_path)
|
||||
print(f"[*] Loaded: {len(data)} bytes")
|
||||
|
||||
kp = KernelJBPatcher(data)
|
||||
|
||||
# ── Find vnode_getattr ──
|
||||
vnode_getattr_off = kp._resolve_symbol("_vnode_getattr")
|
||||
if vnode_getattr_off < 0:
|
||||
vnode_getattr_off = kp._find_vnode_getattr_via_string()
|
||||
if vnode_getattr_off < 0:
|
||||
print("[-] vnode_getattr not found")
|
||||
sys.exit(1)
|
||||
print(f"[+] vnode_getattr at 0x{vnode_getattr_off:X}")
|
||||
|
||||
# ── Find sandbox ops table ──
|
||||
ops_table = kp._find_sandbox_ops_table_via_conf()
|
||||
if ops_table is None:
|
||||
print("[-] sandbox ops table not found")
|
||||
sys.exit(1)
|
||||
|
||||
# ── Find hook (largest in ops[0:30]) ──
|
||||
hook_index = -1
|
||||
orig_hook = -1
|
||||
best_size = 0
|
||||
for idx in range(0, 30):
|
||||
entry = kp._read_ops_entry(ops_table, idx)
|
||||
if entry is None or entry <= 0:
|
||||
continue
|
||||
if not any(s <= entry < e for s, e in kp.code_ranges):
|
||||
continue
|
||||
fend = kp._find_func_end(entry, 0x2000)
|
||||
fsize = fend - entry
|
||||
if fsize > best_size:
|
||||
best_size = fsize
|
||||
hook_index = idx
|
||||
orig_hook = entry
|
||||
|
||||
if hook_index < 0 or best_size < 1000:
|
||||
print(f"[-] hook not found (best: idx={hook_index}, size={best_size})")
|
||||
sys.exit(1)
|
||||
print(f"[+] hook at ops[{hook_index}] = 0x{orig_hook:X} ({best_size} bytes)")
|
||||
|
||||
# Verify PACIBSP
|
||||
first_insn = data[orig_hook:orig_hook + 4]
|
||||
if first_insn != PACIBSP:
|
||||
print(f"[-] first insn not PACIBSP (got 0x{_rd32(data, orig_hook):08X})")
|
||||
sys.exit(1)
|
||||
|
||||
# ── Find code cave (200 bytes) ──
|
||||
cave = kp._find_code_cave(200)
|
||||
if cave < 0:
|
||||
print("[-] no code cave found")
|
||||
sys.exit(1)
|
||||
print(f"[+] code cave at 0x{cave:X}")
|
||||
|
||||
# ── Build variant shellcode ──
|
||||
print(f"\n[*] Building variant {variant}")
|
||||
parts = build_variant(kp, variant, cave, orig_hook, vnode_getattr_off)
|
||||
if parts is None:
|
||||
sys.exit(1)
|
||||
|
||||
# Write shellcode to data
|
||||
for i, part in enumerate(parts):
|
||||
off = cave + i * 4
|
||||
data[off:off + 4] = part
|
||||
|
||||
# Patch function entry: PACIBSP → B cave
|
||||
b_to_cave = kp._encode_b(orig_hook, cave)
|
||||
if not b_to_cave:
|
||||
print("[-] B to cave out of range")
|
||||
sys.exit(1)
|
||||
data[orig_hook:orig_hook + 4] = b_to_cave
|
||||
|
||||
print(f"[+] Variant {variant}: {len(parts)} instructions written to cave")
|
||||
print(f"[+] Trampoline: B 0x{cave:X} at 0x{orig_hook:X}")
|
||||
|
||||
# Save
|
||||
save_firmware(kernel_path, im4p, data, was_im4p, original_raw)
|
||||
print(f"[+] Saved: {kernel_path}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
66
scripts/testing_c23_bisect.sh
Executable file
66
scripts/testing_c23_bisect.sh
Executable file
@@ -0,0 +1,66 @@
|
||||
#!/usr/bin/env zsh
|
||||
set -euo pipefail
|
||||
|
||||
# Bisect C23 shellcode — restore, patch variant, rebuild ramdisk, boot.
|
||||
#
|
||||
# Usage: ./testing_c23_bisect.sh <variant>
|
||||
# or: make testing_c23_bisect_boot VARIANT=A
|
||||
#
|
||||
# Variants: A B C D E (see testing_c23_bisect.py)
|
||||
|
||||
typeset -a CHILD_PIDS=()
|
||||
|
||||
cleanup() {
|
||||
echo "\n[c23] cleaning up..."
|
||||
for pid in "${CHILD_PIDS[@]}"; do
|
||||
if kill -0 "$pid" 2>/dev/null; then
|
||||
echo "[c23] killing PID $pid"
|
||||
kill -9 "$pid" 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
exit 0
|
||||
}
|
||||
|
||||
trap cleanup EXIT INT TERM
|
||||
|
||||
PROJECT_DIR="$(cd "$(dirname "${0:a:h}")" && pwd)"
|
||||
cd "$PROJECT_DIR"
|
||||
|
||||
VARIANT="${1:-}"
|
||||
if [[ -z "$VARIANT" ]]; then
|
||||
echo "Usage: $0 <variant>"
|
||||
echo " Variants: A B C D E"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
VM_DIR="${VM_DIR:-vm}"
|
||||
|
||||
echo "[c23] ═══════════════════════════════════════════"
|
||||
echo "[c23] Bisect variant: $VARIANT"
|
||||
echo "[c23] ═══════════════════════════════════════════"
|
||||
|
||||
# Kill existing
|
||||
echo "[c23] killing existing vphone-cli..."
|
||||
pkill -9 vphone-cli 2>/dev/null || true
|
||||
sleep 1
|
||||
|
||||
# Restore + patch variant
|
||||
echo "[c23] restoring base kernel + applying variant $VARIANT"
|
||||
make testing_c23_bisect VARIANT="$VARIANT"
|
||||
|
||||
# Rebuild ramdisk
|
||||
echo "[c23] testing_ramdisk_build..."
|
||||
make testing_ramdisk_build
|
||||
|
||||
# Send ramdisk in background
|
||||
echo "[c23] testing_ramdisk_send (background)..."
|
||||
make testing_ramdisk_send &
|
||||
CHILD_PIDS+=($!)
|
||||
|
||||
# Boot
|
||||
echo "[c23] boot_dfu..."
|
||||
make boot_dfu &
|
||||
CHILD_PIDS+=($!)
|
||||
|
||||
echo "[c23] waiting for boot (PID ${CHILD_PIDS[-1]})..."
|
||||
wait "${CHILD_PIDS[-1]}" 2>/dev/null || true
|
||||
Reference in New Issue
Block a user