1
1
use std:: cmp:: min;
2
+ use std:: io:: Write ;
2
3
3
4
use chrono;
4
5
use elfcore:: {
@@ -232,44 +233,78 @@ impl ReadProcessMemory for GuestMemReader {
232
233
}
233
234
}
234
235
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.
236
238
///
237
239
/// 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.
240
243
///
241
244
/// # Arguments
242
245
/// * `hv`: Reference to the hypervisor implementation
243
246
///
244
247
/// # Returns
245
248
/// * `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 < ( ) > {
247
250
log:: info!( "Creating core dump file..." ) ;
248
251
249
252
// Get crash context from hypervisor
250
253
let ctx = hv
251
254
. crashdump_context ( )
252
255
. map_err ( |e| new_error ! ( "Failed to get crashdump context: {:?}" , e) ) ?;
253
256
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 ( ) ;
257
259
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 ) ;
260
262
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 {
261
291
// Generate timestamp string for the filename using chrono
262
292
let timestamp = chrono:: Local :: now ( ) . format ( "%Y%m%d_%H%M%S" ) . to_string ( ) ;
263
293
264
294
// 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 ( )
271
307
}
272
- std:: path:: PathBuf :: from ( dump_dir)
273
308
} else {
274
309
// Fall back to the system temp directory
275
310
std:: env:: temp_dir ( )
@@ -279,19 +314,155 @@ pub(crate) fn crashdump_to_tempfile(hv: &dyn Hypervisor) -> Result<()> {
279
314
let filename = format ! ( "hl_core_{}.elf" , timestamp) ;
280
315
let file_path = output_dir. join ( filename) ;
281
316
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
+ }
285
319
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
+ }
290
350
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
+ }
292
375
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 ( ) ) ) ;
295
382
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
+ }
297
468
}
0 commit comments