Skip to content

Commit 8eee79a

Browse files
committed
crashdump: create core dump file when a guest crashes
- the core dump file is an ELF file with special segments that describe the guest's memory when it crashed, the CPU register's values and other special notes that tell the debugger how to set up a debugging session starting from the core dump Signed-off-by: Doru Blânzeanu <[email protected]>
1 parent a6bc615 commit 8eee79a

File tree

9 files changed

+442
-36
lines changed

9 files changed

+442
-36
lines changed

Cargo.lock

Lines changed: 25 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/hyperlight_host/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ tempfile = { version = "3.19", optional = true }
4646
serde_yaml = "0.9"
4747
anyhow = "1.0"
4848
metrics = "0.24.2"
49+
elfcore = { git = "https://github.com/dblnz/elfcore.git", branch = "split-linux-impl-from-elfcore" }
4950

5051
[target.'cfg(windows)'.dependencies]
5152
windows = { version = "0.61", features = [
Lines changed: 222 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,241 @@
1-
use std::io::Write;
1+
use std::cmp::min;
22

3+
use elfcore::{
4+
ArchComponentState, ArchState, CoreDumpBuilder, CoreError, Elf64_Auxv, ProcessInfoSource,
5+
ReadProcessMemory, ThreadView, VaProtection, VaRegion,
6+
};
37
use tempfile::NamedTempFile;
48

59
use super::Hypervisor;
10+
use crate::mem::memory_region::{MemoryRegion, MemoryRegionFlags};
611
use crate::{new_error, Result};
712

8-
/// Dump registers + memory regions + raw memory to a tempfile
9-
#[cfg(crashdump)]
10-
pub(crate) fn crashdump_to_tempfile(hv: &dyn Hypervisor) -> Result<()> {
11-
let mut temp_file = NamedTempFile::with_prefix("mem")?;
12-
let hv_details = format!("{:#x?}", hv);
13+
const NT_X86_XSTATE: u32 = 0x202;
14+
const AT_ENTRY: u64 = 9;
15+
const AT_NULL: u64 = 0;
1316

14-
// write hypervisor details such as registers, info about mapped memory regions, etc.
15-
temp_file.write_all(hv_details.as_bytes())?;
16-
temp_file.write_all(b"================ MEMORY DUMP =================\n")?;
17+
#[derive(Debug)]
18+
pub(crate) struct CrashDumpContext<'a> {
19+
regions: &'a [MemoryRegion],
20+
regs: [u64; 27],
21+
xsave: Vec<u8>,
22+
entry: u64,
23+
}
1724

18-
// write the raw memory dump for each memory region
19-
for region in hv.get_memory_regions() {
20-
if region.host_region.start == 0 || region.host_region.is_empty() {
21-
continue;
25+
impl<'a> CrashDumpContext<'a> {
26+
pub(crate) fn new(
27+
regions: &'a [MemoryRegion],
28+
regs: [u64; 27],
29+
xsave: Vec<u8>,
30+
entry: u64,
31+
) -> Self {
32+
Self {
33+
regions,
34+
regs,
35+
xsave,
36+
entry,
2237
}
23-
// SAFETY: we got this memory region from the hypervisor so should never be invalid
24-
let region_slice = unsafe {
25-
std::slice::from_raw_parts(
26-
region.host_region.start as *const u8,
27-
region.host_region.len(),
28-
)
38+
}
39+
}
40+
41+
struct GuestView {
42+
regions: Vec<VaRegion>,
43+
threads: Vec<ThreadView>,
44+
aux_vector: Vec<elfcore::Elf64_Auxv>,
45+
}
46+
47+
impl GuestView {
48+
fn new(ctx: &CrashDumpContext) -> Self {
49+
let regions = ctx
50+
.regions
51+
.iter()
52+
.filter(|r| !r.host_region.is_empty())
53+
.map(|r| VaRegion {
54+
begin: r.guest_region.start as u64,
55+
end: r.guest_region.end as u64,
56+
offset: r.host_region.start as u64,
57+
protection: VaProtection {
58+
is_private: false,
59+
read: r.flags.contains(MemoryRegionFlags::READ),
60+
write: r.flags.contains(MemoryRegionFlags::WRITE),
61+
execute: r.flags.contains(MemoryRegionFlags::EXECUTE),
62+
},
63+
mapped_file_name: None,
64+
})
65+
.collect();
66+
67+
let mut components = vec![];
68+
if !ctx.xsave.is_empty() {
69+
components.push(ArchComponentState {
70+
name: "XSAVE",
71+
note_type: NT_X86_XSTATE,
72+
note_name: b"LINUX",
73+
data: ctx.xsave.clone(),
74+
});
75+
}
76+
let thread = ThreadView {
77+
flags: 0, // Kernel flags for the process
78+
tid: 1,
79+
uid: 0, // User ID
80+
gid: 0, // Group ID
81+
comm: "\0".to_string(),
82+
ppid: 0, // Parent PID
83+
pgrp: 0, // Process group ID
84+
nice: 0, // Nice value
85+
state: 0, // Process state
86+
utime: 0, // User time
87+
stime: 0, // System time
88+
cutime: 0, // Children User time
89+
cstime: 0, // Children User time
90+
cursig: 0, // Current signal
91+
session: 0, // Session ID of the process
92+
sighold: 0, // Blocked signal
93+
sigpend: 0, // Pending signal
94+
cmd_line: "\0".to_string(),
95+
96+
arch_state: Box::new(ArchState {
97+
gpr_state: ctx.regs.to_vec(),
98+
components,
99+
}),
29100
};
30-
temp_file.write_all(region_slice)?;
101+
102+
// Create the auxv vector
103+
// The first entry is AT_ENTRY, which is the entry point of the program
104+
// The entry point is the address where the program starts executing
105+
// This helps the debugger to know that the entry is changed by an offset
106+
// so the symbols can be loaded correctly.
107+
// The second entry is AT_NULL, which marks the end of the vector
108+
let auxv = vec![
109+
Elf64_Auxv {
110+
a_type: AT_ENTRY,
111+
a_val: ctx.entry,
112+
},
113+
Elf64_Auxv {
114+
a_type: AT_NULL,
115+
a_val: 0,
116+
},
117+
];
118+
119+
Self {
120+
regions,
121+
threads: vec![thread],
122+
aux_vector: auxv,
123+
}
124+
}
125+
}
126+
127+
impl ProcessInfoSource for GuestView {
128+
fn pid(&self) -> i32 {
129+
1
130+
}
131+
fn threads(&self) -> &[elfcore::ThreadView] {
132+
&self.threads
133+
}
134+
fn page_size(&self) -> usize {
135+
0x1000
136+
}
137+
fn aux_vector(&self) -> Option<&[elfcore::Elf64_Auxv]> {
138+
Some(&self.aux_vector)
139+
}
140+
fn va_regions(&self) -> &[elfcore::VaRegion] {
141+
&self.regions
142+
}
143+
fn mapped_files(&self) -> Option<&[elfcore::MappedFile]> {
144+
None
31145
}
32-
temp_file.flush()?;
146+
}
147+
148+
struct GuestMemReader {
149+
regions: Vec<MemoryRegion>,
150+
}
151+
152+
impl GuestMemReader {
153+
fn new(ctx: &CrashDumpContext) -> Self {
154+
Self {
155+
regions: ctx.regions.to_vec(),
156+
}
157+
}
158+
}
159+
160+
impl ReadProcessMemory for GuestMemReader {
161+
fn read_process_memory(
162+
&mut self,
163+
base: usize,
164+
buf: &mut [u8],
165+
) -> std::result::Result<usize, CoreError> {
166+
for r in self.regions.iter() {
167+
if base >= r.guest_region.start && base < r.guest_region.end {
168+
let offset = base - r.guest_region.start;
169+
let region_slice = unsafe {
170+
std::slice::from_raw_parts(
171+
r.host_region.start as *const u8,
172+
r.host_region.len(),
173+
)
174+
};
175+
176+
// Calculate how much we can copy
177+
let copy_size = min(buf.len(), region_slice.len() - offset);
178+
if copy_size == 0 {
179+
return std::result::Result::Ok(0);
180+
}
181+
182+
// Only copy the amount that fits in both buffers
183+
buf[..copy_size].copy_from_slice(&region_slice[offset..offset + copy_size]);
184+
return std::result::Result::Ok(copy_size);
185+
}
186+
}
187+
188+
// If we reach here, we didn't find a matching region
189+
std::result::Result::Ok(0)
190+
}
191+
}
192+
193+
/// Create core dump file from the hypervisor information
194+
///
195+
/// This function generates an ELF core dump file capturing the hypervisor's state,
196+
/// which can be used for debugging when crashes occur. The file is created in the
197+
/// system's temporary directory with extension '.dmp' and the path is printed to stdout and logs.
198+
///
199+
/// # Arguments
200+
/// * `hv`: Reference to the hypervisor implementation
201+
///
202+
/// # Returns
203+
/// * `Result<()>`: Success or error
204+
pub(crate) fn crashdump_to_tempfile(hv: &dyn Hypervisor) -> Result<()> {
205+
log::info!("Creating core dump file...");
206+
207+
// Create a temporary file with a recognizable prefix
208+
let temp_file = NamedTempFile::with_prefix("hl_core_")
209+
.map_err(|e| new_error!("Failed to create temporary file: {:?}", e))?;
210+
211+
// Get crash context from hypervisor
212+
let ctx = hv
213+
.crashdump_context()
214+
.map_err(|e| new_error!("Failed to get crashdump context: {:?}", e))?;
215+
216+
// Set up data sources for the core dump
217+
let guest_view = GuestView::new(&ctx);
218+
let memory_reader = GuestMemReader::new(&ctx);
219+
220+
// Create and write core dump
221+
let core_builder = CoreDumpBuilder::from_source(
222+
Box::new(guest_view) as Box<dyn ProcessInfoSource>,
223+
Box::new(memory_reader) as Box<dyn ReadProcessMemory>,
224+
);
225+
226+
core_builder
227+
.write(&temp_file)
228+
.map_err(|e| new_error!("Failed to write core dump: {:?}", e))?;
33229

34-
// persist the tempfile to disk
230+
// Persist the file with a .dmp extension
35231
let persist_path = temp_file.path().with_extension("dmp");
36232
temp_file
37233
.persist(&persist_path)
38-
.map_err(|e| new_error!("Failed to persist crashdump file: {:?}", e))?;
234+
.map_err(|e| new_error!("Failed to persist core dump file: {:?}", e))?;
39235

40-
println!("Memory dumped to file: {:?}", persist_path);
41-
log::error!("Memory dumped to file: {:?}", persist_path);
236+
let path_string = persist_path.to_string_lossy().to_string();
237+
println!("Core dump created successfully: {}", path_string);
238+
log::error!("Core dump file: {}", path_string);
42239

43240
Ok(())
44241
}

src/hyperlight_host/src/hypervisor/hyperv_linux.rs

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ use mshv_bindings::{
4949
use mshv_ioctls::{Mshv, VcpuFd, VmFd};
5050
use tracing::{instrument, Span};
5151

52+
#[cfg(crashdump)]
53+
use super::crashdump;
5254
use super::fpu::{FP_CONTROL_WORD_DEFAULT, FP_TAG_WORD_DEFAULT, MXCSR_DEFAULT};
5355
#[cfg(gdb)]
5456
use super::gdb::{DebugCommChannel, DebugMsg, DebugResponse, GuestDebug, MshvDebug};
@@ -658,8 +660,47 @@ impl Hypervisor for HypervLinuxDriver {
658660
}
659661

660662
#[cfg(crashdump)]
661-
fn get_memory_regions(&self) -> &[MemoryRegion] {
662-
&self.mem_regions
663+
fn crashdump_context(&self) -> Result<super::crashdump::CrashDumpContext> {
664+
let mut regs = [0; 27];
665+
666+
let vcpu_regs = self.vcpu_fd.get_regs()?;
667+
let sregs = self.vcpu_fd.get_sregs()?;
668+
let xsave = self.vcpu_fd.get_xsave()?;
669+
670+
regs[0] = vcpu_regs.r15; // r15
671+
regs[1] = vcpu_regs.r14; // r14
672+
regs[2] = vcpu_regs.r13; // r13
673+
regs[3] = vcpu_regs.r12; // r12
674+
regs[4] = vcpu_regs.rbp; // rbp
675+
regs[5] = vcpu_regs.rbx; // rbx
676+
regs[6] = vcpu_regs.r11; // r11
677+
regs[7] = vcpu_regs.r10; // r10
678+
regs[8] = vcpu_regs.r9; // r9
679+
regs[9] = vcpu_regs.r8; // r8
680+
regs[10] = vcpu_regs.rax; // rax
681+
regs[11] = vcpu_regs.rcx; // rcx
682+
regs[12] = vcpu_regs.rdx; // rdx
683+
regs[13] = vcpu_regs.rsi; // rsi
684+
regs[14] = vcpu_regs.rdi; // rdi
685+
regs[15] = 0; // orig rax
686+
regs[16] = vcpu_regs.rip; // rip
687+
regs[17] = sregs.cs.selector as u64; // cs
688+
regs[18] = vcpu_regs.rflags; // eflags
689+
regs[19] = vcpu_regs.rsp; // rsp
690+
regs[20] = sregs.ss.selector as u64; // ss
691+
regs[21] = sregs.fs.base; // fs_base
692+
regs[22] = sregs.gs.base; // gs_base
693+
regs[23] = sregs.ds.selector as u64; // ds
694+
regs[24] = sregs.es.selector as u64; // es
695+
regs[25] = sregs.fs.selector as u64; // fs
696+
regs[26] = sregs.gs.selector as u64; // gs
697+
698+
Ok(crashdump::CrashDumpContext::new(
699+
&self.mem_regions,
700+
regs,
701+
xsave.buffer.to_vec(),
702+
self.entrypoint,
703+
))
663704
}
664705

665706
#[cfg(gdb)]

0 commit comments

Comments
 (0)