Skip to content

Commit d5892ec

Browse files
committed
coredump: add core dump creation when a guest crashes
- This works for linux target_os so far - Need to change the elfcore crate to allow building for windows targets Signed-off-by: Doru Blânzeanu <[email protected]>
1 parent 262a159 commit d5892ec

File tree

14 files changed

+412
-47
lines changed

14 files changed

+412
-47
lines changed

Diff for: Cargo.lock

+38
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: src/hyperlight_host/Cargo.toml

+3-1
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ strum = { version = "0.27", features = ["derive"] }
4848
tempfile = { version = "3.19", optional = true }
4949
serde_yaml = "0.9"
5050
anyhow = "1.0"
51+
elfcore = { git = "https://github.com/dblnz/elfcore.git", branch = "split-linux-impl-from-elfcore-refactor" }
52+
nix = { version = "0.29.0", features = ["process"] }
5153

5254
[target.'cfg(windows)'.dependencies]
5355
windows = { version = "0.61", features = [
@@ -118,7 +120,7 @@ cfg_aliases = "0.2.1"
118120
built = { version = "0.7.7", features = ["chrono", "git2"] }
119121

120122
[features]
121-
default = ["kvm", "mshv2", "seccomp"]
123+
default = ["crashdump", "gdb", "kvm", "mshv2", "seccomp"]
122124
seccomp = ["dep:seccompiler"]
123125
function_call_metrics = []
124126
executable_heap = []

Diff for: src/hyperlight_host/src/hypervisor/crashdump.rs

+206-24
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,226 @@
1-
use std::io::Write;
1+
use std::cmp::min;
22

3+
use elfcore::{
4+
ArchComponentState, ArchState, CoreDumpBuilder, CoreError, Elf64_Auxv, Pid, 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+
// amd64 notes
14+
pub const NT_X86_XSTATE: u32 = 0x202;
1315

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")?;
16+
#[derive(Debug)]
17+
pub(crate) struct CrashDumpContext<'a> {
18+
regions: &'a [MemoryRegion],
19+
regs: [u64; 27],
20+
xsave: Vec<u8>,
21+
entry: u64,
22+
binary: Option<String>,
23+
filename: Option<String>,
24+
}
1725

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;
26+
impl<'a> CrashDumpContext<'a> {
27+
pub(crate) fn new(
28+
regions: &'a [MemoryRegion],
29+
regs: [u64; 27],
30+
xsave: Vec<u8>,
31+
entry: u64,
32+
binary: Option<String>,
33+
filename: Option<String>,
34+
) -> Self {
35+
Self {
36+
regions,
37+
regs,
38+
xsave,
39+
entry,
40+
binary,
41+
filename,
2242
}
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-
)
43+
}
44+
}
45+
46+
struct GuestView {
47+
regions: Vec<VaRegion>,
48+
threads: Vec<ThreadView>,
49+
aux_vector: Vec<elfcore::Elf64_Auxv>,
50+
}
51+
52+
impl GuestView {
53+
fn new(ctx: &CrashDumpContext) -> Self {
54+
let regions = ctx
55+
.regions
56+
.iter()
57+
.filter(|r| !r.host_region.is_empty())
58+
.map(|r| VaRegion {
59+
begin: r.guest_region.start as u64,
60+
end: r.guest_region.end as u64,
61+
offset: r.host_region.start as u64,
62+
protection: VaProtection {
63+
is_private: false,
64+
read: r.flags.contains(MemoryRegionFlags::READ),
65+
write: r.flags.contains(MemoryRegionFlags::WRITE),
66+
execute: r.flags.contains(MemoryRegionFlags::EXECUTE),
67+
},
68+
mapped_file_name: None,
69+
})
70+
.collect();
71+
let mut filename = ctx.filename.clone().unwrap_or("".to_string());
72+
filename.push('\0');
73+
println!("{:X?}", filename);
74+
let mut cmd = ctx.binary.clone().unwrap_or("".to_string());
75+
cmd.push('\0');
76+
println!("{:X?}", cmd);
77+
78+
let thread = ThreadView {
79+
flags: 0, // Kernel flags for the process
80+
tid: Pid::from_raw(1),
81+
uid: 0, // User ID
82+
gid: 0, // Group ID
83+
comm: filename,
84+
ppid: 0, // Parent PID
85+
pgrp: 0, // Process group ID
86+
nice: 0, // Nice value
87+
state: 0, // Process state
88+
utime: 0, // User time
89+
stime: 0, // System time
90+
cutime: 0, // Children User time
91+
cstime: 0, // Children User time
92+
cursig: 0, // Current signal
93+
session: 0, // Session ID of the process
94+
sighold: 0, // Blocked signal
95+
sigpend: 0, // Pending signal
96+
cmd_line: cmd,
97+
98+
arch_state: Box::new(ArchState {
99+
gpr_state: ctx.regs.to_vec(),
100+
components: vec![ArchComponentState {
101+
name: "XSAVE",
102+
note_type: NT_X86_XSTATE,
103+
note_name: b"LINUX",
104+
data: ctx.xsave.clone(),
105+
}],
106+
}),
29107
};
30-
temp_file.write_all(region_slice)?;
108+
109+
// Create the auxv vector
110+
// The first entry is AT_ENTRY, which is the entry point of the program
111+
// The entry point is the address where the program starts executing
112+
// This helps the debugger to know that the entry is changed by an offset
113+
// so the symbols can be loaded correctly.
114+
// The second entry is AT_NULL, which marks the end of the vector
115+
let auxv = vec![
116+
Elf64_Auxv {
117+
a_type: 9, // AT_ENTRY
118+
a_val: ctx.entry,
119+
},
120+
Elf64_Auxv {
121+
a_type: 0, // AT_NULL
122+
a_val: 0,
123+
},
124+
];
125+
126+
Self {
127+
regions,
128+
threads: vec![thread],
129+
aux_vector: auxv,
130+
}
131+
}
132+
}
133+
134+
impl ProcessInfoSource for GuestView {
135+
fn get_pid(&self) -> Pid {
136+
Pid::from_raw(1)
137+
}
138+
fn get_threads(&self) -> &[elfcore::ThreadView] {
139+
&self.threads
31140
}
32-
temp_file.flush()?;
141+
fn get_page_size(&self) -> usize {
142+
0x1000
143+
}
144+
fn get_aux_vector(&self) -> Option<&[elfcore::Elf64_Auxv]> {
145+
Some(&self.aux_vector)
146+
}
147+
fn get_va_regions(&self) -> &[elfcore::VaRegion] {
148+
&self.regions
149+
}
150+
fn get_mapped_files(&self) -> Option<&[elfcore::MappedFile]> {
151+
None
152+
}
153+
}
154+
155+
struct GuestMemReader {
156+
regions: Vec<MemoryRegion>,
157+
}
158+
159+
impl GuestMemReader {
160+
fn new(ctx: &CrashDumpContext) -> Self {
161+
Self {
162+
regions: ctx.regions.to_vec(),
163+
}
164+
}
165+
}
166+
167+
impl ReadProcessMemory for GuestMemReader {
168+
fn read_process_memory(
169+
&mut self,
170+
base: usize,
171+
buf: &mut [u8],
172+
) -> std::result::Result<usize, CoreError> {
173+
let mut size = 0;
174+
175+
for r in self.regions.iter() {
176+
if base >= r.guest_region.start && base < r.guest_region.end {
177+
let offset = base - r.guest_region.start;
178+
179+
let region_slice = unsafe {
180+
std::slice::from_raw_parts(
181+
r.host_region.start as *const u8,
182+
r.host_region.len(),
183+
)
184+
};
185+
186+
let start = offset;
187+
let end = offset + min(buf.len(), region_slice.len());
188+
buf.copy_from_slice(&region_slice[start..end]);
189+
size = end - start;
190+
break;
191+
}
192+
}
193+
194+
std::result::Result::Ok(size)
195+
}
196+
}
197+
198+
/// Create core dump file from the hypervisor information
199+
pub(crate) fn crashdump_to_tempfile(hv: &dyn Hypervisor) -> Result<()> {
200+
let temp_file = NamedTempFile::with_prefix("hl")?;
201+
202+
let ctx = hv
203+
.get_crashdump_context()
204+
.map_err(|e| new_error!("Could not create crashdump context: {:?}", e))?;
205+
206+
let gv = GuestView::new(&ctx);
207+
let memory_reader = GuestMemReader::new(&ctx);
208+
209+
let cdb = CoreDumpBuilder::from_source(
210+
Box::new(gv) as Box<dyn ProcessInfoSource>,
211+
Box::new(memory_reader) as Box<dyn ReadProcessMemory>,
212+
);
213+
214+
cdb.write(&temp_file)
215+
.map_err(|e| new_error!("Write Error: {:?}", e))?;
33216

34-
// persist the tempfile to disk
35217
let persist_path = temp_file.path().with_extension("dmp");
36218
temp_file
37219
.persist(&persist_path)
38220
.map_err(|e| new_error!("Failed to persist crashdump file: {:?}", e))?;
39221

40-
println!("Memory dumped to file: {:?}", persist_path);
41-
log::error!("Memory dumped to file: {:?}", persist_path);
222+
println!("Core dump file: {:?}", persist_path);
223+
log::error!("Core dump file: {:?}", persist_path);
42224

43225
Ok(())
44226
}

Diff for: src/hyperlight_host/src/hypervisor/gdb/kvm_debug.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ impl KvmDebug {
8686
self.dbg_cfg.arch.debugreg = [0; 8];
8787
for (k, addr) in addrs.iter().enumerate() {
8888
self.dbg_cfg.arch.debugreg[k] = *addr;
89-
self.dbg_cfg.arch.debugreg[7] |= 1 << (k * 2);
89+
self.dbg_cfg.arch.debugreg[7] |= 0x03 << (k * 2);
9090
}
9191

9292
if !addrs.is_empty() {

0 commit comments

Comments
 (0)