Files
fluxer-desktop-patched/fluxer_desktop/native/win-game-capture/hook/tests/arm64_reloc.rs
T
brenden 682afacd30 Add native self-hosted instance connection to fluxer_desktop
Trimmed monorepo checkout (fluxer_desktop + packages/voice_engine_v2 +
tools/ci) with a "Connect to a Different Server" menu item and popout
that lets the desktop app switch to any self-hosted Fluxer instance,
plus fixes for well-known discovery on single-domain self-hosted
deployments and a false-positive ERR_ABORTED on same-origin client
redirects during the switch. Defaults to chat.fluxr.chat and uses an
isolated userData directory from the official build.
2026-07-01 18:22:43 -04:00

413 lines
13 KiB
Rust

// SPDX-License-Identifier: AGPL-3.0-or-later
#[path = "../src/arm64_reloc.rs"]
mod arm64_reloc;
use arm64_reloc::*;
fn adr(rd: u32, imm21: i32) -> u32 {
let raw = (imm21 as u32) & 0x1F_FFFF;
let immlo = (raw & 0x3) << 29;
let immhi = ((raw >> 2) & 0x7FFFF) << 5;
0x1000_0000 | immlo | immhi | (rd & 0x1F)
}
fn adrp(rd: u32, imm21: i32) -> u32 {
let raw = (imm21 as u32) & 0x1F_FFFF;
let immlo = (raw & 0x3) << 29;
let immhi = ((raw >> 2) & 0x7FFFF) << 5;
0x9000_0000 | immlo | immhi | (rd & 0x1F)
}
fn b(off_words: i32) -> u32 {
0x1400_0000 | ((off_words as u32) & 0x03FF_FFFF)
}
fn bl(off_words: i32) -> u32 {
0x9400_0000 | ((off_words as u32) & 0x03FF_FFFF)
}
fn bcond(cond: u32, off_words: i32) -> u32 {
0x5400_0000 | (((off_words as u32) & 0x7FFFF) << 5) | (cond & 0xF)
}
fn cbz(rt: u32, off_words: i32) -> u32 {
0xB400_0000 | (((off_words as u32) & 0x7FFFF) << 5) | (rt & 0x1F)
}
fn tbz(rt: u32, bit: u32, off_words: i32) -> u32 {
let b5 = (bit & 0x20) << (31 - 5);
let b40 = (bit & 0x1F) << 19;
0x3600_0000 | b5 | b40 | (((off_words as u32) & 0x3FFF) << 5) | (rt & 0x1F)
}
fn ldr_lit(rt: u32, off_words: i32) -> u32 {
0x5800_0000 | (((off_words as u32) & 0x7FFFF) << 5) | (rt & 0x1F)
}
fn adr_target(insn: u32, pc: u64, page: bool) -> u64 {
let immlo = ((insn >> 29) & 0x3) as i64;
let immhi = ((insn >> 5) & 0x7FFFF) as i64;
let raw = (immhi << 2) | immlo;
let imm21 = (raw << 43) >> 43;
if page {
((pc & !0xFFF) as i64 + imm21 * 4096) as u64
} else {
(pc as i64 + imm21) as u64
}
}
fn imm19_target(insn: u32, pc: u64) -> u64 {
let imm19 = ((insn >> 5) & 0x7FFFF) as i64;
let off = ((imm19 << 45) >> 45) * 4;
(pc as i64 + off) as u64
}
fn imm14_target(insn: u32, pc: u64) -> u64 {
let imm14 = ((insn >> 5) & 0x3FFF) as i64;
let off = ((imm14 << 50) >> 50) * 4;
(pc as i64 + off) as u64
}
#[test]
fn non_pc_relative_copied_verbatim() {
let stp = 0xA9BF_7BFD;
assert_eq!(relocate_instruction(stp, 0x1000, 0x9000), Some(stp));
let mov = 0xAA01_03E0;
assert_eq!(relocate_instruction(mov, 0x1000, 0x9000), Some(mov));
let sub = 0xD100_83FF;
assert_eq!(relocate_instruction(sub, 0x1000, 0x9000), Some(sub));
let mov_fp_sp = 0x9100_03FD;
assert_eq!(
relocate_instruction(mov_fp_sp, 0x1000, 0x9000),
Some(mov_fp_sp)
);
}
#[test]
fn adr_relocates_to_same_target() {
let src_pc = 0x140_0010_0000u64;
let dst_pc = 0x140_0010_8000u64;
let insn = adr(0, 0x4000);
let original = adr_target(insn, src_pc, false);
let reloc = relocate_instruction(insn, src_pc, dst_pc).expect("in range");
assert_eq!(adr_target(reloc, dst_pc, false), original);
assert_eq!(reloc & 0x1F, 0);
}
#[test]
fn adr_negative_offset() {
let src_pc = 0x140_0010_0000u64;
let dst_pc = 0x140_0010_0010u64;
let insn = adr(5, -0x100);
let original = adr_target(insn, src_pc, false);
let reloc = relocate_instruction(insn, src_pc, dst_pc).expect("in range");
assert_eq!(adr_target(reloc, dst_pc, false), original);
assert_eq!(reloc & 0x1F, 5);
}
#[test]
fn adr_out_of_range_refused() {
let src_pc = 0x0000_0000_0000u64;
let dst_pc = 0x0000_0080_0000u64;
let insn = adr(0, 0x1000);
assert_eq!(relocate_instruction(insn, src_pc, dst_pc), None);
}
#[test]
fn adrp_relocates_to_same_page() {
let src_pc = 0x140_0010_0000u64;
let dst_pc = 0x140_0030_0000u64;
let insn = adrp(9, 0x10);
let original = adr_target(insn, src_pc, true);
let reloc = relocate_instruction(insn, src_pc, dst_pc).expect("in range");
assert_eq!(adr_target(reloc, dst_pc, true), original);
assert_eq!(reloc & 0x1F, 9);
assert_eq!(reloc & 0x9F00_0000, 0x9000_0000);
}
#[test]
fn adrp_negative() {
let src_pc = 0x140_0090_0000u64;
let dst_pc = 0x140_0050_0000u64;
let insn = adrp(1, -0x20);
let original = adr_target(insn, src_pc, true);
let reloc = relocate_instruction(insn, src_pc, dst_pc).expect("in range");
assert_eq!(adr_target(reloc, dst_pc, true), original);
}
#[test]
fn bcond_relocates() {
let src_pc = 0x10_0000u64;
let dst_pc = 0x12_0000u64;
let insn = bcond(0x0, 0x40);
let original = imm19_target(insn, src_pc);
let reloc = relocate_instruction(insn, src_pc, dst_pc).expect("in range");
assert_eq!(imm19_target(reloc, dst_pc), original);
assert_eq!(reloc & 0xF, 0x0);
}
#[test]
fn bcond_out_of_range_refused() {
let src_pc = 0x0u64;
let dst_pc = 0x20_0000u64;
let insn = bcond(0x1, 0x10);
assert_eq!(relocate_instruction(insn, src_pc, dst_pc), None);
}
#[test]
fn cbz_relocates() {
let src_pc = 0x10_0000u64;
let dst_pc = 0x10_8000u64;
let insn = cbz(3, -0x20);
let original = imm19_target(insn, src_pc);
let reloc = relocate_instruction(insn, src_pc, dst_pc).expect("in range");
assert_eq!(imm19_target(reloc, dst_pc), original);
assert_eq!(reloc & 0x1F, 3);
assert_eq!(reloc & 0x8000_0000, 0x8000_0000);
}
#[test]
fn tbz_relocates() {
let src_pc = 0x10_0000u64;
let dst_pc = 0x10_1000u64;
let insn = tbz(7, 5, 0x10);
let original = imm14_target(insn, src_pc);
let reloc = relocate_instruction(insn, src_pc, dst_pc).expect("in range");
assert_eq!(imm14_target(reloc, dst_pc), original);
assert_eq!(reloc & 0x1F, 7);
}
#[test]
fn tbz_out_of_range_refused() {
let src_pc = 0x0u64;
let dst_pc = 0x1_0000u64;
let insn = tbz(0, 1, 0x8);
assert_eq!(relocate_instruction(insn, src_pc, dst_pc), None);
}
#[test]
fn ldr_literal_relocates() {
let src_pc = 0x20_0000u64;
let dst_pc = 0x20_4000u64;
let insn = ldr_lit(2, 0x100);
let original = imm19_target(insn, src_pc);
let reloc = relocate_instruction(insn, src_pc, dst_pc).expect("in range");
assert_eq!(imm19_target(reloc, dst_pc), original);
assert_eq!(reloc & 0x1F, 2);
}
#[test]
fn direct_b_relocation_in_range() {
let src_pc = 0x10_0000u64;
let dst_pc = 0x14_0000u64;
let insn = b(0x100);
let target = branch_target(insn, src_pc).unwrap();
let reloc = relocate_instruction(insn, src_pc, dst_pc).expect("in range");
assert_eq!(branch_target(reloc, dst_pc).unwrap(), target);
}
#[test]
fn branch_target_decode() {
let pc = 0x10_0000u64;
assert_eq!(branch_target(b(4), pc), Some(pc + 16));
assert_eq!(branch_target(b(-4), pc), Some(pc - 16));
assert_eq!(branch_target(bl(1), pc), Some(pc + 4));
}
#[test]
fn abs_branch_encoding() {
let mut bytes = Vec::new();
append_abs_branch(&mut bytes, 0x1234_5678_9ABC_DEF0, false);
assert_eq!(bytes.len(), 16);
assert_eq!(
u32::from_le_bytes(bytes[0..4].try_into().unwrap()),
LDR_X16_PC8
);
assert_eq!(u32::from_le_bytes(bytes[4..8].try_into().unwrap()), BR_X16);
assert_eq!(
u64::from_le_bytes(bytes[8..16].try_into().unwrap()),
0x1234_5678_9ABC_DEF0
);
let mut linked = Vec::new();
append_abs_branch(&mut linked, 0xDEAD_BEEF, true);
assert_eq!(
u32::from_le_bytes(linked[4..8].try_into().unwrap()),
BLR_X16
);
}
#[test]
fn classify_branches() {
assert!(is_b(b(1)));
assert!(!is_bl(b(1)));
assert!(is_bl(bl(1)));
assert!(!is_b(bl(1)));
assert!(needs_absolute_island(b(1)));
assert!(needs_absolute_island(bl(1)));
assert!(!needs_absolute_island(adr(0, 1)));
assert!(!needs_absolute_island(NOP));
}
#[test]
fn assemble_trampoline_relocates_prologue() {
let stp = 0xA9BF_7BFDu32;
let mov = 0x9100_03FDu32;
let adrp_insn = adrp(8, 0x20);
let mut prologue = Vec::new();
for insn in [stp, mov, adrp_insn, NOP] {
prologue.extend_from_slice(&insn.to_le_bytes());
}
let src_base = 0x140_0010_0000u64;
let dst_base = 0x140_0030_0000u64;
let resume = src_base + STOLEN_BYTES as u64;
let body = assemble_trampoline(&prologue, src_base, dst_base, resume).expect("relocatable");
assert_eq!(body.len(), 32);
assert_eq!(u32::from_le_bytes(body[0..4].try_into().unwrap()), stp);
assert_eq!(u32::from_le_bytes(body[4..8].try_into().unwrap()), mov);
assert_eq!(u32::from_le_bytes(body[12..16].try_into().unwrap()), NOP);
let orig_target = adr_target(adrp_insn, src_base + 8, true);
let reloc_adrp = u32::from_le_bytes(body[8..12].try_into().unwrap());
assert_eq!(adr_target(reloc_adrp, dst_base + 8, true), orig_target);
assert_eq!(
u32::from_le_bytes(body[16..20].try_into().unwrap()),
LDR_X16_PC8
);
assert_eq!(u32::from_le_bytes(body[20..24].try_into().unwrap()), BR_X16);
assert_eq!(u64::from_le_bytes(body[24..32].try_into().unwrap()), resume);
}
#[test]
fn assemble_trampoline_promotes_leading_branch() {
let lead_b = b(0x4000);
let mut prologue = Vec::new();
for insn in [lead_b, NOP, NOP, NOP] {
prologue.extend_from_slice(&insn.to_le_bytes());
}
let src_base = 0x140_0010_0000u64;
let dst_base = 0x0000_7000_0000u64;
let resume = src_base + STOLEN_BYTES as u64;
let body = assemble_trampoline(&prologue, src_base, dst_base, resume).expect("island path");
assert_eq!(body.len(), 48);
let first = u32::from_le_bytes(body[0..4].try_into().unwrap());
assert!(is_b(first));
let island_addr = branch_target(first, dst_base).unwrap();
assert_eq!(island_addr, dst_base + 16 + 16);
let original_b_target = branch_target(lead_b, src_base).unwrap();
assert_eq!(
u32::from_le_bytes(body[32..36].try_into().unwrap()),
LDR_X16_PC8
);
assert_eq!(u32::from_le_bytes(body[36..40].try_into().unwrap()), BR_X16);
assert_eq!(
u64::from_le_bytes(body[40..48].try_into().unwrap()),
original_b_target
);
}
#[test]
fn assemble_trampoline_promotes_leading_bl_with_blr() {
let lead_bl = bl(0x100);
let mut prologue = Vec::new();
for insn in [lead_bl, NOP, NOP, NOP] {
prologue.extend_from_slice(&insn.to_le_bytes());
}
let src_base = 0x140_0010_0000u64;
let dst_base = 0x0000_7000_0000u64;
let resume = src_base + STOLEN_BYTES as u64;
let body = assemble_trampoline(&prologue, src_base, dst_base, resume).expect("island path");
assert_eq!(body.len(), 48);
let first = u32::from_le_bytes(body[0..4].try_into().unwrap());
assert!(is_bl(first));
let original = branch_target(lead_bl, src_base).unwrap();
assert_eq!(
u32::from_le_bytes(body[36..40].try_into().unwrap()),
BLR_X16
);
assert_eq!(
u64::from_le_bytes(body[40..48].try_into().unwrap()),
original
);
}
#[test]
fn assemble_trampoline_two_islands() {
let b0 = b(0x10);
let bl1 = bl(0x20);
let mut prologue = Vec::new();
for insn in [b0, NOP, bl1, NOP] {
prologue.extend_from_slice(&insn.to_le_bytes());
}
let src_base = 0x140_0010_0000u64;
let dst_base = 0x0000_7000_0000u64;
let resume = src_base + STOLEN_BYTES as u64;
let body = assemble_trampoline(&prologue, src_base, dst_base, resume).expect("islands");
assert_eq!(body.len(), 64);
let first = u32::from_le_bytes(body[0..4].try_into().unwrap());
let third = u32::from_le_bytes(body[8..12].try_into().unwrap());
let island0 = branch_target(first, dst_base).unwrap();
let island1 = branch_target(third, dst_base + 8).unwrap();
assert_eq!(island0, dst_base + 32);
assert_eq!(island1, dst_base + 48);
assert_eq!(
u64::from_le_bytes(body[40..48].try_into().unwrap()),
branch_target(b0, src_base).unwrap()
);
assert_eq!(
u64::from_le_bytes(body[56..64].try_into().unwrap()),
branch_target(bl1, src_base + 8).unwrap()
);
}
#[test]
fn assemble_trampoline_refuses_unrelocatable_narrow_branch() {
let cond = bcond(0x2, 0x10);
let mut prologue = Vec::new();
for insn in [NOP, cond, NOP, NOP] {
prologue.extend_from_slice(&insn.to_le_bytes());
}
let src_base = 0x0u64;
let dst_base = 0x0000_0080_0000u64;
let resume = src_base + STOLEN_BYTES as u64;
assert_eq!(
assemble_trampoline(&prologue, src_base, dst_base, resume),
None
);
}
#[test]
fn relocated_prologue_matches_assemble_prefix() {
let stp = 0xA9BF_7BFDu32;
let mut prologue = Vec::new();
for insn in [stp, NOP, NOP, NOP] {
prologue.extend_from_slice(&insn.to_le_bytes());
}
let src_base = 0x140_0010_0000u64;
let dst_base = 0x140_0030_0000u64;
let pro = relocated_prologue(&prologue, src_base, dst_base).expect("ok");
assert_eq!(pro.len(), 16);
assert_eq!(u32::from_le_bytes(pro[0..4].try_into().unwrap()), stp);
}
#[test]
fn island_holds_absolute_branch_to_original_target() {
let lead_bl = bl(0x100);
let src_base = 0x140_0010_0000u64;
let original_target = branch_target(lead_bl, src_base).unwrap();
let island = island_for_branch(lead_bl, src_base).expect("island");
assert_eq!(island.len(), 16);
assert_eq!(
u32::from_le_bytes(island[4..8].try_into().unwrap()),
BLR_X16
);
assert_eq!(
u64::from_le_bytes(island[8..16].try_into().unwrap()),
original_target
);
}
#[test]
fn stolen_bytes_is_four_instructions() {
assert_eq!(STOLEN_BYTES, 16);
assert_eq!(STOLEN_BYTES % 4, 0);
}
#[test]
fn encode_imm26_rejects_unaligned_and_overflow() {
assert_eq!(encode_imm26(3), None);
assert_eq!(encode_imm26(4), Some(1));
assert_eq!(encode_imm26(-4), Some((-1i32 as u32) & 0x03FF_FFFF));
assert_eq!(encode_imm26(1 << 27), None);
}