radare2 0.1.0

Rust integration helpers for radare2 core plugins
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
//! Header-backed native binary metadata operations.
//!
//! The Rust API owns every input and snapshot string. The C shim copies symbol
//! and addrline strings into radare2-owned storage before these calls return.

use crate::Core;
use crate::sys::{self, R2RustAddrLine, R2RustBinIdentity, R2RustMapIdentity, R2RustSymbol};
use std::ffi::{CStr, CString};
use std::os::raw::c_void;
use std::ptr;

/// Identity token for the current radare2 bin file and object.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BinIdentity(R2RustBinIdentity);

impl BinIdentity {
    /// Capture the binary object currently selected in `core`.
    pub fn current(core: Core) -> Option<Self> {
        let mut identity = R2RustBinIdentity {
            bin_file: 0,
            bin_object: 0,
            bin_file_id: 0,
        };
        unsafe {
            sys::r2_rust_current_bin_identity(core.as_ptr(), &mut identity)
                .then_some(Self(identity))
        }
    }

    /// Return whether this identity still names the current binary object.
    pub fn is_current(self, core: Core) -> bool {
        unsafe { sys::r2_rust_bin_identity_matches(core.as_ptr(), &self.0) }
    }

    /// Return radare2's stable ID for the bin file.
    pub const fn file_id(self) -> u32 {
        self.0.bin_file_id
    }
}

/// Native binary-symbol classification.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SymbolKind {
    /// Symbol without a stronger type claim.
    NoType,
    /// Sized data object.
    Object,
    /// Function entry.
    Function,
}

impl SymbolKind {
    const fn raw(self) -> i32 {
        match self {
            Self::NoType => sys::R2_RUST_SYMBOL_NOTYPE,
            Self::Object => sys::R2_RUST_SYMBOL_OBJECT,
            Self::Function => sys::R2_RUST_SYMBOL_FUNCTION,
        }
    }
}

/// One symbol to append to the current native bin object.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Symbol {
    /// Original symbol spelling.
    pub name: String,
    /// Physical output offset, or `None` when FAS did not provide one.
    pub paddr: Option<u64>,
    /// Original object virtual address.
    pub vaddr: u64,
    /// Symbol size.
    pub size: u32,
    /// Stable caller-provided ordinal.
    pub ordinal: u32,
    /// Native symbol classification.
    pub kind: SymbolKind,
}

/// Result of attempting to append a native symbol.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SymbolAdd {
    /// A new symbol was appended and is owned by the bin object.
    Added,
    /// An exact symbol already existed and was left unchanged.
    Duplicate,
    /// The binary identity changed or native insertion failed.
    Failed,
}

/// Append-only native symbol transaction.
#[derive(Debug, Clone, Copy)]
pub struct SymbolTransaction {
    identity: BinIdentity,
    initial_len: usize,
    added: usize,
}

impl SymbolTransaction {
    /// Start a transaction against the current bin object's symbol vector.
    pub fn begin(core: Core, identity: BinIdentity) -> Option<Self> {
        let initial_len = unsafe { sys::r2_rust_symbol_count(core.as_ptr(), &identity.0) };
        (initial_len != usize::MAX).then_some(Self {
            identity,
            initial_len,
            added: 0,
        })
    }

    /// Append `symbol`, suppressing exact pre-existing duplicates.
    pub fn add(&mut self, core: Core, symbol: &Symbol) -> SymbolAdd {
        let Some((name, raw)) = raw_symbol(symbol) else {
            return SymbolAdd::Failed;
        };
        let _name = name;
        unsafe {
            if sys::r2_rust_symbol_exists(core.as_ptr(), &self.identity.0, &raw) {
                return SymbolAdd::Duplicate;
            }
            if sys::r2_rust_symbol_add(core.as_ptr(), &self.identity.0, &raw) {
                self.added += 1;
                SymbolAdd::Added
            } else {
                SymbolAdd::Failed
            }
        }
    }

