intel_crashlog/ffi.rs
1// Copyright (C) 2025 Intel Corporation
2// SPDX-License-Identifier: MIT
3
4//! C interface to the Intel Crash Log extraction and decoding functions.
5
6#![allow(unused_variables)]
7
8#[cfg(test)]
9mod tests;
10
11#[cfg(feature = "embedded_collateral_tree")]
12use crate::collateral::{CollateralManager, EmbeddedTree};
13use crate::crashlog::CrashLog;
14use crate::node::{Node, NodeChildren, NodeType};
15#[cfg(not(feature = "std"))]
16use alloc::{
17 boxed::Box,
18 collections::VecDeque,
19 ffi::CString,
20 string::{String, ToString},
21};
22use core::slice;
23#[cfg(any(all(target_os = "uefi", feature = "extraction"), doc))]
24use core::{ffi::c_void, ptr::NonNull};
25#[cfg(not(feature = "std"))]
26use core::{
27 ffi::{CStr, c_char, c_uchar},
28 ptr,
29};
30#[cfg(feature = "std")]
31use std::{
32 collections::VecDeque,
33 ffi::{CStr, CString, c_char, c_uchar},
34 ptr,
35};
36#[cfg(all(target_os = "uefi", feature = "extraction"))]
37use uefi_raw::table::system::SystemTable;
38
39/// Crash Log Global Context.
40///
41/// Contains all the resources required by the Crash Log library.
42/// It can be initialized using the [`crashlog_init`] function and freed using the
43/// [`crashlog_deinit`] function.
44pub struct CrashLogContext {
45 #[cfg(feature = "embedded_collateral_tree")]
46 collateral_manager: CollateralManager<EmbeddedTree>,
47}
48
49/// Opaque type that represents an iterator over Crash Logs.
50///
51/// The actual content of the structure can be accessed from this structure using the
52/// [`crashlog_next`] function.
53pub struct CrashLogs {
54 crashlogs: VecDeque<CrashLog>,
55}
56
57/// Opaque type that stores a representation of a Crash Log.
58///
59/// This structure is created by the [`crashlog_export_to_json`] and [`crashlog_export_to_cper`]
60/// functions and must be released using the [`crashlog_release_export`] function.
61///
62/// The actual content of the structure can be accessed from this structure using the
63/// [`crashlog_read_export`] function.
64pub struct CrashLogExport {
65 data: VecDeque<u8>,
66}
67
68/// Opaque type that represents an iterator over the children of a Crash Log register tree node.
69///
70/// This structure is created by the [`crashlog_get_node_children`] function.
71///
72/// The actual nodes can be accessed from this structure using the [`crashlog_get_next_node_child`]
73/// function.
74pub struct CrashLogNodeChildren<'a> {
75 children: NodeChildren<'a>,
76}
77
78/// Initializes the Crash Log Decoder Context
79///
80/// This function allocates the memory needed to store the [`CrashLogContext`],
81/// initializes it, and returns a pointer to it.
82///
83/// The value returned by this function must be passed to any subsequent calls made to the
84/// other functions exposed by this library.
85///
86/// The memory allocated by this function can be released using the [`crashlog_deinit`] function.
87///
88/// # Errors
89///
90/// Returns a NULL pointer if the context cannot be initialized.
91#[unsafe(no_mangle)]
92pub extern "C" fn crashlog_init() -> *mut CrashLogContext {
93 #[cfg(not(feature = "embedded_collateral_tree"))]
94 {
95 alloc(CrashLogContext {})
96 }
97
98 #[cfg(feature = "embedded_collateral_tree")]
99 {
100 if let Ok(collateral_manager) = CollateralManager::embedded_tree() {
101 alloc(CrashLogContext { collateral_manager })
102 } else {
103 ptr::null_mut()
104 }
105 }
106}
107
108/// Creates a [`CrashLog`] object from a binary blob.
109///
110/// The binary blob pointed by the `data` argument can be a raw Crash Log region, a BERT
111/// dump, or a CPER record.
112///
113/// The memory allocated by this function can be freed using the [`crashlog_release`] function.
114///
115/// # Safety
116///
117/// This must be called with a pointer to a [`CrashLogContext`] that was earlier obtained by
118/// calling the [`crashlog_init`] function.
119///
120/// The `data` pointer must point to a valid memory region that contains at most `size` bytes of
121/// Crash Log data.
122///
123/// # Errors
124///
125/// Returns a `NULL` pointer if the binary blob does not encode any valid Crash Log records.
126#[unsafe(no_mangle)]
127pub unsafe extern "C" fn crashlog_read_from_buffer(
128 context: *mut CrashLogContext,
129 data: *const u8,
130 size: usize,
131) -> *mut CrashLog {
132 CrashLog::from_slice(unsafe { slice::from_raw_parts(data, size) })
133 .map(alloc)
134 .unwrap_or(ptr::null_mut())
135}
136
137/// Reads the Crash Log from the UEFI System Table.
138///
139/// # Errors
140///
141/// Returns a `NULL` pointer if the Crash Log records cannot be found.
142#[cfg(all(target_os = "uefi", feature = "extraction"))]
143#[unsafe(no_mangle)]
144pub extern "C" fn crashlog_read_from_system_table(
145 context: *mut CrashLogContext,
146 system_table: *mut c_void,
147) -> *mut CrashLog {
148 CrashLog::from_system_table(NonNull::new(system_table as *mut SystemTable))
149 .map(alloc)
150 .unwrap_or(ptr::null_mut())
151}
152
153/// Reads the Crash Log records from the Windows Event Logs.
154///
155/// # Errors
156///
157/// Returns a `NULL` pointer if the Crash Log records cannot be found.
158#[cfg(any(all(target_os = "windows", feature = "extraction"), doc))]
159#[unsafe(no_mangle)]
160pub extern "C" fn crashlog_read_from_windows_event_logs(
161 context: *mut CrashLogContext,
162) -> *mut CrashLogs {
163 CrashLog::from_windows_event_logs(None)
164 .map(|crashlogs| {
165 alloc(CrashLogs {
166 crashlogs: VecDeque::from(crashlogs),
167 })
168 })
169 .unwrap_or(ptr::null_mut())
170}
171
172/// Reads the Crash Log reported through ACPI from the linux sysfs
173///
174/// # Errors
175///
176/// Returns a `NULL` pointer if the Crash Log record cannot be found.
177#[cfg(any(all(target_os = "linux", feature = "extraction"), doc))]
178#[unsafe(no_mangle)]
179pub extern "C" fn crashlog_read_from_acpi_sysfs(context: *mut CrashLogContext) -> *mut CrashLog {
180 CrashLog::from_acpi_sysfs()
181 .map(alloc)
182 .unwrap_or(ptr::null_mut())
183}
184
185/// Reads the Crash Log reported through Intel PMT from the linux sysfs
186///
187/// # Errors
188///
189/// Returns a `NULL` pointer if the Crash Log record cannot be found.
190#[cfg(any(all(target_os = "linux", feature = "extraction"), doc))]
191#[unsafe(no_mangle)]
192pub extern "C" fn crashlog_read_from_pmt_sysfs(context: *mut CrashLogContext) -> *mut CrashLog {
193 CrashLog::from_pmt_sysfs()
194 .map(alloc)
195 .unwrap_or(ptr::null_mut())
196}
197
198/// Returns the next Crash Log in the iterator.
199///
200/// The memory allocated for the iterator will be automatically freed by this function once all the
201/// children have been returned.
202///
203/// # Safety
204///
205/// This must be called with a pointer to a [`CrashLogContext`] that was earlier obtained by
206/// calling the [`crashlog_init`] function.
207///
208/// If the previous call to this function returns a `NULL` pointer, the iterator must not be used
209/// again as it is freed automatically by this function.
210///
211/// # Errors
212///
213/// Returns a `NULL` pointer if one of the arguments is `NULL` or if no more Crash Logs is
214/// available in the iterator.
215#[unsafe(no_mangle)]
216pub unsafe extern "C" fn crashlog_next(
217 context: *mut CrashLogContext,
218 crashlogs: *mut CrashLogs,
219) -> *mut CrashLog {
220 if crashlogs.is_null() {
221 return ptr::null_mut();
222 }
223
224 unsafe { &mut *crashlogs }
225 .crashlogs
226 .pop_front()
227 .map(alloc)
228 .unwrap_or_else(|| {
229 free(crashlogs);
230 ptr::null_mut()
231 })
232}
233
234/// Decodes a [`CrashLog`] into a register tree.
235///
236/// Returns a [`Node`] object that represents the root node of the register tree.
237///
238/// The memory allocated by this function can be released using the [`crashlog_release_nodes`]
239/// function.
240///
241/// # Safety
242///
243/// This must be called with a pointer to a [`CrashLogContext`] that was earlier obtained by
244/// calling the [`crashlog_init`] function.
245///
246/// The `crashlog` pointer must be obtained using one of the `crashlog_read_from_*` functions.
247///
248/// # Errors
249///
250/// Returns a `NULL` pointer if one of the arguments is `NULL`.
251#[unsafe(no_mangle)]
252pub unsafe extern "C" fn crashlog_decode(
253 context: *mut CrashLogContext,
254 crashlog: *const CrashLog,
255) -> *mut Node {
256 if crashlog.is_null() || context.is_null() {
257 return ptr::null_mut();
258 }
259
260 let context = unsafe { &mut *context };
261 let crashlog = unsafe { &*crashlog };
262 #[cfg(feature = "embedded_collateral_tree")]
263 {
264 alloc(crashlog.decode(&mut context.collateral_manager))
265 }
266 #[cfg(not(feature = "embedded_collateral_tree"))]
267 {
268 alloc(crashlog.decode_without_cm())
269 }
270}
271
272/// Exports the Crash Log as a CPER record.
273///
274/// The memory allocated by this function can be freed using the [`crashlog_release_export`]
275/// function.
276///
277/// # Safety
278///
279/// This must be called with a pointer to a [`CrashLogContext`] that was earlier obtained by
280/// calling the [`crashlog_init`] function.
281///
282/// The `crashlog` pointer must be valid and obtained using one of the `crashlog_read_*()`
283/// functions.
284#[unsafe(no_mangle)]
285pub unsafe extern "C" fn crashlog_export_to_cper(
286 context: *mut CrashLogContext,
287 crashlog: *const CrashLog,
288) -> *mut CrashLogExport {
289 if crashlog.is_null() {
290 return ptr::null_mut();
291 }
292
293 let crashlog = unsafe { &*crashlog };
294 alloc(CrashLogExport {
295 data: VecDeque::from(crashlog.to_bytes()),
296 })
297}
298
299/// Exports the Crash Log register tree ([`Node`]) as JSON file.
300///
301/// The memory allocated by this function can be freed using the [`crashlog_release_export`]
302/// function.
303///
304/// # Safety
305///
306/// This must be called with a pointer to a [`CrashLogContext`] that was earlier obtained by
307/// calling the [`crashlog_init`] function.
308///
309/// The `node` pointer must be obtained using the [`crashlog_decode`] function.
310///
311/// # Errors
312///
313/// Returns a `NULL` pointer if one of the arguments is `NULL` or if an error happens during the
314/// generation of the JSON.
315#[unsafe(no_mangle)]
316#[cfg(feature = "serialize")]
317pub unsafe extern "C" fn crashlog_export_to_json(
318 context: *mut CrashLogContext,
319 node: *const Node,
320) -> *mut CrashLogExport {
321 if node.is_null() {
322 return ptr::null_mut();
323 }
324
325 let node = unsafe { &*node };
326
327 serde_json::to_string(node)
328 .ok()
329 .map(|json| {
330 alloc(CrashLogExport {
331 data: VecDeque::from(json.into_bytes()),
332 })
333 })
334 .unwrap_or(ptr::null_mut())
335}
336
337/// Reads the next chunk of the Crash Log export.
338///
339/// Writes the next `buffer_size` bytes of the Crash Log export in the buffer pointed by the
340/// `buffer` argument.
341///
342/// Returns the amount of bytes that has been written into the buffer. Zero is returned if an error
343/// occurred or if no more data is available.
344///
345/// # Safety
346///
347/// This must be called with a pointer to a [`CrashLogContext`] that was earlier obtained by
348/// calling the [`crashlog_init`] function.
349///
350/// The `export` pointer must be obtained using the [`crashlog_export_to_json`] function or the
351/// [`crashlog_export_to_cper`] function.
352///
353/// The `buffer` pointer must point to a valid writable memory region of `buffer_size` bytes.
354/// The data written to this buffer is not nul-terminated.
355#[unsafe(no_mangle)]
356pub unsafe extern "C" fn crashlog_read_export(
357 context: *mut CrashLogContext,
358 export: *mut CrashLogExport,
359 buffer: *mut u8,
360 buffer_size: usize,
361) -> usize {
362 if export.is_null() {
363 return 0;
364 }
365
366 let export = unsafe { &mut *export };
367 let buffer = unsafe { slice::from_raw_parts_mut(buffer, buffer_size) };
368
369 for (i, dst) in buffer.iter_mut().enumerate().take(buffer_size) {
370 if let Some(byte) = export.data.pop_front() {
371 *dst = byte;
372 } else {
373 return i;
374 }
375 }
376
377 buffer_size
378}
379
380/// Writes the name of the Crash Log [`Node`] in the buffer pointed by the `buffer` argument.
381///
382/// A maximum of `buffer_size` bytes are written into the buffer, including the trailing nul
383/// terminator.
384///
385/// Returns the amount of bytes required to store the full name of the register,
386/// including the nul terminator. Returns zero if an error occurred while accessing the name of the
387/// node.
388///
389/// # Safety
390///
391/// This must be called with a pointer to a [`CrashLogContext`] that was earlier obtained by
392/// calling the [`crashlog_init`] function.
393///
394/// The `node` pointer must be obtained using the [`crashlog_decode`],
395/// [`crashlog_get_next_node_child`], or the [`crashlog_get_node_by_path`] functions.
396///
397/// The `buffer` pointer must point to a writable memory region of `buffer_size` bytes.
398#[unsafe(no_mangle)]
399pub unsafe extern "C" fn crashlog_get_node_name(
400 context: *mut CrashLogContext,
401 node: *const Node,
402 buffer: *mut c_uchar,
403 buffer_size: usize,
404) -> usize {
405 if node.is_null() {
406 return 0;
407 }
408
409 let node = unsafe { &*node };
410
411 if let Ok(name) = CString::new(&*node.name) {
412 let src = name.into_bytes();
413 let end = src.len().min(buffer_size - 1);
414 let buffer = unsafe { slice::from_raw_parts_mut(buffer, buffer_size) };
415 buffer[..end].copy_from_slice(&src[..end]);
416 buffer[end] = 0;
417 return src.len() + 1;
418 }
419
420 0
421}
422
423/// Copies the value stored in the Crash Log register tree node into the qword pointed by the
424/// `value` argument.
425///
426/// Returns true if the value has been written. If an error happens or if the node does not store
427/// any value, false is returned.
428///
429/// # Safety
430///
431/// This must be called with a pointer to a [`CrashLogContext`] that was earlier obtained by
432/// calling the [`crashlog_init`] function.
433///
434/// The `node` pointer must be obtained using the [`crashlog_decode`],
435/// [`crashlog_get_next_node_child`], or the [`crashlog_get_node_by_path`] functions.
436///
437/// The `value` pointer must point to a writable memory region of 8 bytes.
438#[unsafe(no_mangle)]
439pub unsafe extern "C" fn crashlog_get_node_value(
440 context: *mut CrashLogContext,
441 node: *const Node,
442 value: *mut u64,
443) -> bool {
444 if node.is_null() || value.is_null() {
445 return false;
446 }
447
448 let node = unsafe { &*node };
449 let dst = unsafe { &mut *value };
450 if let NodeType::Field { value } = node.kind {
451 *dst = value;
452 true
453 } else {
454 false
455 }
456}
457
458/// Returns a iterator over the children of a Crash Log register tree node.
459///
460/// The actual children can be obtained from the iterator using the
461/// [`crashlog_get_next_node_child`] function.
462///
463/// # Safety
464///
465/// This must be called with a pointer to a [`CrashLogContext`] that was earlier obtained by
466/// calling the [`crashlog_init`] function.
467///
468/// The `node` pointer must be obtained using the [`crashlog_decode`],
469/// [`crashlog_get_next_node_child`], or the [`crashlog_get_node_by_path`] functions.
470///
471/// # Errors
472///
473/// Returns a `NULL` pointer if one of the arguments is `NULL`.
474#[unsafe(no_mangle)]
475pub unsafe extern "C" fn crashlog_get_node_children<'a>(
476 context: *mut CrashLogContext,
477 node: *const Node,
478) -> *mut CrashLogNodeChildren<'a> {
479 if node.is_null() {
480 return ptr::null_mut();
481 }
482
483 let node = unsafe { &*node };
484 alloc(CrashLogNodeChildren {
485 children: node.children(),
486 })
487}
488
489/// Returns the next child of a Crash Log register tree node.
490///
491/// The memory allocated for the iterator will be automatically freed by this function once all the
492/// children have been returned.
493///
494/// # Safety
495///
496/// This must be called with a pointer to a [`CrashLogContext`] that was earlier obtained by
497/// calling the [`crashlog_init`] function.
498///
499/// The `children` pointer must be obtained using the [`crashlog_get_node_children`] function.
500/// If the previous call to this function returns a `NULL` pointer, the iterator must not be used
501/// again as it is freed automatically by this function.
502///
503/// # Errors
504///
505/// Returns a `NULL` pointer if one of the arguments is `NULL` or if no more node is available in
506/// the iterator.
507#[unsafe(no_mangle)]
508pub unsafe extern "C" fn crashlog_get_next_node_child(
509 context: *mut CrashLogContext,
510 children: *mut CrashLogNodeChildren,
511) -> *const Node {
512 if children.is_null() {
513 return ptr::null();
514 }
515
516 unsafe { &mut *children }
517 .children
518 .next()
519 .map(ptr::from_ref)
520 .unwrap_or_else(|| {
521 free(children);
522 ptr::null()
523 })
524}
525
526/// Returns the Crash Log register tree node located at the provided `path`.
527///
528/// # Safety
529///
530/// This must be called with a pointer to a [`CrashLogContext`] that was earlier obtained by
531/// calling the [`crashlog_init`] function.
532///
533/// The `node` pointer must be obtained using the [`crashlog_decode`],
534/// [`crashlog_get_next_node_child`], or the [`crashlog_get_node_by_path`] functions.
535///
536/// The `path` must be a valid nul-terminated string.
537///
538/// # Errors
539///
540/// Returns a `NULL` pointer if one of the arguments is `NULL` or if the node does not exist.
541#[unsafe(no_mangle)]
542pub unsafe extern "C" fn crashlog_get_node_by_path(
543 context: *mut CrashLogContext,
544 node: *const Node,
545 path: *const c_char,
546) -> *const Node {
547 if node.is_null() {
548 return ptr::null();
549 }
550
551 let node = unsafe { &*node };
552 let path = String::from_utf8_lossy(unsafe { CStr::from_ptr(path) }.to_bytes()).to_string();
553 node.get_by_path(&path)
554 .map(ptr::from_ref)
555 .unwrap_or(ptr::null())
556}
557
558fn alloc<T>(data: T) -> *mut T {
559 Box::into_raw(Box::new(data))
560}
561
562fn free<T>(ptr: *mut T) {
563 if ptr.is_null() {
564 return;
565 }
566
567 unsafe {
568 let _ = Box::from_raw(ptr);
569 }
570}
571
572/// Releases the memory allocated for the Crash Log.
573#[unsafe(no_mangle)]
574pub extern "C" fn crashlog_release(crashlog: *mut CrashLog) {
575 free(crashlog)
576}
577
578/// Releases the memory allocated for the Crash Log register tree.
579///
580/// The root node of the tree is expected to be passed to this function.
581#[unsafe(no_mangle)]
582pub extern "C" fn crashlog_release_nodes(node: *mut Node) {
583 free(node)
584}
585
586/// Releases the memory allocated for the Crash Log export.
587#[unsafe(no_mangle)]
588pub extern "C" fn crashlog_release_export(node: *mut CrashLogExport) {
589 free(node)
590}
591
592/// Releases the memory allocated for the Crash Log global context.
593#[unsafe(no_mangle)]
594pub extern "C" fn crashlog_deinit(ctx: *mut CrashLogContext) {
595 free(ctx)
596}