1#![feature(fmt_helpers_for_derive)]
2use std::sync::{LazyLock, OnceLock};
62
63#[cfg(not(target_arch = "x86_64"))]
64compile_error!("only supported on x86_64");
65
66struct Pos(*const u8);
67unsafe impl Send for Pos {}
68unsafe impl Sync for Pos {}
69static MATCHES: OnceLock<Vec<Pos>> = OnceLock::new();
70
71macro_rules! patch_slice {
72 ($ty:ty, $on:expr, $check:literal) => { {
73 static DETOUR: LazyLock<retour::RawDetour> = LazyLock::new(|| unsafe {
74 type Hook = for<'a, 'b, 'c> fn(&'a [$ty], &'b mut std::fmt::Formatter<'c>) -> std::fmt::Result;
75 #[cfg($check)]
76 #[expect(unused)]
77 static ORIG_FUNC: Hook = <[$ty] as std::fmt::Debug>::fmt;
78 static HOOK: Hook = |elf, f| {
79 write!(f, "[")?;
80 for (i, v) in elf.iter().enumerate() {
81 if i != 0 {
82 write!(f, ", ")?;
83 }
84 std::fmt::Debug::fmt(v, f)?;
85 }
86 write!(f, "]")
87 };
88 retour::RawDetour::new(<[$ty] as std::fmt::Debug>::fmt as *const (), HOOK as *const ()).unwrap()
89 });
90 if $on {
91 DETOUR.enable().unwrap();
92 } else {
93 DETOUR.disable().unwrap();
94 }
95 } }
96}
97
98pub unsafe fn enable(on: bool) {
108 unsafe {
109 let matches = MATCHES.get_or_init(find_all);
110
111 for Pos(ptr) in matches {
112 let ptr = ptr.cast_mut();
113 let _prot = region::protect_with_handle(ptr, 1, region::Protection::READ_WRITE_EXECUTE)
114 .unwrap();
115 ptr.write(if on { 0 } else { 0x80 });
116 }
117
118 patch_slice!(u8, on, true);
119 patch_slice!(u16, on, true);
120 patch_slice!(u32, on, true);
121 patch_slice!(u64, on, true);
122 patch_slice!(u128, on, true);
123 patch_slice!(usize, on, true);
124 patch_slice!(i8, on, true);
125 patch_slice!(i16, on, true);
126 patch_slice!(i32, on, true);
127 patch_slice!(i64, on, true);
128 patch_slice!(i128, on, true);
129 patch_slice!(isize, on, true);
130 patch_slice!(f32, on, true);
131 patch_slice!(f64, on, true);
132 patch_slice!(bool, on, true);
133 patch_slice!(char, on, true);
134 patch_slice!(&str, on, false); patch_slice!(String, on, true);
136 }
137}
138
139fn find_all() -> Vec<Pos> {
140 unsafe {
141 let mut out = Vec::new();
142 macro_rules! find {
143 ($name:path) => {
144 do_find(&mut out, stringify!($name), $name as *const () as *const u8);
145 };
146 }
147 find!(std::fmt::DebugTuple::field);
148 find!(std::fmt::DebugTuple::finish);
149 find!(std::fmt::DebugTuple::finish_non_exhaustive);
150 find!(std::fmt::Formatter::debug_tuple_field1_finish);
151 find!(std::fmt::Formatter::debug_tuple_field2_finish);
152 find!(std::fmt::Formatter::debug_tuple_field3_finish);
153 find!(std::fmt::Formatter::debug_tuple_field4_finish);
154 find!(std::fmt::Formatter::debug_tuple_field5_finish);
155 find!(std::fmt::Formatter::debug_tuple_fields_finish);
156
157 assert!(out.iter().all(|x| x.0 == out[0].0), "field offsets differ");
159
160 out.into_iter().map(|x| Pos(x.1)).collect()
161 }
162}
163
164unsafe fn do_find(out: &mut Vec<(u8, *const u8)>, name: &str, mut ptr: *const u8) {
167 let n = out.len();
168 loop {
169 if (ptr as usize & 0xF) == 0xF && (*ptr == 0xC3 || *ptr == 0xCC) {
170 break;
171 }
172 if *ptr == 0xF6 && *ptr.add(1) & 0xF0 == 0x40 && *ptr.add(3) == 0x80 {
173 out.push((*ptr.add(2), ptr.add(3)));
174 }
175 ptr = ptr.add(1);
176 }
177 assert!(out.len() > n, "no matches found for {name}");
178}
179
180#[test]
183fn test() {
184 #[derive(Debug)]
185 #[allow(dead_code)]
186 struct A(u32, u32);
187
188 #[allow(dead_code)]
189 #[derive(Debug)]
190 struct B {
191 x: u32,
192 y: u32,
193 }
194
195 #[allow(dead_code)]
196 #[derive(Debug)]
197 enum Enum {
198 A,
199 B(u32),
200 C(u32, u32),
201 D(u32, u32, u32),
202 E(u32, u32, u32, u32),
203 F(u32, u32, u32, u32, u32),
204 }
205
206 let a = A(8, 32);
207 let b = B { x: 8, y: 32 };
208
209 assert_eq!(format!("{a:?}"), "A(8, 32)");
210 assert_eq!(format!("{a:#?}"), "A(\n 8,\n 32,\n)");
211 assert_eq!(format!("{b:?}"), "B { x: 8, y: 32 }");
212 assert_eq!(format!("{b:#?}"), "B {\n x: 8,\n y: 32,\n}");
213
214 unsafe { enable(true) };
215
216 assert_eq!(format!("{a:?}"), "A(8, 32)");
217 assert_eq!(format!("{a:#?}"), "A(8, 32)");
218 assert_eq!(format!("{b:?}"), "B { x: 8, y: 32 }");
219 assert_eq!(format!("{b:#?}"), "B {\n x: 8,\n y: 32,\n}");
220
221 assert!(!format!("{:#?}", Enum::A).contains('\n'));
222 assert!(!format!("{:#?}", Enum::B(0)).contains('\n'));
223 assert!(!format!("{:#?}", Enum::C(0, 0)).contains('\n'));
224 assert!(!format!("{:#?}", Enum::D(0, 0, 0)).contains('\n'));
225 assert!(!format!("{:#?}", Enum::E(0, 0, 0, 0)).contains('\n'));
226 assert!(!format!("{:#?}", Enum::F(0, 0, 0, 0, 0)).contains('\n'));
227
228 unsafe { enable(false) };
229
230 assert_eq!(format!("{a:?}"), "A(8, 32)");
231 assert_eq!(format!("{a:#?}"), "A(\n 8,\n 32,\n)");
232 assert_eq!(format!("{b:?}"), "B { x: 8, y: 32 }");
233 assert_eq!(format!("{b:#?}"), "B {\n x: 8,\n y: 32,\n}");
234
235 test_list([1u8, 2, 3, 4, 5], "[\n 1,\n 2,\n 3,\n 4,\n 5,\n]", "[1, 2, 3, 4, 5]");
236 test_list([123u16], "[\n 123,\n]", "[123]");
237 test_list([123u32, 456], "[\n 123,\n 456,\n]", "[123, 456]");
238 test_list([123u64, 456, 789], "[\n 123,\n 456,\n 789,\n]", "[123, 456, 789]");
239 test_list([123u128, 456, 789, 101112], "[\n 123,\n 456,\n 789,\n 101112,\n]", "[123, 456, 789, 101112]");
240 test_list([1i32, 2, 3, 4, 5], "[\n 1,\n 2,\n 3,\n 4,\n 5,\n]", "[1, 2, 3, 4, 5]");
241 test_list(["one", "two", "three", "four", "five"], "[\n \"one\",\n \"two\",\n \"three\",\n \"four\",\n \"five\",\n]", "[\"one\", \"two\", \"three\", \"four\", \"five\"]");
242 test_list([(1, 2), (3, 4), (5, 6)], "[\n (\n 1,\n 2,\n ),\n (\n 3,\n 4,\n ),\n (\n 5,\n 6,\n ),\n]", "[\n (1, 2),\n (3, 4),\n (5, 6),\n]");
243}
244
245#[cfg(test)]
246fn test_list<T: std::fmt::Debug + Clone, const N: usize>(value: [T; N], long: &str, short: &str) {
247 let array = value;
248 let array_ref = &array;
249 let slice_ref = &array as &[_];
250 let vec = array.to_vec();
251
252 assert_eq!(format!("{array:#?}"), long);
253 assert_eq!(format!("{array_ref:#?}"), long);
254 assert_eq!(format!("{slice_ref:#?}"), long);
255 assert_eq!(format!("{vec:#?}"), long);
256
257 unsafe { enable(true) };
258
259 assert_eq!(format!("{array:#?}"), short);
260 assert_eq!(format!("{array_ref:#?}"), short);
261 assert_eq!(format!("{slice_ref:#?}"), short);
262 assert_eq!(format!("{vec:#?}"), short);
263
264 unsafe { enable(false) };
265
266 assert_eq!(format!("{array:#?}"), long);
267 assert_eq!(format!("{array_ref:#?}"), long);
268 assert_eq!(format!("{slice_ref:#?}"), long);
269 assert_eq!(format!("{vec:#?}"), long);
270}