    /// Number of symbols appended through this transaction.
    pub const fn added(self) -> usize {
        self.added
    }

    /// Return whether rollback can safely truncate only symbols this transaction added.
    pub fn can_rollback(self, core: Core) -> bool {
        let current_len = unsafe { sys::r2_rust_symbol_count(core.as_ptr(), &self.identity.0) };
        current_len == self.initial_len.saturating_add(self.added)
    }

    /// Remove every symbol appended after this transaction began.
    ///
    /// This succeeds only while the same bin object is current and no unrelated
    /// code has changed the expected vector length.
    pub fn rollback(self, core: Core) -> bool {
        self.can_rollback(core)
            && unsafe {
                sys::r2_rust_symbols_truncate(core.as_ptr(), &self.identity.0, self.initial_len)
            }
    }
}

fn raw_symbol(symbol: &Symbol) -> Option<(CString, R2RustSymbol)> {
    let name = CString::new(symbol.name.as_bytes()).ok()?;
    let raw = R2RustSymbol {
        name: name.as_ptr(),
        paddr: symbol.paddr.unwrap_or(u64::MAX),
        vaddr: symbol.vaddr,
        size: symbol.size,
        ordinal: symbol.ordinal,
        kind: symbol.kind.raw(),
    };
    Some((name, raw))
}

/// Owned address-to-source row.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AddrLine {
    /// Analysis/runtime address.
    pub addr: u64,
    /// Source filename or full path.
    pub file: String,
    /// Optional compilation path.
    pub path: Option<String>,
    /// One-based line.
    pub line: u32,
    /// One-based column, or zero when unknown.
    pub column: u32,
}

/// Owned pre-mutation addrline snapshot.
#[derive(Debug, Clone)]
pub struct AddrLineSnapshot {
    identity: BinIdentity,
    rows: Vec<AddrLine>,
}

impl AddrLineSnapshot {
    /// Capture all current rows in insertion order.
    pub fn capture(core: Core, identity: BinIdentity) -> Option<Self> {
        let mut rows = Vec::<AddrLine>::new();
        let ok = unsafe {
            sys::r2_rust_addrline_foreach(
                core.as_ptr(),
                &identity.0,
                collect_addrline,
                (&mut rows as *mut Vec<AddrLine>).cast(),
            )
        };
        ok.then_some(Self { identity, rows })
    }

    /// Return the captured rows.
    pub fn rows(&self) -> &[AddrLine] {
        &self.rows
    }

    /// Reset the current store and restore this snapshot exactly in order.
    pub fn restore(&self, core: Core) -> bool {
        replace_addrlines(core, self.identity, &self.rows)
    }
}

unsafe extern "C" fn collect_addrline(user: *mut c_void, raw: *const R2RustAddrLine) -> bool {
    let Some(rows) = (unsafe { user.cast::<Vec<AddrLine>>().as_mut() }) else {
        return false;
    };
    let Some(raw) = (unsafe { raw.as_ref() }) else {
        return false;
    };
    let Some(file) = (unsafe { c_string(raw.file) }) else {
        return true;
    };
    rows.push(AddrLine {
        addr: raw.addr,
        file,
        path: unsafe { c_string(raw.path) },
        line: raw.line,
        column: raw.column,
    });
    true
}

unsafe fn c_string(raw: *const std::os::raw::c_char) -> Option<String> {
    (!raw.is_null()).then(|| unsafe { CStr::from_ptr(raw).to_string_lossy().into_owned() })
}

/// Reset the current addrline store and add `rows` in order.
///
/// If insertion fails, the store remains partially replaced; callers that need
/// atomic behavior should immediately restore a previously captured snapshot.
pub fn replace_addrlines(core: Core, identity: BinIdentity, rows: &[AddrLine]) -> bool {
    let Some(previous) = AddrLineSnapshot::capture(core, identity) else {
        return false;
    };
    if replace_addrlines_unchecked(core, identity, rows) {
        true
    } else {
        let _ = replace_addrlines_unchecked(core, identity, previous.rows());
        false
    }
}

