11use std:: cmp:: min;
2+ use std:: io:: Write ;
23
34use chrono;
45use elfcore:: {
@@ -232,44 +233,78 @@ 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+ // Get env variable for core dump directory
258+ let core_dump_dir = std:: env:: var ( "HYPERLIGHT_CORE_DUMP_DIR" ) . ok ( ) ;
257259
258- // Create and write core dump
259- let core_builder = CoreDumpBuilder :: from_source ( guest_view , memory_reader ) ;
260+ // Compute file path on the filesystem
261+ let file_path = core_dump_file_path ( core_dump_dir ) ;
260262
263+ let create_dump_file = || {
264+ // Create the file
265+ Ok ( Box :: new (
266+ std:: fs:: File :: create ( & file_path)
267+ . map_err ( |e| new_error ! ( "Failed to create core dump file: {:?}" , e) ) ?,
268+ ) as Box < dyn Write > )
269+ } ;
270+
271+ checked_core_dump ( ctx, create_dump_file) . map ( |_| {
272+ println ! ( "Core dump created successfully: {}" , file_path) ;
273+ log:: error!( "Core dump file: {}" , file_path) ;
274+ } )
275+ }
276+
277+ /// Computes the file path for the core dump file.
278+ ///
279+ /// The file path is generated based on the current timestamp and an
280+ /// output directory.
281+ /// If the directory does not exist, it falls back to the system's temp directory.
282+ /// If the variable is not set, it defaults to the system's temporary directory.
283+ /// The filename is formatted as `hl_core_<timestamp>.elf`.
284+ ///
285+ /// Arguments:
286+ /// * `dump_dir`: The environment variable value to check for the output directory.
287+ ///
288+ /// Returns:
289+ /// * `String`: The file path for the core dump file.
290+ fn core_dump_file_path ( dump_dir : Option < String > ) -> String {
261291 // Generate timestamp string for the filename using chrono
262292 let timestamp = chrono:: Local :: now ( ) . format ( "%Y%m%d_%H%M%S" ) . to_string ( ) ;
263293
264294 // Determine the output directory based on environment variable
265- 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) ) ?;
295+ let output_dir = if let Some ( dump_dir) = dump_dir {
296+ // Check if the directory exists
297+ // If it doesn't exist, fall back to the system temp directory
298+ // This is to ensure that the core dump can be created even if the directory is not set
299+ if std:: path:: Path :: new ( & dump_dir) . exists ( ) {
300+ std:: path:: PathBuf :: from ( dump_dir)
301+ } else {
302+ log:: warn!(
303+ "Directory \" {}\" does not exist, falling back to temp directory" ,
304+ dump_dir
305+ ) ;
306+ std:: env:: temp_dir ( )
271307 }
272- std:: path:: PathBuf :: from ( dump_dir)
273308 } else {
274309 // Fall back to the system temp directory
275310 std:: env:: temp_dir ( )
@@ -279,19 +314,155 @@ pub(crate) fn crashdump_to_tempfile(hv: &dyn Hypervisor) -> Result<()> {
279314 let filename = format ! ( "hl_core_{}.elf" , timestamp) ;
280315 let file_path = output_dir. join ( filename) ;
281316
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) ) ?;
317+ file_path. to_string_lossy ( ) . to_string ( )
318+ }
285319
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) ) ?;
320+ /// Create core dump from Hypervisor context if the sandbox is configured to allow core dumps.
321+ ///
322+ /// Arguments:
323+ /// * `ctx`: Optional crash dump context from the hypervisor. This contains the information
324+ /// needed to create the core dump. If `None`, no core dump will be created.
325+ /// * `get_writer`: Closure that returns a writer to the output destination.
326+ ///
327+ /// Returns:
328+ /// * `Result<usize>`: The number of bytes written to the core dump file.
329+ fn checked_core_dump (
330+ ctx : Option < CrashDumpContext > ,
331+ get_writer : impl FnOnce ( ) -> Result < Box < dyn Write > > ,
332+ ) -> Result < usize > {
333+ let mut nbytes = 0 ;
334+ // If the HV returned a context it means we can create a core dump
335+ // This is the case when the sandbox has been configured at runtime to allow core dumps
336+ if let Some ( ctx) = ctx {
337+ // Set up data sources for the core dump
338+ let guest_view = GuestView :: new ( & ctx) ;
339+ let memory_reader = GuestMemReader :: new ( & ctx) ;
340+
341+ // Create and write core dump
342+ let core_builder = CoreDumpBuilder :: from_source ( guest_view, memory_reader) ;
343+
344+ let writer = get_writer ( ) ?;
345+ // Write the core dump directly to the file
346+ nbytes = core_builder
347+ . write ( writer)
348+ . map_err ( |e| new_error ! ( "Failed to write core dump: {:?}" , e) ) ?;
349+ }
290350
291- let path_string = file_path. to_string_lossy ( ) . to_string ( ) ;
351+ Ok ( nbytes)
352+ }
353+
354+ /// Test module for the crash dump functionality
355+ #[ cfg( test) ]
356+ mod test {
357+ use super :: * ;
358+
359+ /// Test the core_dump_file_path function when the environment variable is set to an existing
360+ /// directory
361+ #[ test]
362+ fn test_crashdump_file_path_valid ( ) {
363+ // Get CWD
364+ let valid_dir = std:: env:: current_dir ( )
365+ . unwrap ( )
366+ . to_string_lossy ( )
367+ . to_string ( ) ;
368+
369+ // Call the function
370+ let path = core_dump_file_path ( Some ( valid_dir. clone ( ) ) ) ;
371+
372+ // Check if the path is correct
373+ assert ! ( path. contains( & valid_dir) ) ;
374+ }
292375
293- println ! ( "Core dump created successfully: {}" , path_string) ;
294- log:: error!( "Core dump file: {}" , path_string) ;
376+ /// Test the core_dump_file_path function when the environment variable is set to an invalid
377+ /// directory
378+ #[ test]
379+ fn test_crashdump_file_path_invalid ( ) {
380+ // Call the function
381+ let path = core_dump_file_path ( Some ( "/tmp/not_existing_dir" . to_string ( ) ) ) ;
295382
296- Ok ( ( ) )
383+ // Get the temp directory
384+ let temp_dir = std:: env:: temp_dir ( ) . to_string_lossy ( ) . to_string ( ) ;
385+
386+ // Check if the path is correct
387+ assert ! ( path. contains( & temp_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 ( None ) ;
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