binaryninja/
demangle.rs

1// Copyright 2022-2025 Vector 35 Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Interfaces for demangling and simplifying mangled names in binaries.
16
17use binaryninjacore_sys::*;
18use std::ffi::{c_char, c_void};
19
20use crate::architecture::CoreArchitecture;
21use crate::binary_view::BinaryView;
22use crate::string::{raw_to_string, BnString, IntoCStr};
23use crate::types::{QualifiedName, Type};
24
25use crate::rc::*;
26
27pub type Result<R> = std::result::Result<R, ()>;
28
29pub fn demangle_generic(
30    arch: &CoreArchitecture,
31    mangled_name: &str,
32    view: Option<&BinaryView>,
33    simplify: bool,
34) -> Option<(QualifiedName, Option<Ref<Type>>)> {
35    let mangled_name = mangled_name.to_cstr();
36    let mut out_type: *mut BNType = std::ptr::null_mut();
37    let mut out_name = BNQualifiedName::default();
38    let res = unsafe {
39        BNDemangleGeneric(
40            arch.handle,
41            mangled_name.as_ptr(),
42            &mut out_type,
43            &mut out_name,
44            view.map(|v| v.handle).unwrap_or(std::ptr::null_mut()),
45            simplify,
46        )
47    };
48
49    if res {
50        let out_type = match out_type.is_null() {
51            true => None,
52            false => Some(unsafe { Type::ref_from_raw(out_type) }),
53        };
54        Some((QualifiedName::from_owned_raw(out_name), out_type))
55    } else {
56        None
57    }
58}
59
60pub fn demangle_llvm(mangled_name: &str, simplify: bool) -> Option<QualifiedName> {
61    let mangled_name = mangled_name.to_cstr();
62    let mut out_name: *mut *mut std::os::raw::c_char = std::ptr::null_mut();
63    let mut out_size: usize = 0;
64    let res = unsafe {
65        BNDemangleLLVM(
66            mangled_name.as_ptr(),
67            &mut out_name,
68            &mut out_size,
69            simplify,
70        )
71    };
72
73    match res {
74        true => {
75            assert!(!out_name.is_null());
76            let names: Vec<_> = unsafe { ArrayGuard::<BnString>::new(out_name, out_size, ()) }
77                .iter()
78                .map(str::to_string)
79                .collect();
80            unsafe { BNFreeDemangledName(&mut out_name, out_size) };
81
82            Some(names.into())
83        }
84        false => None,
85    }
86}
87
88pub fn demangle_gnu3(
89    arch: &CoreArchitecture,
90    mangled_name: &str,
91    simplify: bool,
92) -> Option<(QualifiedName, Option<Ref<Type>>)> {
93    let mangled_name = mangled_name.to_cstr();
94    let mut out_type: *mut BNType = std::ptr::null_mut();
95    let mut out_name: *mut *mut std::os::raw::c_char = std::ptr::null_mut();
96    let mut out_size: usize = 0;
97    let res = unsafe {
98        BNDemangleGNU3(
99            arch.handle,
100            mangled_name.as_ptr(),
101            &mut out_type,
102            &mut out_name,
103            &mut out_size,
104            simplify,
105        )
106    };
107
108    match res {
109        true => {
110            assert!(!out_name.is_null());
111            let names: Vec<_> = unsafe { ArrayGuard::<BnString>::new(out_name, out_size, ()) }
112                .iter()
113                .map(str::to_string)
114                .collect();
115            unsafe { BNFreeDemangledName(&mut out_name, out_size) };
116
117            let out_type = match out_type.is_null() {
118                true => None,
119                false => Some(unsafe { Type::ref_from_raw(out_type) }),
120            };
121
122            Some((names.into(), out_type))
123        }
124        false => None,
125    }
126}
127
128pub fn demangle_ms(
129    arch: &CoreArchitecture,
130    mangled_name: &str,
131    simplify: bool,
132) -> Option<(QualifiedName, Option<Ref<Type>>)> {
133    let mangled_name = mangled_name.to_cstr();
134    let mut out_type: *mut BNType = std::ptr::null_mut();
135    let mut out_name: *mut *mut std::os::raw::c_char = std::ptr::null_mut();
136    let mut out_size: usize = 0;
137    let res = unsafe {
138        BNDemangleMS(
139            arch.handle,
140            mangled_name.as_ptr(),
141            &mut out_type,
142            &mut out_name,
143            &mut out_size,
144            simplify,
145        )
146    };
147
148    match res {
149        true => {
150            assert!(!out_name.is_null());
151            let names: Vec<_> = unsafe { ArrayGuard::<BnString>::new(out_name, out_size, ()) }
152                .iter()
153                .map(str::to_string)
154                .collect();
155            unsafe { BNFreeDemangledName(&mut out_name, out_size) };
156
157            let out_type = match out_type.is_null() {
158                true => None,
159                false => Some(unsafe { Type::ref_from_raw(out_type) }),
160            };
161
162            Some((names.into(), out_type))
163        }
164        false => None,
165    }
166}
167
168#[derive(PartialEq, Eq, Hash)]
169pub struct Demangler {
170    pub(crate) handle: *mut BNDemangler,
171}
172
173impl Demangler {
174    pub(crate) unsafe fn from_raw(handle: *mut BNDemangler) -> Self {
175        debug_assert!(!handle.is_null());
176        Self { handle }
177    }
178
179    pub fn list() -> Array<Self> {
180        let mut count: usize = 0;
181        let demanglers = unsafe { BNGetDemanglerList(&mut count) };
182        unsafe { Array::<Demangler>::new(demanglers, count, ()) }
183    }
184
185    pub fn is_mangled_string(&self, name: &str) -> bool {
186        let bytes = name.to_cstr();
187        unsafe { BNIsDemanglerMangledName(self.handle, bytes.as_ref().as_ptr() as *const _) }
188    }
189
190    pub fn demangle(
191        &self,
192        arch: &CoreArchitecture,
193        name: &str,
194        view: Option<&BinaryView>,
195    ) -> Option<(QualifiedName, Option<Ref<Type>>)> {
196        let name_bytes = name.to_cstr();
197
198        let mut out_type = std::ptr::null_mut();
199        let mut out_var_name = BNQualifiedName::default();
200
201        let view_ptr = match view {
202            Some(v) => v.handle,
203            None => std::ptr::null_mut(),
204        };
205
206        let res = unsafe {
207            BNDemanglerDemangle(
208                self.handle,
209                arch.handle,
210                name_bytes.as_ref().as_ptr() as *const _,
211                &mut out_type,
212                &mut out_var_name,
213                view_ptr,
214            )
215        };
216
217        match res {
218            true => {
219                let var_type = match out_type.is_null() {
220                    true => None,
221                    false => Some(unsafe { Type::ref_from_raw(out_type) }),
222                };
223
224                Some((QualifiedName::from_owned_raw(out_var_name), var_type))
225            }
226            false => None,
227        }
228    }
229
230    pub fn name(&self) -> String {
231        unsafe { BnString::into_string(BNGetDemanglerName(self.handle)) }
232    }
233
234    pub fn from_name(name: &str) -> Option<Self> {
235        let name_bytes = name.to_cstr();
236        let demangler = unsafe { BNGetDemanglerByName(name_bytes.as_ref().as_ptr() as *const _) };
237        if demangler.is_null() {
238            None
239        } else {
240            Some(unsafe { Demangler::from_raw(demangler) })
241        }
242    }
243
244    pub fn register<C: CustomDemangler>(name: &str, demangler: C) -> Self {
245        extern "C" fn cb_is_mangled_string<C>(ctxt: *mut c_void, name: *const c_char) -> bool
246        where
247            C: CustomDemangler,
248        {
249            ffi_wrap!("CustomDemangler::cb_is_mangled_string", unsafe {
250                let cmd = &*(ctxt as *const C);
251                let Some(name) = raw_to_string(name) else {
252                    return false;
253                };
254                cmd.is_mangled_string(&name)
255            })
256        }
257        extern "C" fn cb_demangle<C>(
258            ctxt: *mut c_void,
259            arch: *mut BNArchitecture,
260            name: *const c_char,
261            out_type: *mut *mut BNType,
262            out_var_name: *mut BNQualifiedName,
263            view: *mut BNBinaryView,
264        ) -> bool
265        where
266            C: CustomDemangler,
267        {
268            ffi_wrap!("CustomDemangler::cb_demangle", unsafe {
269                let cmd = &*(ctxt as *const C);
270                let arch = CoreArchitecture::from_raw(arch);
271                let Some(name) = raw_to_string(name) else {
272                    return false;
273                };
274                let view = match view.is_null() {
275                    false => Some(BinaryView::from_raw(view).to_owned()),
276                    true => None,
277                };
278
279                match cmd.demangle(&arch, &name, view) {
280                    Some((name, ty)) => {
281                        // NOTE: Leaked to the caller, who must pick the ref up.
282                        *out_type = match ty {
283                            Some(t) => Ref::into_raw(t).handle,
284                            None => std::ptr::null_mut(),
285                        };
286                        // NOTE: Leaked to be freed with `cb_free_var_name`.
287                        *out_var_name = QualifiedName::into_raw(name);
288                        true
289                    }
290                    None => false,
291                }
292            })
293        }
294
295        extern "C" fn cb_free_var_name(_ctxt: *mut c_void, name: *mut BNQualifiedName) {
296            ffi_wrap!("CustomDemangler::cb_free_var_name", unsafe {
297                // TODO: What is the point of this free callback?
298                QualifiedName::free_raw(*name)
299            })
300        }
301
302        let name = name.to_cstr();
303        let name_ptr = name.as_ptr();
304        let ctxt = Box::into_raw(Box::new(demangler));
305
306        let callbacks = BNDemanglerCallbacks {
307            context: ctxt as *mut c_void,
308            isMangledString: Some(cb_is_mangled_string::<C>),
309            demangle: Some(cb_demangle::<C>),
310            freeVarName: Some(cb_free_var_name),
311        };
312
313        unsafe {
314            Demangler::from_raw(BNRegisterDemangler(
315                name_ptr,
316                Box::leak(Box::new(callbacks)),
317            ))
318        }
319    }
320
321    pub fn promote(demangler: &Demangler) {
322        unsafe {
323            BNPromoteDemangler(demangler.handle);
324        }
325    }
326}
327
328unsafe impl Sync for Demangler {}
329
330unsafe impl Send for Demangler {}
331
332impl CoreArrayProvider for Demangler {
333    type Raw = *mut BNDemangler;
334    type Context = ();
335    type Wrapped<'a> = Demangler;
336}
337
338unsafe impl CoreArrayProviderInner for Demangler {
339    unsafe fn free(raw: *mut Self::Raw, _count: usize, _context: &Self::Context) {
340        BNFreeDemanglerList(raw);
341    }
342
343    unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> {
344        Demangler::from_raw(*raw)
345    }
346}
347
348pub trait CustomDemangler: 'static + Sync {
349    fn is_mangled_string(&self, name: &str) -> bool;
350
351    fn demangle(
352        &self,
353        arch: &CoreArchitecture,
354        name: &str,
355        view: Option<Ref<BinaryView>>,
356    ) -> Option<(QualifiedName, Option<Ref<Type>>)>;
357}