1use crate::file_metadata::SessionId;
7use crate::rc::{Ref, RefCountable};
8use crate::string::{raw_to_string, BnString, IntoCStr};
9use binaryninjacore_sys::*;
10use std::ffi::CString;
11use std::os::raw::{c_char, c_void};
12use std::ptr::NonNull;
13
14#[allow(unused_imports)]
16use crate::binary_view::BinaryView;
17
18pub use binaryninjacore_sys::BNLogLevel as BnLogLevel;
19
20pub const LOGGER_DEFAULT_SESSION_ID: SessionId = SessionId(0);
21
22pub fn bn_log(logger: &str, level: BnLogLevel, msg: &str) {
26 bn_log_with_session(LOGGER_DEFAULT_SESSION_ID, logger, level, msg);
27}
28
29pub fn bn_log_with_session(session_id: SessionId, logger: &str, level: BnLogLevel, msg: &str) {
34 if let Ok(msg) = CString::new(msg) {
35 let logger_name = logger.to_cstr();
36 unsafe {
37 BNLog(
38 session_id.0,
39 level,
40 logger_name.as_ptr(),
41 0,
42 c"%s".as_ptr(),
43 msg.as_ptr(),
44 )
45 }
46 }
47}
48
49#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
50pub struct Logger {
51 handle: NonNull<BNLogger>,
52}
53
54impl Logger {
55 pub fn new(name: &str) -> Ref<Logger> {
57 Self::new_with_session(name, LOGGER_DEFAULT_SESSION_ID)
58 }
59
60 pub fn new_with_session(name: &str, session_id: SessionId) -> Ref<Logger> {
74 let name_raw = CString::new(name).unwrap();
75 let handle = unsafe { BNLogCreateLogger(name_raw.as_ptr(), session_id.0) };
76 unsafe {
77 Ref::new(Logger {
78 handle: NonNull::new(handle).unwrap(),
79 })
80 }
81 }
82
83 pub fn name(&self) -> String {
85 unsafe { BnString::into_string(BNLoggerGetName(self.handle.as_ptr())) }
86 }
87
88 pub fn session_id(&self) -> Option<SessionId> {
93 let raw = unsafe { BNLoggerGetSessionId(self.handle.as_ptr()) };
94 match raw {
95 0 => None,
96 _ => Some(SessionId(raw)),
97 }
98 }
99
100 pub fn log(&self, level: BnLogLevel, msg: &str) {
104 let session = self.session_id().unwrap_or(LOGGER_DEFAULT_SESSION_ID);
105 bn_log_with_session(session, &self.name(), level, msg);
106 }
107}
108
109impl Default for Ref<Logger> {
110 fn default() -> Self {
111 Logger::new("Default")
112 }
113}
114
115impl ToOwned for Logger {
116 type Owned = Ref<Self>;
117
118 fn to_owned(&self) -> Self::Owned {
119 unsafe { RefCountable::inc_ref(self) }
120 }
121}
122
123unsafe impl RefCountable for Logger {
124 unsafe fn inc_ref(logger: &Self) -> Ref<Self> {
125 Ref::new(Self {
126 handle: NonNull::new(BNNewLoggerReference(logger.handle.as_ptr())).unwrap(),
127 })
128 }
129
130 unsafe fn dec_ref(logger: &Self) {
131 BNFreeLogger(logger.handle.as_ptr());
132 }
133}
134
135unsafe impl Send for Logger {}
136unsafe impl Sync for Logger {}
137
138#[must_use]
144pub fn register_log_listener<L: LogListener>(listener: L) -> LogGuard<L> {
145 use binaryninjacore_sys::BNRegisterLogListener;
146
147 let raw = Box::into_raw(Box::new(listener));
148 let mut bn_obj = BNLogListener {
149 context: raw as *mut _,
150 log: Some(cb_log::<L>),
151 logWithStackTrace: Some(cb_log_with_stack_trace::<L>),
152 close: Some(cb_close::<L>),
153 getLogLevel: Some(cb_level::<L>),
154 };
155
156 unsafe {
157 BNRegisterLogListener(&mut bn_obj);
158 BNUpdateLogListeners();
159 }
160
161 LogGuard { ctxt: raw }
162}
163
164#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
166pub struct LogContext<'a> {
167 pub session_id: Option<SessionId>,
171 pub thread_id: usize,
173 pub stack_trace: Option<&'a str>,
175 pub logger_name: &'a str,
177}
178
179pub trait LogListener: 'static + Sync {
184 fn log(&self, ctx: &LogContext, lvl: BnLogLevel, msg: &str);
188
189 fn level(&self) -> BnLogLevel;
194
195 fn close(&self) {}
197}
198
199pub struct LogGuard<L: LogListener> {
200 ctxt: *mut L,
201}
202
203impl<L: LogListener> Drop for LogGuard<L> {
204 fn drop(&mut self) {
205 use binaryninjacore_sys::BNUnregisterLogListener;
206
207 let mut bn_obj = BNLogListener {
208 context: self.ctxt as *mut _,
209 log: Some(cb_log::<L>),
210 logWithStackTrace: Some(cb_log_with_stack_trace::<L>),
211 close: Some(cb_close::<L>),
212 getLogLevel: Some(cb_level::<L>),
213 };
214
215 unsafe {
216 BNUnregisterLogListener(&mut bn_obj);
217 BNUpdateLogListeners();
218
219 let _listener = Box::from_raw(self.ctxt);
220 }
221 }
222}
223
224extern "C" fn cb_log<L>(
225 ctxt: *mut c_void,
226 session: usize,
227 level: BnLogLevel,
228 msg: *const c_char,
229 logger_name: *const c_char,
230 tid: usize,
231) where
232 L: LogListener,
233{
234 ffi_wrap!("LogListener::log", unsafe {
235 let listener = &*(ctxt as *const L);
236 let msg_str = raw_to_string(msg).unwrap();
237 let logger_name_str = raw_to_string(logger_name).unwrap();
238 let session_id = match session {
239 0 => None,
240 _ => Some(SessionId(session)),
241 };
242 let ctx = LogContext {
243 session_id,
244 thread_id: tid,
245 stack_trace: None,
246 logger_name: &logger_name_str,
247 };
248 listener.log(&ctx, level, &msg_str);
249 })
250}
251
252extern "C" fn cb_log_with_stack_trace<L>(
253 ctxt: *mut c_void,
254 session: usize,
255 level: BnLogLevel,
256 stack_trace: *const c_char,
257 msg: *const c_char,
258 logger_name: *const c_char,
259 tid: usize,
260) where
261 L: LogListener,
262{
263 ffi_wrap!("LogListener::log_with_stack_trace", unsafe {
264 let listener = &*(ctxt as *const L);
265 let stack_trace_str = raw_to_string(stack_trace).unwrap();
266 let msg_str = raw_to_string(msg).unwrap();
267 let logger_name_str = raw_to_string(logger_name).unwrap();
268 let session_id = match session {
269 0 => None,
270 _ => Some(SessionId(session)),
271 };
272 let ctx = LogContext {
273 session_id,
274 thread_id: tid,
275 stack_trace: Some(&stack_trace_str),
276 logger_name: &logger_name_str,
277 };
278 listener.log(&ctx, level, &msg_str);
279 })
280}
281
282extern "C" fn cb_close<L>(ctxt: *mut c_void)
283where
284 L: LogListener,
285{
286 ffi_wrap!("LogListener::close", unsafe {
287 let listener = &*(ctxt as *const L);
288 listener.close();
289 })
290}
291
292extern "C" fn cb_level<L>(ctxt: *mut c_void) -> BnLogLevel
293where
294 L: LogListener,
295{
296 ffi_wrap!("LogListener::log", unsafe {
297 let listener = &*(ctxt as *const L);
298 listener.level()
299 })
300}