fn replace_addrlines_unchecked(core: Core, identity: BinIdentity, rows: &[AddrLine]) -> bool {
    if !identity.is_current(core)
        || !unsafe { sys::r2_rust_addrline_reset(core.as_ptr(), &identity.0) }
    {
        return false;
    }
    rows.iter().all(|row| add_addrline(core, identity, row))
}

/// Add one row, copying its strings into radare2-owned storage.
pub fn add_addrline(core: Core, identity: BinIdentity, row: &AddrLine) -> bool {
    let Ok(file) = CString::new(row.file.as_bytes()) else {
        return false;
    };
    let path = match row.path.as_deref() {
        Some(path) => match CString::new(path.as_bytes()) {
            Ok(path) => Some(path),
            Err(_) => return false,
        },
        None => None,
    };
    let raw = R2RustAddrLine {
        addr: row.addr,
        file: file.as_ptr(),
        path: path.as_ref().map_or(ptr::null(), |path| path.as_ptr()),
        line: row.line,
        column: row.column,
    };
    unsafe { sys::r2_rust_addrline_add(core.as_ptr(), &identity.0, &raw) }
}

/// Stable description of one active radare2 IO map.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct MapIdentity(R2RustMapIdentity);

impl MapIdentity {
    /// Return whether this exact map still exists with unchanged geometry.
    pub fn is_current(self, core: Core) -> bool {
        unsafe { sys::r2_rust_map_identity_matches(core.as_ptr(), &self.0) }
    }

    /// Runtime virtual start of the map.
    pub const fn begin(self) -> u64 {
        self.0.begin
    }

    /// Physical offset represented at the map start.
    pub const fn delta(self) -> u64 {
        self.0.delta
    }

    /// Return whether the map has execute permission.
    pub const fn is_executable(self) -> bool {
        self.0.perm & 1 != 0
    }
}

fn empty_map_identity() -> R2RustMapIdentity {
    R2RustMapIdentity {
        map_id: 0,
        fd: -1,
        perm: 0,
        begin: 0,
        size: 0,
        delta: 0,
    }
}

/// Resolve an output physical address through the active IO maps.
pub fn resolve_paddr(core: Core, paddr: u64) -> Option<(u64, MapIdentity)> {
    let mut vaddr = 0;
    let mut identity = empty_map_identity();
    unsafe {
        sys::r2_rust_map_for_paddr(core.as_ptr(), paddr, &mut vaddr, &mut identity)
            .then_some((vaddr, MapIdentity(identity)))
    }
}

/// Validate an active virtual address and return its physical mapping.
pub fn resolve_vaddr(core: Core, vaddr: u64) -> Option<(u64, MapIdentity)> {
    let mut paddr = 0;
    let mut identity = empty_map_identity();
    unsafe {
        sys::r2_rust_map_for_vaddr(core.as_ptr(), vaddr, &mut paddr, &mut identity)
            .then_some((paddr, MapIdentity(identity)))
    }
}

/// Result of adding an analyzed function transaction entry.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FunctionAdd {
    /// Analysis created a new function owned by this transaction.
    Added,
    /// A function already existed at the exact address and was preserved.
    Existing,
    /// Analysis or naming failed.
    Failed,
}

#[derive(Debug, Clone, PartialEq, Eq)]
struct OwnedFunction {
    addr: u64,
    name: String,
}

/// Transaction of newly analyzed functions.
#[derive(Debug, Clone, Default)]
pub struct FunctionTransaction {
    functions: Vec<OwnedFunction>,
}

impl FunctionTransaction {
    /// Begin an empty function transaction.
    pub const fn begin() -> Self {
        Self {
            functions: Vec::new(),
        }
    }

