11use std:: cmp:: min;
2+ use std:: io:: Write ;
23
34use chrono;
45use elfcore:: {
@@ -232,44 +233,72 @@ impl ReadProcessMemory for GuestMemReader {
232233 }
233234}
234235
235- /// Create core dump file from the hypervisor information
236+ /// Create core dump file from the hypervisor information if the sandbox is configured
237+ /// to allow core dumps.
236238///
237239/// This function generates an ELF core dump file capturing the hypervisor's state,
238- /// which can be used for debugging when crashes occur. The file is created in the
239- /// system's temporary directory with extension '.elf' and the path is printed to stdout and logs.
240+ /// which can be used for debugging when crashes occur.
241+ /// The location of the core dump file is determined by the `HYPERLIGHT_CORE_DUMP_DIR`
242+ /// environment variable. If not set, it defaults to the system's temporary directory.
240243///
241244/// # Arguments
242245/// * `hv`: Reference to the hypervisor implementation
243246///
244247/// # Returns
245248/// * `Result<()>`: Success or error
246- pub ( crate ) fn crashdump_to_tempfile ( hv : & dyn Hypervisor ) -> Result < ( ) > {
249+ pub ( crate ) fn generate_crashdump ( hv : & dyn Hypervisor ) -> Result < ( ) > {
247250 log:: info!( "Creating core dump file..." ) ;
248251
249252 // Get crash context from hypervisor
250253 let ctx = hv
251254 . crashdump_context ( )
252255 . map_err ( |e| new_error ! ( "Failed to get crashdump context: {:?}" , e) ) ?;
253256
254- // Set up data sources for the core dump
255- let guest_view = GuestView :: new ( & ctx) ;
256- let memory_reader = GuestMemReader :: new ( & ctx) ;
257+ // Compute file path on the filesystem
258+ let file_path = core_dump_file_path ( ) ;
257259
258- // Create and write core dump
259- let core_builder = CoreDumpBuilder :: from_source ( guest_view, memory_reader) ;
260+ let create_dump_file = || {
261+ // Create the file
262+ Ok ( Box :: new (
263+ std:: fs:: File :: create ( & file_path)
264+ . map_err ( |e| new_error ! ( "Failed to create core dump file: {:?}" , e) ) ?,
265+ ) as Box < dyn Write > )
266+ } ;
267+
268+ checked_core_dump ( ctx, create_dump_file) . map ( |_| {
269+ println ! ( "Core dump created successfully: {}" , file_path) ;
270+ log:: error!( "Core dump file: {}" , file_path) ;
271+ } )
272+ }
260273
274+ /// Computes the file path for the core dump file.
275+ ///
276+ /// The file path is generated based on the current timestamp and the
277+ /// `HYPERLIGHT_CORE_DUMP_DIR` environment variable to determine the output directory.
278+ /// If the directory does not exist, it falls back to the system's temp directory.
279+ /// If the variable is not set, it defaults to the system's temporary directory.
280+ /// The filename is formatted as `hl_core_<timestamp>.elf`.
281+ ///
282+ /// Returns:
283+ /// * `String`: The file path for the core dump file.
284+ fn core_dump_file_path ( ) -> String {
261285 // Generate timestamp string for the filename using chrono
262286 let timestamp = chrono:: Local :: now ( ) . format ( "%Y%m%d_%H%M%S" ) . to_string ( ) ;
263287
264288 // Determine the output directory based on environment variable
265289 let output_dir = if let Ok ( dump_dir) = std:: env:: var ( "HYPERLIGHT_CORE_DUMP_DIR" ) {
266- // Create the directory if it doesn't exist
267- let path = std:: path:: Path :: new ( & dump_dir) ;
268- if !path. exists ( ) {
269- std:: fs:: create_dir_all ( path)
270- . map_err ( |e| new_error ! ( "Failed to create core dump directory: {:?}" , e) ) ?;
290+ // Check if the directory exists
291+ // If it doesn't exist, fall back to the system temp directory
292+ // This is to ensure that the core dump can be created even if the directory is not set
293+ if std:: path:: Path :: new ( & dump_dir) . exists ( ) {
294+ std:: path:: PathBuf :: from ( dump_dir)
295+ } else {
296+ log:: warn!(
297+ "Directory HYPERLIGHT_CORE_DUMP_DIR=\" {}\" does not exist, falling back to temp directory" ,
298+ dump_dir
299+ ) ;
300+ std:: env:: temp_dir ( )
271301 }
272- std:: path:: PathBuf :: from ( dump_dir)
273302 } else {
274303 // Fall back to the system temp directory
275304 std:: env:: temp_dir ( )
@@ -279,19 +308,161 @@ pub(crate) fn crashdump_to_tempfile(hv: &dyn Hypervisor) -> Result<()> {
279308 let filename = format ! ( "hl_core_{}.elf" , timestamp) ;
280309 let file_path = output_dir. join ( filename) ;
281310
282- // Create the file
283- let file = std:: fs:: File :: create ( & file_path)
284- . map_err ( |e| new_error ! ( "Failed to create core dump file: {:?}" , e) ) ?;
311+ file_path. to_string_lossy ( ) . to_string ( )
312+ }
313+
314+ /// Create core dump from Hypervisor context if the sandbox is configured to allow core dumps.
315+ ///
316+ /// Arguments:
317+ /// * `ctx`: Optional crash dump context from the hypervisor. This contains the information
318+ /// needed to create the core dump. If `None`, no core dump will be created.
319+ /// * `get_writer`: Closure that returns a writer to the output destination.
320+ ///
321+ /// Returns:
322+ /// * `Result<usize>`: The number of bytes written to the core dump file.
323+ fn checked_core_dump (
324+ ctx : Option < CrashDumpContext > ,
325+ get_writer : impl FnOnce ( ) -> Result < Box < dyn Write > > ,
326+ ) -> Result < usize > {
327+ let mut nbytes = 0 ;
328+ // If the HV returned a context it means we can create a core dump
329+ // This is the case when the sandbox has been configured at runtime to allow core dumps
330+ if let Some ( ctx) = ctx {
331+ // Set up data sources for the core dump
332+ let guest_view = GuestView :: new ( & ctx) ;
333+ let memory_reader = GuestMemReader :: new ( & ctx) ;
334+
335+ // Create and write core dump
336+ let core_builder = CoreDumpBuilder :: from_source ( guest_view, memory_reader) ;
337+
338+ let writer = get_writer ( ) ?;
339+ // Write the core dump directly to the file
340+ nbytes = core_builder
341+ . write ( writer)
342+ . map_err ( |e| new_error ! ( "Failed to write core dump: {:?}" , e) ) ?;
343+ }
344+
345+ Ok ( nbytes)
346+ }
347+
348+ /// Test module for the crash dump functionality
349+ #[ cfg( test) ]
350+ mod test {
351+ use super :: * ;
352+
353+ /// Test the core_dump_file_path function when the environment variable is set to an existing
354+ /// directory
355+ #[ test]
356+ fn test_crashdump_file_path_env_var_valid ( ) {
357+ // Set the environment variable
358+ std:: env:: set_var ( "HYPERLIGHT_CORE_DUMP_DIR" , "/tmp" ) ;
285359
286- // Write the core dump directly to the file
287- core_builder
288- . write ( & file)
289- . map_err ( |e| new_error ! ( "Failed to write core dump: {:?}" , e) ) ?;
360+ // Call the function
361+ let path = core_dump_file_path ( ) ;
290362
291- let path_string = file_path. to_string_lossy ( ) . to_string ( ) ;
363+ // Check if the path is correct
364+ assert ! ( path. contains( "/tmp/hl_core_" ) ) ;
365+
366+ // Clean up the environment variable
367+ std:: env:: remove_var ( "HYPERLIGHT_CORE_DUMP_DIR" ) ;
368+ }
292369
293- println ! ( "Core dump created successfully: {}" , path_string) ;
294- log:: error!( "Core dump file: {}" , path_string) ;
370+ /// Test the core_dump_file_path function when the environment variable is set to an invalid
371+ /// directory
372+ #[ test]
373+ fn test_crashdump_file_path_env_var_invalid ( ) {
374+ // Set the environment variable
375+ std:: env:: set_var ( "HYPERLIGHT_CORE_DUMP_DIR" , "/tmp/not_existing_dir" ) ;
295376
296- Ok ( ( ) )
377+ // Call the function
378+ let path = core_dump_file_path ( ) ;
379+
380+ // Get the temp directory
381+ let temp_dir = std:: env:: temp_dir ( ) . to_string_lossy ( ) . to_string ( ) ;
382+
383+ // Check if the path is correct
384+ assert ! ( path. contains( & temp_dir) ) ;
385+
386+ // Clean up the environment variable
387+ std:: env:: remove_var ( "HYPERLIGHT_CORE_DUMP_DIR" ) ;
388+ }
389+
390+ /// Test the core_dump_file_path function when the environment is not set
391+ /// Check against the default temp directory by using the env::temp_dir() function
392+ #[ test]
393+ fn test_crashdump_file_path_default ( ) {
394+ // Call the function
395+ let path = core_dump_file_path ( ) ;
396+
397+ let temp_dir = std:: env:: temp_dir ( ) . to_string_lossy ( ) . to_string ( ) ;
398+
399+ // Check if the path is correct
400+ assert ! ( path. starts_with( & temp_dir) ) ;
401+ }
402+
403+ /// Test core is not created when the context is None
404+ #[ test]
405+ fn test_crashdump_not_created_when_context_is_none ( ) {
406+ // Call the function with None context
407+ let result = checked_core_dump ( None , || Ok ( Box :: new ( std:: io:: empty ( ) ) ) ) ;
408+
409+ // Check if the result is ok and the number of bytes is 0
410+ assert ! ( result. is_ok( ) ) ;
411+ assert_eq ! ( result. unwrap( ) , 0 ) ;
412+ }
413+
414+ /// Test the core dump creation with no regions fails
415+ #[ test]
416+ fn test_crashdump_write_fails_when_no_regions ( ) {
417+ // Create a dummy context
418+ let ctx = CrashDumpContext :: new (
419+ & [ ] ,
420+ [ 0 ; 27 ] ,
421+ vec ! [ ] ,
422+ 0 ,
423+ Some ( "dummy_binary" . to_string ( ) ) ,
424+ Some ( "dummy_filename" . to_string ( ) ) ,
425+ ) ;
426+
427+ let get_writer = || Ok ( Box :: new ( std:: io:: empty ( ) ) as Box < dyn Write > ) ;
428+
429+ // Call the function
430+ let result = checked_core_dump ( Some ( ctx) , get_writer) ;
431+
432+ // Check if the result is an error
433+ // This should fail because there are no regions
434+ assert ! ( result. is_err( ) ) ;
435+ }
436+
437+ /// Check core dump with a dummy region to local vec
438+ /// This test checks if the core dump is created successfully
439+ #[ test]
440+ fn test_crashdump_dummy_core_dump ( ) {
441+ let dummy_vec = vec ! [ 0 ; 0x1000 ] ;
442+ let regions = vec ! [ MemoryRegion {
443+ guest_region: 0x1000 ..0x2000 ,
444+ host_region: dummy_vec. as_ptr( ) as usize ..dummy_vec. as_ptr( ) as usize + dummy_vec. len( ) ,
445+ flags: MemoryRegionFlags :: READ | MemoryRegionFlags :: WRITE ,
446+ region_type: crate :: mem:: memory_region:: MemoryRegionType :: Code ,
447+ } ] ;
448+ // Create a dummy context
449+ let ctx = CrashDumpContext :: new (
450+ & regions,
451+ [ 0 ; 27 ] ,
452+ vec ! [ ] ,
453+ 0x1000 ,
454+ Some ( "dummy_binary" . to_string ( ) ) ,
455+ Some ( "dummy_filename" . to_string ( ) ) ,
456+ ) ;
457+
458+ let get_writer = || Ok ( Box :: new ( std:: io:: empty ( ) ) as Box < dyn Write > ) ;
459+
460+ // Call the function
461+ let result = checked_core_dump ( Some ( ctx) , get_writer) ;
462+
463+ // Check if the result is ok and the number of bytes is 0
464+ assert ! ( result. is_ok( ) ) ;
465+ // Check the number of bytes written is more than 0x1000 (the size of the region)
466+ assert_eq ! ( result. unwrap( ) , 0x2000 ) ;
467+ }
297468}
0 commit comments