Skip to main content

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}