fugue_radare/
lib.rs

1//! Fugue import/export glue for Radare and Rizin.
2//!
3//! Example use:
4//! ```rust,ignore
5//! use fugue::db::DatabaseImporter;
6//! use fugue::ir::LanguageDB;
7//!
8//! let ldb = LanguageDB::from_directory_with("path/to/processors", true)?;
9//! let mut dbi = DatabaseImporter::new("/bin/ls");
10//!
11//! dbi.register_backend(Radare::new_rizin()?);
12//!
13//! let db = dbi.import(&ldb)?;
14//! ```
15
16use std::collections::{HashMap, HashSet};
17use std::fs::File;
18use std::io::Write;
19use std::ops::Deref;
20use std::path::{Path, PathBuf};
21
22use fugue_db::backend::{Backend, Imported};
23use fugue_db::Error as ExportError;
24use iset::IntervalSet;
25use itertools::Itertools;
26use r2pipe::R2Pipe;
27pub use r2pipe::R2PipeSpawnOptions;
28use serde::Deserialize;
29use thiserror::Error;
30use url::Url;
31use which::{which, which_in};
32
33mod schema;
34
35#[derive(Debug, Error)]
36pub enum Error {
37    #[error("r2/rizin is not available as a backend")]
38    NotAvailable,
39    #[error("invalid path to r2/rizin: {0}")]
40    InvalidPath(which::Error),
41    #[error("invalid import URL: {0}")]
42    InvalidImportPath(String),
43    #[error("invalid shared memory mapping: {0}")]
44    InvalidImportShm(shared_memory::ShmemError),
45    #[error("invalid file-system path: {0}")]
46    InvalidImportFile(std::io::Error),
47    #[error("invalid segment")]
48    InvalidSegment,
49    #[error("coult not export to file: {0}")]
50    CannotExportToFile(std::io::Error),
51    #[error("coult not map file: {0}")]
52    CannotMapFile(std::io::Error),
53    #[error("could not deserialise output from `{0}`: {1}")]
54    Deserialise(&'static str, #[source] serde_json::Error),
55    #[error("pipe communication error: {0}")]
56    Pipe(#[from] r2pipe::Error),
57    #[error("unsupported architecture: {0}")]
58    UnsupportedArch(String),
59    #[error("unsupported format: {0}")]
60    UnsupportedFormat(String),
61    #[error("unsupported URL scheme: {0}")]
62    UnsupportedScheme(String),
63}
64
65impl From<Error> for ExportError {
66    fn from(e: Error) -> Self {
67        ExportError::importer_error("fugue-radare", e)
68    }
69}
70
71#[derive(Debug, Deserialize)]
72struct MetadataBin<'d> {
73    arch: &'d str,
74    bintype: &'d str,
75    bits: u32,
76    endian: &'d str,
77}
78
79#[derive(Debug, Deserialize)]
80struct MetadataCore<'d> {
81    file: &'d str,
82    #[allow(unused)]
83    size: u32,
84}
85
86#[derive(Debug, Deserialize)]
87struct MetadataHashes<'d> {
88    #[allow(unused)]
89    md5: &'d str,
90    #[allow(unused)]
91    sha1: &'d str,
92    #[allow(unused)]
93    sha256: &'d str,
94}
95
96#[derive(Debug, Deserialize)]
97struct Metadata<'d> {
98    #[serde(borrow)]
99    core: MetadataCore<'d>,
100    #[serde(borrow)]
101    bin: MetadataBin<'d>,
102}
103
104impl<'d> Metadata<'d> {
105    fn format(format: &str) -> Result<&'static str, Error> {
106        Ok(match format {
107            "elf" | "elf64" => "ELF",
108            "mach0" | "mach064" => "Mach-O",
109            "pe" | "pe64" => "PE",
110            "te" => "TE",
111            "any" => "Raw",
112            _ => return Err(Error::UnsupportedFormat(format.to_string())),
113        })
114    }
115
116    fn processor(processor: &str, bits: u32) -> Result<(&'static str, &'static str), Error> {
117        Ok(match processor {
118            "arm" => {
119                if bits == 64 {
120                    ("AARCH64", "v8A")
121                } else {
122                    ("ARM", "v7")
123                }
124            }
125            "mips" => ("MIPS", "default"),
126            "ppc" => ("PowerPC", "default"),
127            "x86" => ("x86", "default"), // TODO: handle 16-bit variants
128            _ => return Err(Error::UnsupportedArch(processor.to_string())),
129        })
130    }
131
132    fn endian(endian: &str) -> bool {
133        endian != "little" && endian != "LE"
134    }
135}
136
137#[derive(Debug, Deserialize)]
138struct Version<'d> {
139    name: &'d str,
140    version: &'d str,
141}
142
143#[derive(Debug, Deserialize)]
144struct SegmentInfo<'d> {
145    name: &'d str,
146    size: u32,
147    vsize: u32,
148    perm: &'d str,
149    #[serde(default)]
150    paddr: u64,
151    vaddr: i128,
152}
153
154impl<'d> SegmentInfo<'d> {
155    fn is_executable(&self) -> bool {
156        self.perm.contains("x")
157    }
158
159    fn is_readable(&self) -> bool {
160        self.perm.contains("r")
161    }
162
163    fn is_writable(&self) -> bool {
164        self.perm.contains("w")
165    }
166
167    fn address(&self) -> Option<u64> {
168        if self.vaddr < 0 || self.vaddr > u64::MAX as i128 {
169            None
170        } else {
171            Some(self.vaddr as u64)
172        }
173    }
174
175    fn is_code(&self) -> bool {
176        self.is_executable()
177    }
178
179    fn is_data(&self) -> bool {
180        !self.is_code()
181    }
182}
183
184#[derive(Debug, Deserialize)]
185struct Symbol<'d> {
186    realname: &'d str, // imported should match name of relocation?
187    #[serde(rename = "type")]
188    kind: &'d str,
189    #[serde(default)]
190    vaddr: u64,
191    #[serde(default)]
192    #[allow(unused)]
193    paddr: u64,
194    is_imported: bool,
195}
196
197#[derive(Debug, Deserialize)]
198struct Relocation<'d> {
199    name: Option<&'d str>,
200    vaddr: u64,
201}
202
203#[derive(Debug, Deserialize)]
204struct InterRef<'d> {
205    from: u64, // source
206    #[serde(rename = "type")]
207    kind: &'d str, // we care about "CALL"
208    fcn_addr: Option<u64>, // source fcn
209}
210
211#[derive(Debug, Default, Deserialize)]
212struct BasicBlock {
213    addr: u64,
214    size: u32,
215    jump: Option<u64>,
216    fail: Option<u64>,
217}
218
219#[derive(Debug, Deserialize)]
220struct Function<'d> {
221    offset: u64,
222    name: &'d str, // source fcn
223}
224
225pub enum Backing {
226    M(File, memmap2::Mmap),
227    S(shared_memory::Shmem),
228}
229
230impl Deref for Backing {
231    type Target = [u8];
232
233    fn deref(&self) -> &Self::Target {
234        match self {
235            Self::M(_, ref m) => m.deref(),
236            Self::S(ref s) => unsafe { s.as_slice() },
237        }
238    }
239}
240
241pub struct RadareExporter<'db> {
242    config: R2PipeSpawnOptions,
243
244    builder: flatbuffers::FlatBufferBuilder<'db>,
245    pipe: R2Pipe,
246
247    backing: Backing,
248
249    endian: bool, // little = false; big = true
250    address_size: u32,
251    bits: u32,
252}
253
254impl<'db> RadareExporter<'db> {
255    pub fn new_with<P: AsRef<str>>(path: P, mut config: R2PipeSpawnOptions) -> Result<Self, Error> {
256        if config.exepath.is_empty() {
257            config.exepath.push_str("r2");
258        }
259
260        let path = path.as_ref();
261
262        let backing = if let Some(id) = path
263            .strip_prefix("shm:/")
264            .and_then(|rest| rest.rsplit_once("/").map(|(v, _)| v))
265        {
266            let sc = shared_memory::ShmemConf::new().os_id(id);
267            Backing::S(sc.open().map_err(Error::InvalidImportShm)?)
268        } else {
269            let path = path.strip_prefix("file://").unwrap_or(&path);
270            let file = File::open(path).map_err(Error::InvalidImportFile)?;
271
272            let mm = unsafe { memmap2::Mmap::map(&file) }.map_err(Error::CannotMapFile)?;
273
274            Backing::M(file, mm)
275        };
276
277        Ok(Self {
278            builder: flatbuffers::FlatBufferBuilder::new(),
279            pipe: R2Pipe::spawn(path, Some(config.clone()))?,
280            config,
281            backing,
282            endian: Default::default(),
283            address_size: Default::default(),
284            bits: Default::default(),
285        })
286    }
287
288    pub fn new<P: AsRef<str>>(path: P) -> Result<Self, Error> {
289        Self::new_with(path, Default::default())
290    }
291
292    pub fn analyse_with<S: AsRef<str>>(&mut self, command: S) -> Result<(), Error> {
293        self.pipe
294            .cmd(command.as_ref())
295            .map(|_| ())
296            .map_err(Error::Pipe)
297    }
298
299    pub fn analyse(&mut self) -> Result<(), Error> {
300        self.analyse_with("aaaa")
301    }
302
303    fn export_project(&mut self) -> Result<flatbuffers::WIPOffset<schema::Project<'db>>, Error> {
304        let (arch, meta) = self.export_metadata()?;
305        let avec = self.builder.create_vector_from_iter(std::iter::once(arch));
306
307        let (segs, seg_ivt) = self.export_segments()?;
308        let svec = self.builder.create_vector_from_iter(segs.into_iter());
309
310        let fcns = self.export_functions(&seg_ivt)?;
311        let fvec = self.builder.create_vector_from_iter(fcns.into_iter());
312
313        let mut dbuilder = schema::ProjectBuilder::new(&mut self.builder);
314
315        dbuilder.add_architectures(avec);
316        dbuilder.add_segments(svec);
317        dbuilder.add_functions(fvec);
318        dbuilder.add_metadata(meta);
319
320        Ok(dbuilder.finish())
321    }
322
323    fn export_version(&mut self) -> Result<String, Error> {
324        // NOTE: no json output for rizin...
325        let vers = if self.config.exepath.ends_with("rizin")
326            || self.config.exepath.ends_with("rizin.exe")
327        {
328            self.pipe.cmd(&format!("!{} -v", self.config.exepath))?
329        } else {
330            let value3 = self.pipe.cmd(&format!("!{} -vj", self.config.exepath))?;
331            let version = serde_json::from_str::<Version>(&value3)
332                .map_err(|e| Error::Deserialise("!r2 -vj", e))?;
333            format!("{} {}", version.name, version.version)
334        };
335
336        Ok(vers)
337    }
338
339    fn export_metadata(
340        &mut self,
341    ) -> Result<
342        (
343            flatbuffers::WIPOffset<schema::Architecture<'db>>,
344            flatbuffers::WIPOffset<schema::Metadata<'db>>,
345        ),
346        Error,
347    > {
348        let value1 = self.pipe.cmd("ij")?;
349        let corebin =
350            serde_json::from_str::<Metadata>(&value1).map_err(|e| Error::Deserialise("ij", e))?;
351
352        let md5 = md5::compute(&*self.backing);
353        let sha256 = <sha2::Sha256 as sha2::Digest>::digest(&*self.backing);
354
355        let mut exporter = self
356            .export_version()
357            .unwrap_or_else(|_| "radare (unknown version)".to_string());
358
359        if exporter.is_empty() {
360            exporter = Path::new(&self.config.exepath)
361                .file_name()
362                .and_then(|path| path.to_str().map(ToOwned::to_owned))
363                .unwrap_or_else(|| self.config.exepath.clone());
364        }
365
366        let (def_arch_processor, def_arch_variant) =
367            Metadata::processor(&corebin.bin.arch, corebin.bin.bits)?;
368        let endian = Metadata::endian(&corebin.bin.endian);
369
370        self.endian = endian;
371        self.bits = corebin.bin.bits;
372        self.address_size = corebin.bin.bits;
373
374        let processor = self.builder.create_string(def_arch_processor);
375        let variant = self.builder.create_string(def_arch_variant);
376
377        let arch = {
378            let mut abuilder = schema::ArchitectureBuilder::new(&mut self.builder);
379
380            abuilder.add_processor(processor);
381            abuilder.add_bits(corebin.bin.bits);
382            abuilder.add_endian(endian);
383            abuilder.add_variant(variant);
384
385            abuilder.finish()
386        };
387
388        let meta = {
389            let input_path = self.builder.create_string(&corebin.core.file);
390            let input_md5 = self.builder.create_vector(&*md5);
391            let input_sha256 = self.builder.create_vector(&*sha256);
392            let input_format = self
393                .builder
394                .create_string(Metadata::format(&corebin.bin.bintype)?);
395            let exporter = self.builder.create_string(&exporter);
396
397            let mut mbuilder = schema::MetadataBuilder::new(&mut self.builder);
398
399            mbuilder.add_input_path(input_path);
400            mbuilder.add_input_md5(input_md5);
401            mbuilder.add_input_sha256(input_sha256);
402            mbuilder.add_input_format(input_format);
403            mbuilder.add_input_size(self.backing.len() as u32);
404            mbuilder.add_exporter(exporter);
405
406            mbuilder.finish()
407        };
408
409        Ok((arch, meta))
410    }
411
412    fn export_segment(
413        &mut self,
414        seg: SegmentInfo,
415    ) -> Result<flatbuffers::WIPOffset<schema::Segment<'db>>, Error> {
416        let content = if seg.size > 0 {
417            if seg.size > seg.vsize {
418                return Err(Error::InvalidSegment);
419            }
420
421            let start = seg.paddr as usize;
422            let end = start
423                .checked_add(seg.size as usize)
424                .ok_or(Error::InvalidSegment)?;
425            self.backing.get(start..end).ok_or(Error::InvalidSegment)?
426        } else {
427            &[]
428        };
429
430        let name = self.builder.create_string(seg.name);
431        let bytes = self.builder.create_vector(&content);
432
433        let mut sbuilder = schema::SegmentBuilder::new(&mut self.builder);
434
435        sbuilder.add_name(name);
436        sbuilder.add_address(seg.address().unwrap());
437        sbuilder.add_size_(seg.vsize);
438        sbuilder.add_alignment_(1); // can we get this from r2?
439        sbuilder.add_address_size(self.address_size);
440        sbuilder.add_endian(self.endian);
441        sbuilder.add_bits(self.bits);
442        sbuilder.add_code(seg.is_code());
443        sbuilder.add_data(seg.is_data());
444        sbuilder.add_external(false);
445        sbuilder.add_executable(seg.is_executable());
446        sbuilder.add_readable(seg.is_readable());
447        sbuilder.add_writable(seg.is_writable());
448        sbuilder.add_bytes(bytes);
449
450        Ok(sbuilder.finish())
451    }
452
453    fn export_segments(
454        &mut self,
455    ) -> Result<
456        (
457            Vec<flatbuffers::WIPOffset<schema::Segment<'db>>>,
458            IntervalSet<u64>,
459        ),
460        Error,
461    > {
462        let segsv = self.pipe.cmd("iSj")?;
463        let seginfos = serde_json::from_str::<Vec<SegmentInfo>>(&segsv)
464            .map_err(|e| Error::Deserialise("iSj", e))?;
465
466        let mut seg_ivt = IntervalSet::new();
467        let segs = seginfos
468            .into_iter()
469            .filter_map(|s| {
470                if s.name.is_empty() && s.size == 0 {
471                    None
472                } else {
473                    let vaddr = s.address()?;
474                    seg_ivt.insert(vaddr..vaddr.checked_add(s.vsize as u64)?);
475                    Some(self.export_segment(s))
476                }
477            })
478            .collect::<Result<Vec<_>, Error>>()?;
479
480        Ok((segs, seg_ivt))
481    }
482
483    fn export_interref(
484        &mut self,
485        from: InterRef,
486        to_id: u32,
487        is_call: bool,
488        fcn_map: &HashMap<u64, u32>,
489    ) -> flatbuffers::WIPOffset<schema::InterRef<'db>> {
490        let mut ibuilder = schema::InterRefBuilder::new(&mut self.builder);
491
492        ibuilder.add_address(from.from);
493        ibuilder.add_source(
494            *from
495                .fcn_addr
496                .as_ref()
497                .and_then(|addr| fcn_map.get(addr))
498                .unwrap_or(&0xffff_ffff),
499        );
500        ibuilder.add_target(to_id);
501        ibuilder.add_call(is_call);
502
503        ibuilder.finish()
504    }
505
506    fn export_pred_intraref(
507        &mut self,
508        fcn_id: u32,
509        from: u64,
510        to_id: u64,
511        blk_map: &HashMap<u64, u64>,
512    ) -> flatbuffers::WIPOffset<schema::IntraRef<'db>> {
513        let source = blk_map[&from];
514        let mut ibuilder = schema::IntraRefBuilder::new(&mut self.builder);
515
516        ibuilder.add_source(source);
517        ibuilder.add_target(to_id);
518        ibuilder.add_function(fcn_id);
519
520        ibuilder.finish()
521    }
522
523    fn export_succ_intraref(
524        &mut self,
525        fcn_id: u32,
526        from_id: u64,
527        to: u64,
528        blk_map: &HashMap<u64, u64>,
529    ) -> flatbuffers::WIPOffset<schema::IntraRef<'db>> {
530        let target = blk_map[&to];
531        let mut ibuilder = schema::IntraRefBuilder::new(&mut self.builder);
532
533        ibuilder.add_source(from_id);
534        ibuilder.add_target(target);
535        ibuilder.add_function(fcn_id);
536
537        ibuilder.finish()
538    }
539
540    fn export_block(
541        &mut self,
542        fcn: u32,
543        block: BasicBlock,
544        id: u64,
545        preds: Vec<u64>,
546        succs: Vec<u64>,
547        blk_map: &HashMap<u64, u64>,
548    ) -> flatbuffers::WIPOffset<schema::BasicBlock<'db>> {
549        let predsi = preds
550            .into_iter()
551            .map(|pred| self.export_pred_intraref(fcn, pred, id, blk_map))
552            .collect::<Vec<_>>();
553
554        let preds = self.builder.create_vector_from_iter(predsi.into_iter());
555
556        let succsi = succs
557            .into_iter()
558            .filter(|succ| blk_map.contains_key(succ)) // filters out inter-procedural edges
559            .map(|succ| self.export_succ_intraref(fcn, id, succ, blk_map))
560            .collect::<Vec<_>>();
561
562        let succs = self.builder.create_vector_from_iter(succsi.into_iter());
563
564        let mut bbuilder = schema::BasicBlockBuilder::new(&mut self.builder);
565
566        bbuilder.add_address(block.addr);
567        bbuilder.add_size_(block.size);
568        bbuilder.add_predecessors(preds);
569        bbuilder.add_successors(succs);
570
571        bbuilder.finish()
572    }
573
574    fn export_function(
575        &mut self,
576        name: String,
577        address: u64,
578        imported: bool,
579        id: u32,
580        fcn_map: &HashMap<u64, u32>,
581        seg_ivt: &IntervalSet<u64>,
582    ) -> Result<flatbuffers::WIPOffset<schema::Function<'db>>, Error> {
583        let (entry, blocks) = if !imported {
584            let blksv = self.pipe.cmd(&format!("afbj @ {:#x}", address))?;
585            let blks = serde_json::from_str::<Vec<BasicBlock>>(&blksv)
586                .map_err(|e| Error::Deserialise("afbj", e))?;
587
588            let mut blk_map = HashMap::with_capacity(blks.len());
589            let mut blk_ibps_map =
590                HashMap::<u64, (u64, BasicBlock, Vec<u64>, Vec<u64>)>::with_capacity(blks.len());
591
592            for (i, blk) in blks
593                .into_iter()
594                .filter(|b| seg_ivt.has_overlap(b.addr..=b.addr))
595                .enumerate()
596            {
597                let bid = (id as u64) << 32 | (i as u64);
598                let addr = blk.addr;
599
600                let mut succs = Vec::new();
601                if let Some(addr) = blk.jump {
602                    succs.push(addr);
603                }
604
605                if let Some(addr) = blk.fail {
606                    succs.push(addr);
607                }
608
609                for succ in succs.iter() {
610                    let ibps = blk_ibps_map.entry(*succ).or_default();
611                    ibps.2.push(addr); // set self as a pred
612                }
613
614                blk_map.insert(addr, bid);
615
616                let ibps = blk_ibps_map.entry(addr).or_default();
617                ibps.0 = bid;
618                ibps.1 = blk;
619                ibps.3.extend(succs.into_iter());
620            }
621
622            let entry = *blk_map.get(&address).unwrap_or(&0xffffffff_ffffffff);
623            let blocks = blk_ibps_map
624                .into_iter()
625                .sorted_by_key(|(_, (bid, _, _, _))| *bid)
626                .filter(|(addr, _)| blk_map.contains_key(&addr)) // work around for r2 inserting inter-procedural successors -_-
627                .map(|(_, (bid, blk, preds, succs))| {
628                    self.export_block(id, blk, bid, preds, succs, &blk_map)
629                })
630                .collect::<Vec<_>>();
631            (entry, blocks)
632        } else {
633            (0xffffffff_ffffffff, Vec::default())
634        };
635
636        let refsv = self.pipe.cmd(&format!("axtj @ {:#x}", address))?;
637        let refs = serde_json::from_str::<Vec<InterRef>>(&refsv)
638            .map_err(|e| Error::Deserialise("axtj", e))?;
639
640        let references = refs
641            .into_iter()
642            // ensures call type and jumps that are from outside this function
643            .filter(|r| r.kind == "CALL" || (r.kind == "CODE" && r.fcn_addr != Some(address)))
644            .map(|r| {
645                let is_call = r.kind == "CALL";
646                self.export_interref(r, id, is_call, fcn_map)
647            })
648            .collect::<Vec<_>>();
649
650        let symbol = self.builder.create_string(name.trim());
651        let basic_blocks = self.builder.create_vector_from_iter(blocks.into_iter());
652        let references = self.builder.create_vector_from_iter(references.into_iter());
653
654        let mut fbuilder = schema::FunctionBuilder::new(&mut self.builder);
655
656        fbuilder.add_address(address);
657        fbuilder.add_entry(entry);
658        fbuilder.add_symbol(symbol);
659        fbuilder.add_blocks(basic_blocks);
660        fbuilder.add_references(references);
661
662        Ok(fbuilder.finish())
663    }
664
665    fn export_functions(
666        &mut self,
667        seg_ivt: &IntervalSet<u64>,
668    ) -> Result<Vec<flatbuffers::WIPOffset<schema::Function<'db>>>, Error> {
669        let symbolsv = self.pipe.cmd("isj")?;
670        let symbols = serde_json::from_str::<Vec<Symbol>>(&symbolsv)
671            .map_err(|e| Error::Deserialise("isj", e))?;
672
673        let relocsv = self.pipe.cmd("irj")?;
674        let relocs = serde_json::from_str::<Vec<Relocation>>(&relocsv)
675            .map_err(|e| Error::Deserialise("irj", e))?;
676
677        let fcnsv = self.pipe.cmd("aflj")?; // functions: previously aflqj
678        let fcns = serde_json::from_str::<Vec<Function>>(&fcnsv)
679            .map_err(|e| Error::Deserialise("aflj", e))?;
680
681        let mut n2a_map = HashMap::new();
682        let mut a2n_map = HashMap::new(); // we use index as fn id for addr -> id
683
684        for (id, fcn) in fcns.iter().enumerate() {
685            let id = id as u32;
686            a2n_map.insert(fcn.offset, id);
687            n2a_map.insert(fcn.name.to_owned(), (fcn.offset, false, id));
688        }
689
690        let mut unmatched = HashSet::new();
691
692        for sym in symbols
693            .into_iter()
694            .filter(|s| s.kind == "FUNC" && s.is_imported)
695        {
696            if sym.vaddr != 0 {
697                let id = n2a_map.len() as u32;
698                a2n_map.insert(sym.vaddr, id);
699                n2a_map.insert(sym.realname.to_string(), (sym.vaddr, true, id));
700            } else {
701                unmatched.insert(sym.realname);
702            }
703        }
704
705        for (addr, name) in relocs.into_iter().filter_map(|r| {
706            if let Some(name) = r.name {
707                Some((r.vaddr, name))
708            } else {
709                None
710            }
711        }) {
712            if unmatched.contains(name) {
713                let id = n2a_map.len() as u32;
714                a2n_map.insert(addr, id);
715                n2a_map.insert(name.to_string(), (addr, true, id));
716            }
717        }
718
719        n2a_map
720            .into_iter()
721            .sorted_by_key(|(_name, (_addr, _imported, id))| *id)
722            .map(|(name, (addr, imported, id))| {
723                self.export_function(name, addr, imported, id, &a2n_map, seg_ivt)
724            })
725            .collect::<Result<Vec<_>, Error>>()
726    }
727
728    fn export(&mut self) -> Result<(), Error> {
729        let project = self.export_project()?;
730        schema::finish_project_buffer(&mut self.builder, project);
731        Ok(())
732    }
733
734    pub fn to_file<P: AsRef<Path>>(&mut self, path: P) -> Result<(), Error> {
735        self.builder.reset();
736        self.export()?;
737
738        let path = path.as_ref();
739        let mut file = File::create(path).map_err(Error::CannotExportToFile)?;
740
741        file.write_all(self.builder.finished_data())
742            .map_err(Error::CannotExportToFile)?;
743
744        Ok(())
745    }
746
747    pub fn to_bytes(&mut self) -> Result<&[u8], Error> {
748        self.builder.reset();
749        self.export()?;
750        Ok(self.builder.finished_data())
751    }
752}
753
754#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
755pub struct Radare {
756    r2_path: Option<PathBuf>,
757    fdb_path: Option<PathBuf>,
758    overwrite: bool,
759    analysis_commands: Vec<String>,
760}
761
762impl Default for Radare {
763    fn default() -> Self {
764        Self {
765            r2_path: None,
766            fdb_path: None,
767            overwrite: false,
768            analysis_commands: Vec::default(),
769        }
770    }
771}
772
773impl Radare {
774    fn find_r2<F: Fn(&str) -> Result<PathBuf, Error>>(f: F) -> Result<PathBuf, Error> {
775        if let Ok(local) = f("radare2").or_else(|_| f("r2")).or_else(|_| f("rizin")) {
776            Ok(local)
777        } else {
778            f("radare2.exe")
779                .or_else(|_| f("r2.exe"))
780                .or_else(|_| f("rizin.exe"))
781        }
782    }
783
784    fn find_rz<F: Fn(&str) -> Result<PathBuf, Error>>(f: F) -> Result<PathBuf, Error> {
785        if let Ok(local) = f("rizin") {
786            Ok(local)
787        } else {
788            f("rizin.exe")
789        }
790    }
791
792    pub fn new() -> Result<Self, Error> {
793        if let Ok(r2_path) = Self::find_r2(|p| which(p).map_err(Error::InvalidPath)) {
794            Ok(Self {
795                r2_path: Some(r2_path),
796                ..Default::default()
797            })
798        } else {
799            Err(Error::NotAvailable)
800        }
801    }
802
803    pub fn new_rizin() -> Result<Self, Error> {
804        if let Ok(rz_path) = Self::find_rz(|p| which(p).map_err(Error::InvalidPath)) {
805            Ok(Self {
806                r2_path: Some(rz_path),
807                ..Default::default()
808            })
809        } else {
810            Err(Error::NotAvailable)
811        }
812    }
813
814    pub fn from_path<P: AsRef<Path>>(path: P) -> Result<Self, Error> {
815        let root_dir = path.as_ref();
816        let r2_path =
817            Self::find_r2(|p| which_in(p, Some(root_dir), ".").map_err(Error::InvalidPath))?;
818
819        Ok(Self {
820            r2_path: Some(r2_path),
821            ..Default::default()
822        })
823    }
824
825    pub fn export_path<P: AsRef<Path>>(mut self, path: P, overwrite: bool) -> Self {
826        self.fdb_path = Some(path.as_ref().to_owned());
827        self.overwrite = overwrite;
828        self
829    }
830
831    pub fn with_analysis<S: AsRef<str>>(mut self, command: S) -> Self {
832        self.analysis_commands.push(command.as_ref().to_owned());
833        self
834    }
835}
836
837impl Backend for Radare {
838    type Error = Error;
839
840    fn name(&self) -> &'static str {
841        "fugue-radare"
842    }
843
844    fn is_available(&self) -> bool {
845        self.r2_path.is_some()
846    }
847
848    fn is_preferred_for(&self, path: &Url) -> Option<bool> {
849        Some(path.scheme() == "shm")
850    }
851
852    fn import(&self, program: &Url) -> Result<Imported, Self::Error> {
853        let program = if program.scheme() == "file" {
854            program
855                .to_file_path()
856                .map_err(|_| Error::UnsupportedScheme(program.scheme().to_owned()))?
857                .to_string_lossy()
858                .to_string()
859        } else if program.scheme() == "shm" {
860            program.to_string()
861        } else {
862            return Err(Error::UnsupportedScheme(program.scheme().to_owned()));
863        };
864
865        let r2_path = self.r2_path.as_ref().ok_or_else(|| Error::NotAvailable)?;
866        let config = R2PipeSpawnOptions {
867            exepath: format!("{}", r2_path.display()),
868            ..Default::default()
869        };
870
871        let mut exporter = RadareExporter::new_with(&program, config)?;
872        if self.analysis_commands.is_empty() {
873            exporter.analyse()?;
874        } else {
875            for cmd in self.analysis_commands.iter() {
876                exporter.analyse_with(cmd)?;
877            }
878        }
879
880        if let Some(ref fdb_path) = self.fdb_path {
881            if fdb_path.exists() && !self.overwrite {
882                return Ok(Imported::File(fdb_path.clone()));
883            } else {
884                exporter.to_file(fdb_path)?;
885                Ok(Imported::File(fdb_path.clone()))
886            }
887        } else {
888            Ok(Imported::Bytes(exporter.to_bytes()?.to_vec()))
889        }
890    }
891}
892
893#[cfg(test)]
894mod test {
895    use super::*;
896
897    #[test]
898    fn test_available() -> Result<(), Error> {
899        let r2 = Radare::new()?;
900        assert!(r2.is_available());
901        Ok(())
902    }
903
904    #[cfg(target_os = "linux")]
905    #[test]
906    fn test_available_bwrap() -> Result<(), Error> {
907        let r2 = Radare::from_path("./tests")?;
908        assert!(r2.is_available());
909        Ok(())
910    }
911
912    #[test]
913    fn test_import_true() -> Result<(), Error> {
914        let mut ex = RadareExporter::new_with(
915            "./tests/true",
916            R2PipeSpawnOptions {
917                exepath: "radare2".to_string(),
918                ..Default::default()
919            },
920        )?;
921        ex.analyse()?;
922
923        let _ = ex.to_bytes()?;
924        Ok(())
925    }
926
927    #[cfg(target_os = "linux")]
928    #[test]
929    fn test_import_true_bwrap() -> Result<(), Error> {
930        let mut ex = RadareExporter::new_with(
931            "/bin/true",
932            R2PipeSpawnOptions {
933                exepath: "./tests/radare2".to_string(),
934                ..Default::default()
935            },
936        )?;
937
938        ex.analyse()?;
939        let _ = ex.to_bytes()?;
940
941        Ok(())
942    }
943
944    #[test]
945    fn test_import_true_rizin() -> Result<(), Error> {
946        let mut ex = RadareExporter::new_with(
947            "./tests/true",
948            R2PipeSpawnOptions {
949                exepath: "rizin".to_string(),
950                ..Default::default()
951            },
952        )?;
953        ex.analyse()?;
954
955        let _ = ex.to_bytes()?;
956        Ok(())
957    }
958
959    #[test]
960    fn test_import_true_rizin_shm() -> Result<(), Box<dyn std::error::Error>> {
961        use std::io::Read;
962
963        let mut file = File::open("./tests/true")?;
964        let mut bytes = Vec::new();
965        file.read_to_end(&mut bytes)?;
966
967        let mut shm = shared_memory::ShmemConf::new().size(bytes.len()).create()?;
968
969        unsafe {
970            shm.as_slice_mut().copy_from_slice(&bytes);
971        }
972
973        let path = format!("shm:/{}/{}", shm.get_os_id(), bytes.len());
974        let purl = Url::parse(&path)?;
975
976        let riz = Radare::new_rizin()?.export_path("/tmp/ls-rz.fdb", true);
977        let _imp = riz.import(&purl)?;
978
979        Ok(())
980    }
981
982    #[test]
983    fn test_import_true_export() -> Result<(), Error> {
984        let mut ex = RadareExporter::new("./tests/true")?;
985        ex.analyse()?;
986        let _ = ex.to_file("/tmp/ls.fdb")?;
987        Ok(())
988    }
989
990    #[test]
991    fn test_import_efi_export() -> Result<(), Error> {
992        let mut ex = RadareExporter::new("./tests/tetris.efi")?;
993        ex.analyse()?;
994        let _ = ex.to_file("/tmp/tetris.fdb")?;
995        Ok(())
996    }
997
998    #[test]
999    fn test_db() -> Result<(), Box<dyn std::error::Error>> {
1000        use fugue::db::DatabaseImporter;
1001        use fugue::ir::disassembly::IRBuilderArena;
1002        use fugue::ir::LanguageDB;
1003
1004        let ldb = LanguageDB::from_directory_with("./tests", true)?;
1005        let irb = IRBuilderArena::with_capacity(4096);
1006        let mut dbi = DatabaseImporter::new("./tests/tetris.efi");
1007
1008        dbi.register_backend(Radare::new_rizin()?);
1009        let db = dbi.import(&ldb)?;
1010
1011        let mut ctx = db.translators().next().unwrap().context_database();
1012
1013        for f in db.functions() {
1014            println!("=== function: {:x} ===", f.address());
1015            for b in f.blocks() {
1016                println!("=== block insns: {:x} ===", b.address());
1017
1018                for insn in b.disassemble(&irb)? {
1019                    println!("{}", insn.display());
1020                }
1021
1022                println!("=== block pcode: {:x} ===", b.address());
1023
1024                for stmt in b.lift_with(&mut ctx)? {
1025                    println!("{}", stmt.display());
1026                }
1027            }
1028        }
1029
1030        Ok(())
1031    }
1032}