    /// Analyze and name a new function, preserving any exact pre-existing one.
    pub fn add(&mut self, core: Core, addr: u64, name: &str) -> FunctionAdd {
        if unsafe { sys::r2_rust_function_exists(core.as_ptr(), addr) } {
            return FunctionAdd::Existing;
        }
        let Ok(name_c) = CString::new(name) else {
            return FunctionAdd::Failed;
        };
        if unsafe { sys::r2_rust_function_analyze(core.as_ptr(), addr, name_c.as_ptr()) } {
            self.functions.push(OwnedFunction {
                addr,
                name: name.to_owned(),
            });
            FunctionAdd::Added
        } else {
            FunctionAdd::Failed
        }
    }

    /// Number of newly created analyzed functions.
    pub fn added(&self) -> usize {
        self.functions.len()
    }

    /// Return whether all owned functions still have their expected identity.
    pub fn can_rollback(&self, core: Core) -> bool {
        self.functions.iter().all(|function| {
            CString::new(function.name.as_bytes()).is_ok_and(|name| unsafe {
                sys::r2_rust_function_matches(core.as_ptr(), function.addr, name.as_ptr())
            })
        })
    }

    /// Remove owned functions in reverse creation order.
    pub fn rollback(&self, core: Core) -> bool {
        if !self.can_rollback(core) {
            return false;
        }
        self.functions.iter().rev().all(|function| {
            let name = CString::new(function.name.as_bytes()).expect("function name was validated");
            unsafe { sys::r2_rust_function_delete(core.as_ptr(), function.addr, name.as_ptr()) }
        })
    }
}

/// Conservative analysis-reference type exposed by this crate.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum XrefKind {
    /// FAS records a symbol use but not instruction-level access semantics.
    Data,
}

impl XrefKind {
    const fn raw(self) -> i32 {
        match self {
            Self::Data => b'd' as i32,
        }
    }
}

/// Result of adding one exact xref.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum XrefAdd {
    /// A new xref was inserted and is owned by this transaction.
    Added,
    /// An exact xref already existed and was preserved.
    Existing,
    /// Insertion failed.
    Failed,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct OwnedXref {
    from: u64,
    to: u64,
    kind: XrefKind,
}

/// Transaction of exact newly inserted analysis xrefs.
#[derive(Debug, Clone, Default)]
pub struct XrefTransaction {
    xrefs: Vec<OwnedXref>,
}

impl XrefTransaction {
    /// Begin an empty xref transaction.
    pub const fn begin() -> Self {
        Self { xrefs: Vec::new() }
    }

    /// Add an exact xref without claiming a pre-existing one.
    pub fn add(&mut self, core: Core, from: u64, to: u64, kind: XrefKind) -> XrefAdd {
        let raw = kind.raw();
        if unsafe { sys::r2_rust_xref_exists(core.as_ptr(), from, to, raw) } {
            return XrefAdd::Existing;
        }
        if unsafe { sys::r2_rust_xref_add(core.as_ptr(), from, to, raw) } {
            self.xrefs.push(OwnedXref { from, to, kind });
            XrefAdd::Added
        } else {
            XrefAdd::Failed
        }
    }

    /// Number of newly inserted xrefs.
    pub fn added(&self) -> usize {
        self.xrefs.len()
    }

    /// Return whether all exact owned xrefs are still present.
    pub fn can_rollback(&self, core: Core) -> bool {
        self.xrefs.iter().all(|xref| unsafe {
            sys::r2_rust_xref_exists(core.as_ptr(), xref.from, xref.to, xref.kind.raw())
        })
    }

    /// Delete owned xrefs in reverse insertion order.
    pub fn rollback(&self, core: Core) -> bool {
        if !self.can_rollback(core) {
            return false;
        }
        self.xrefs.iter().rev().all(|xref| unsafe {
            sys::r2_rust_xref_delete(core.as_ptr(), xref.from, xref.to, xref.kind.raw())
        })
    }
}