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.
413 lines
13 KiB
Rust
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);
|
|
}
|