Skip to main content

frust_hotpatch/
jump_table.rs

1// Ported from subsecond-types 0.7.10 (DioxusLabs, MIT OR Apache-2.0),
2// https://github.com/DioxusLabs/dioxus/tree/main/packages/subsecond.
3//
4//! The jump table a patch builder sends: the wire type dx 0.7.10 serializes inside its devserver
5//! `HotReloadMsg`. Field names and types match `subsecond_types::JumpTable` exactly, so the JSON
6//! dx emits deserializes into [`JumpTable`] unchanged (the `wire_compat` test below pins that).
7
8use std::collections::HashMap;
9use std::hash::{BuildHasherDefault, Hasher};
10use std::path::PathBuf;
11
12use serde::{Deserialize, Serialize};
13
14/// One patch: the library holding the new code and the old -> new address map into it.
15#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
16pub struct JumpTable {
17    /// The patch dylib, a path the platform loader can open directly.
18    pub lib: PathBuf,
19
20    /// Old function address -> new function address, both as link-time addresses: neither the
21    /// running binary's ASLR slide nor the patch's load address is applied yet
22    /// ([`apply_patch`](crate::apply_patch) rebases both sides).
23    pub map: AddressMap,
24
25    /// Link-time address of the running binary's `main`, the anchor that recovers its ASLR slide.
26    pub aslr_reference: u64,
27
28    /// Link-time address of the patch library's `main`, the anchor that recovers its load address.
29    pub new_base_address: u64,
30
31    /// Indirect-function count; only a wasm loader reads it, kept so the wire shape is identical.
32    pub ifunc_count: u64,
33}
34
35/// An address -> address map that does not hash its keys: addresses are already unique.
36pub type AddressMap = HashMap<u64, u64, BuildAddressHasher>;
37
38/// The [`AddressMap`] hasher builder.
39pub type BuildAddressHasher = BuildHasherDefault<AddressHasher>;
40
41/// Identity hasher for integer keys; [`AddressMap`] only ever feeds it a `u64`.
42#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
43pub struct AddressHasher(u64);
44
45impl Hasher for AddressHasher {
46    fn write(&mut self, _: &[u8]) {
47        panic!("AddressHasher only hashes integer keys")
48    }
49    fn write_u8(&mut self, n: u8) {
50        self.0 = u64::from(n)
51    }
52    fn write_u16(&mut self, n: u16) {
53        self.0 = u64::from(n)
54    }
55    fn write_u32(&mut self, n: u32) {
56        self.0 = u64::from(n)
57    }
58    fn write_u64(&mut self, n: u64) {
59        self.0 = n
60    }
61    fn write_usize(&mut self, n: usize) {
62        self.0 = n as u64
63    }
64    fn write_i8(&mut self, n: i8) {
65        self.0 = n as u64
66    }
67    fn write_i16(&mut self, n: i16) {
68        self.0 = n as u64
69    }
70    fn write_i32(&mut self, n: i32) {
71        self.0 = n as u64
72    }
73    fn write_i64(&mut self, n: i64) {
74        self.0 = n as u64
75    }
76    fn write_isize(&mut self, n: isize) {
77        self.0 = n as u64
78    }
79    fn finish(&self) -> u64 {
80        self.0
81    }
82}
83
84#[cfg(test)]
85mod tests {
86    use super::*;
87
88    fn upstream_table() -> subsecond_types::JumpTable {
89        let mut map = subsecond_types::AddressMap::default();
90        map.insert(0x1_0000_1234, 0x4567);
91        map.insert(0x1_0000_9abc, 0xdef0);
92        map.insert(u64::MAX, 0);
93        subsecond_types::JumpTable {
94            lib: PathBuf::from("/tmp/patch-1.dylib"),
95            map,
96            aslr_reference: 0x1_0000_0000,
97            new_base_address: 0x2000,
98            ifunc_count: 3,
99        }
100    }
101
102    #[test]
103    fn wire_compat_with_subsecond_types() {
104        let upstream = upstream_table();
105        let json = serde_json::to_value(&upstream).expect("upstream table serializes");
106
107        let ours: JumpTable = serde_json::from_value(json.clone()).expect("dx's JSON deserializes");
108        assert_eq!(ours.lib, upstream.lib);
109        assert_eq!(ours.aslr_reference, upstream.aslr_reference);
110        assert_eq!(ours.new_base_address, upstream.new_base_address);
111        assert_eq!(ours.ifunc_count, upstream.ifunc_count);
112        assert_eq!(ours.map.len(), upstream.map.len());
113        for (old, new) in &upstream.map {
114            assert_eq!(ours.map.get(old), Some(new));
115        }
116
117        // Back out: the JSON value is equal (map key order is not part of a JSON object identity).
118        let back = serde_json::to_value(&ours).expect("our table serializes");
119        assert_eq!(back, json);
120        let round: subsecond_types::JumpTable =
121            serde_json::from_value(back).expect("upstream reads our JSON");
122        assert_eq!(round, upstream);
123    }
124
125    #[test]
126    fn address_hasher_is_identity() {
127        let mut h = AddressHasher::default();
128        h.write_u64(0xdead_beef);
129        assert_eq!(h.finish(), 0xdead_beef);
130    }
131}