1
1
use std:: cmp:: min;
2
+ use std:: io:: Write ;
2
3
3
4
use chrono;
4
5
use elfcore:: {
@@ -232,44 +233,72 @@ 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
+ // Compute file path on the filesystem
258
+ let file_path = core_dump_file_path ( ) ;
257
259
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
+ }
260
273
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 {
261
285
// Generate timestamp string for the filename using chrono
262
286
let timestamp = chrono:: Local :: now ( ) . format ( "%Y%m%d_%H%M%S" ) . to_string ( ) ;
263
287
264
288
// Determine the output directory based on environment variable
265
289
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 ( )
271
301
}
272
- std:: path:: PathBuf :: from ( dump_dir)
273
302
} else {
274
303
// Fall back to the system temp directory
275
304
std:: env:: temp_dir ( )
@@ -279,19 +308,161 @@ pub(crate) fn crashdump_to_tempfile(hv: &dyn Hypervisor) -> Result<()> {
279
308
let filename = format ! ( "hl_core_{}.elf" , timestamp) ;
280
309
let file_path = output_dir. join ( filename) ;
281
310
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" ) ;
285
359
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 ( ) ;
290
362
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
+ }
292
369
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" ) ;
295
376
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
+ }
297
468
}
0 commit comments