1pub mod solana;
8
9use std::path::Path;
10
11use ud_core::{assert_bytes_equal, Error, Result};
12use ud_translate::compile::AsmWarning;
13
14pub fn roundtrip(input: &Path, output: &Path) -> Result<()> {
30 let bytes = std::fs::read(input).map_err(|source| Error::Io {
31 path: input.to_path_buf(),
32 source,
33 })?;
34
35 let rebuilt = pipeline_bytes(&bytes);
36
37 std::fs::write(output, &rebuilt).map_err(|source| Error::Io {
38 path: output.to_path_buf(),
39 source,
40 })?;
41
42 let written_back = std::fs::read(output).map_err(|source| Error::Io {
43 path: output.to_path_buf(),
44 source,
45 })?;
46
47 assert_bytes_equal(&bytes, &written_back)
48}
49
50fn pipeline_bytes(bytes: &[u8]) -> Vec<u8> {
54 if ud_format::elf::is_elf64_le(bytes) {
55 if let Ok(elf) = ud_format::elf::Elf64File::parse(bytes) {
56 return elf.write_to_vec();
57 }
58 }
61 if ud_format::pe::is_pe(bytes) {
62 if let Ok(pe) = ud_format::pe::PeFile::parse(bytes) {
63 return pe.write_to_vec();
64 }
65 }
67 if ud_format::macho::is_macho64(bytes) {
68 if let Ok(macho) = ud_format::macho::MachoFile::parse(bytes) {
69 return macho.write_to_vec();
70 }
71 }
74 if ud_format::wasm::is_wasm(bytes) {
75 if let Ok(wasm) = ud_format::wasm::WasmFile::parse(bytes) {
76 return wasm.write_to_vec();
77 }
78 }
80 bytes.to_vec()
81}
82
83#[derive(Debug, Clone)]
85pub struct SourceRoundTripReport {
86 pub byte_identical: bool,
88 pub input_len: usize,
90 pub output_len: usize,
92 pub first_diff_offset: Option<usize>,
95 pub diff_context: Option<DiffContext>,
98 pub warnings: Vec<AsmWarning>,
103}
104
105#[derive(Debug, Clone)]
108pub struct DiffContext {
109 pub window_start: usize,
110 pub input_window: Vec<u8>,
111 pub output_window: Vec<u8>,
112}
113
114#[derive(Debug, thiserror::Error)]
115pub enum SourceRoundTripError {
116 #[error("input is not a recognised binary format")]
117 UnknownFormat,
118 #[error(transparent)]
119 Io(std::io::Error),
120 #[error(transparent)]
121 Decompile(#[from] ud_translate::decompile::Error),
122 #[error(transparent)]
123 Decompile6502(#[from] ud_translate::decompile::raw6502::Error),
124 #[error(transparent)]
125 ElfFormat(#[from] ud_format::elf::Error),
126 #[error(transparent)]
127 PeFormat(#[from] ud_format::pe::Error),
128 #[error(transparent)]
129 MachoFormat(#[from] ud_format::macho::Error),
130 #[error(transparent)]
131 WasmFormat(#[from] ud_format::wasm::Error),
132 #[error("parse of decompile output failed: {0}")]
133 Parse(String),
134 #[error(transparent)]
135 ElfLower(#[from] ud_translate::compile::ElfLowerError),
136 #[error(transparent)]
137 PeLower(#[from] ud_translate::compile::PeLowerError),
138 #[error(transparent)]
139 MachoLower(#[from] ud_translate::compile::MachoLowerError),
140 #[error(transparent)]
141 RawLower(#[from] ud_translate::compile::RawLowerError),
142 #[error(transparent)]
143 WasmLower(#[from] ud_translate::compile::WasmLowerError),
144}
145
146pub fn roundtrip_through_source(
154 input: &Path,
155 output: &Path,
156) -> std::result::Result<SourceRoundTripReport, SourceRoundTripError> {
157 let input_bytes = std::fs::read(input).map_err(SourceRoundTripError::Io)?;
158
159 let (text, warnings, rebuilt) = if ud_format::elf::is_elf64_le(&input_bytes) {
160 let elf = ud_format::elf::Elf64File::parse(&input_bytes)?;
161 let ast = ud_translate::decompile::decompile(&elf)?;
162 let text = ud_ast::emit(&ast);
163 let parsed = ud_translate::compile::parse(&text)
164 .map_err(|e| SourceRoundTripError::Parse(e.to_string()))?;
165 let warnings = ud_translate::compile::verify_asm(&parsed);
166 let rebuilt = ud_translate::compile::lower_to_elf(&parsed)?;
167 (text, warnings, rebuilt)
168 } else if ud_format::pe::is_pe(&input_bytes) {
169 let pe = ud_format::pe::PeFile::parse(&input_bytes)?;
170 let ast = ud_translate::decompile::decompile_pe(&pe);
171 let text = ud_ast::emit(&ast);
172 let parsed = ud_translate::compile::parse(&text)
173 .map_err(|e| SourceRoundTripError::Parse(e.to_string()))?;
174 let warnings = ud_translate::compile::verify_asm(&parsed);
175 let rebuilt = ud_translate::compile::lower_to_pe(&parsed)?;
176 (text, warnings, rebuilt)
177 } else if ud_format::macho::is_macho64(&input_bytes) {
178 let macho = ud_format::macho::MachoFile::parse(&input_bytes)?;
179 let ast = ud_translate::decompile::decompile_macho(&macho);
180 let text = ud_ast::emit(&ast);
181 let parsed = ud_translate::compile::parse(&text)
182 .map_err(|e| SourceRoundTripError::Parse(e.to_string()))?;
183 let warnings = ud_translate::compile::verify_asm(&parsed);
184 let rebuilt = ud_translate::compile::lower_to_macho(&parsed)?;
185 (text, warnings, rebuilt)
186 } else if ud_format::wasm::is_wasm(&input_bytes) {
187 let wasm = ud_format::wasm::WasmFile::parse(&input_bytes)?;
188 let ast = ud_translate::decompile::decompile_wasm(&wasm);
189 let text = ud_ast::emit(&ast);
190 let parsed = ud_translate::compile::parse(&text)
191 .map_err(|e| SourceRoundTripError::Parse(e.to_string()))?;
192 let warnings = ud_translate::compile::verify_asm(&parsed);
193 let rebuilt = ud_translate::compile::lower_to_wasm(&parsed)?;
194 (text, warnings, rebuilt)
195 } else if let Some(load_addr) = raw_6502_load_addr(&input_bytes) {
196 let image = ud_format::raw::RawImage::new(input_bytes.clone(), load_addr);
197 let ast = ud_translate::decompile::decompile_raw_6502(&image)?;
198 let text = ud_ast::emit(&ast);
199 let parsed = ud_translate::compile::parse(&text)
200 .map_err(|e| SourceRoundTripError::Parse(e.to_string()))?;
201 let warnings = ud_translate::compile::verify_asm(&parsed);
202 let rebuilt = ud_translate::compile::lower_to_raw(&parsed)?;
203 (text, warnings, rebuilt)
204 } else {
205 return Err(SourceRoundTripError::UnknownFormat);
206 };
207 let _ = text; std::fs::write(output, &rebuilt).map_err(SourceRoundTripError::Io)?;
210
211 let first_diff_offset = first_byte_diff(&input_bytes, &rebuilt);
212 let diff_context = first_diff_offset.map(|off| make_diff_context(off, &input_bytes, &rebuilt));
213 Ok(SourceRoundTripReport {
214 byte_identical: first_diff_offset.is_none() && input_bytes.len() == rebuilt.len(),
215 input_len: input_bytes.len(),
216 output_len: rebuilt.len(),
217 first_diff_offset,
218 diff_context,
219 warnings,
220 })
221}
222
223fn make_diff_context(off: usize, input: &[u8], output: &[u8]) -> DiffContext {
224 let window_start = off.saturating_sub(8);
225 let window_end_in = (off + 8).min(input.len());
226 let window_end_out = (off + 8).min(output.len());
227 DiffContext {
228 window_start,
229 input_window: input[window_start..window_end_in].to_vec(),
230 output_window: output[window_start..window_end_out].to_vec(),
231 }
232}
233
234#[must_use]
244pub fn raw_6502_load_addr(bytes: &[u8]) -> Option<u64> {
245 let len = bytes.len();
246 if !(6..=0x10000).contains(&len) {
247 return None;
248 }
249 let load_addr = 0x10000u64 - len as u64;
250 let end = 0x10000u64;
251 let reset_lo_off = usize::try_from(0xFFFCu64 - load_addr).ok()?;
252 let reset_hi_off = reset_lo_off + 1;
253 if reset_hi_off >= len {
254 return None;
255 }
256 let reset = u64::from(u16::from_le_bytes([
257 bytes[reset_lo_off],
258 bytes[reset_hi_off],
259 ]));
260 if reset >= load_addr && reset < end {
261 Some(load_addr)
262 } else {
263 None
264 }
265}
266
267fn first_byte_diff(a: &[u8], b: &[u8]) -> Option<usize> {
268 a.iter()
269 .zip(b)
270 .position(|(x, y)| x != y)
271 .or_else(|| (a.len() != b.len()).then_some(a.len().min(b.len())))
272}
273
274#[cfg(test)]
275mod tests {
276 use super::*;
277
278 #[test]
279 fn pipeline_passes_through_non_elf_bytes() {
280 let bytes = b"\x00\x01\x02\x03not an elf";
281 assert_eq!(pipeline_bytes(bytes), bytes);
282 }
283
284 #[test]
285 fn pipeline_passes_through_elf32() {
286 let mut bytes = vec![0u8; 64];
288 bytes[..4].copy_from_slice(b"\x7fELF");
289 bytes[4] = 1; bytes[5] = 1; let out = pipeline_bytes(&bytes);
292 assert_eq!(out, bytes);
293 }
294
295 #[test]
296 fn roundtrip_on_a_temp_file_succeeds() {
297 let dir = std::env::temp_dir();
298 let input = dir.join("ud-cli-rt-in");
299 let output = dir.join("ud-cli-rt-out");
300 std::fs::write(&input, b"\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00").unwrap();
301 roundtrip(&input, &output).expect("identity round-trip should succeed");
302 let _ = std::fs::remove_file(&input);
303 let _ = std::fs::remove_file(&output);
304 }
305}