Skip to content

Commit

Permalink
Add support for the MINIDUMP_HANDLE_DATA_STREAM to Linux
Browse files Browse the repository at this point in the history
This fixes issue #92
  • Loading branch information
gabrielesvelto committed Oct 26, 2023
1 parent 4147ad3 commit f32b32c
Show file tree
Hide file tree
Showing 8 changed files with 173 additions and 2 deletions.
24 changes: 24 additions & 0 deletions src/bin/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,26 @@ mod linux {
}
}

fn create_files_wait(num: usize) -> Result<()> {
let mut file_array = Vec::<tempfile::NamedTempFile>::with_capacity(num);
for id in 0..num {
let file = tempfile::Builder::new()
.prefix("test_file")
.suffix::<str>(id.to_string().as_ref())
.tempfile()
.unwrap();
file_array.push(file);
println!("1");
}
println!("1");
loop {
std::thread::park();
// This shouldn't be executed, but we put it here to ensure that
// all the files within the array are kept open.
println!("{}", file_array.len());
}
}

pub(super) fn real_main(args: Vec<String>) -> Result<()> {
match args.len() {
1 => match args[0].as_ref() {
Expand All @@ -268,6 +288,10 @@ mod linux {
let num_of_threads: usize = args[1].parse().unwrap();
spawn_name_wait(num_of_threads)
}
"create_files_wait" => {
let num_of_files: usize = args[1].parse().unwrap();
create_files_wait(num_of_files)
}
_ => Err(format!("Len 2: Unknown test option: {}", args[0]).into()),
},
3 => {
Expand Down
12 changes: 12 additions & 0 deletions src/linux/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,16 @@ pub enum SectionExceptionStreamError {
MemoryWriterError(#[from] MemoryWriterError),
}

#[derive(Debug, Error)]
pub enum SectionHandleDataStreamError {
#[error("Failed to access file")]
IOError(#[from] std::io::Error),
#[error("Failed to write to memory")]
MemoryWriterError(#[from] MemoryWriterError),
#[error("Failed integer conversion")]
TryFromIntError(#[from] std::num::TryFromIntError),
}

#[derive(Debug, Error)]
pub enum SectionMappingsError {
#[error("Failed to write to memory")]
Expand Down Expand Up @@ -218,6 +228,8 @@ pub enum WriterError {
SectionAppMemoryError(#[from] SectionAppMemoryError),
#[error("Failed when writing section ExceptionStream")]
SectionExceptionStreamError(#[from] SectionExceptionStreamError),
#[error("Failed when writing section HandleDataStream")]
SectionHandleDataStreamError(#[from] SectionHandleDataStreamError),
#[error("Failed when writing section MappingsError")]
SectionMappingsError(#[from] SectionMappingsError),
#[error("Failed when writing section MemList")]
Expand Down
5 changes: 4 additions & 1 deletion src/linux/minidump_writer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ impl MinidumpWriter {
) -> Result<()> {
// A minidump file contains a number of tagged streams. This is the number
// of streams which we write.
let num_writers = 15u32;
let num_writers = 16u32;

let mut header_section = MemoryWriter::<MDRawHeader>::alloc(buffer)?;

Expand Down Expand Up @@ -327,6 +327,9 @@ impl MinidumpWriter {
// Write section to file
dir_section.write_to_file(buffer, Some(dirent))?;

let dirent = handle_data_stream::write(self, buffer)?;
dir_section.write_to_file(buffer, Some(dirent))?;

// If you add more directory entries, don't forget to update num_writers, above.
Ok(())
}
Expand Down
1 change: 1 addition & 0 deletions src/linux/sections.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
pub mod app_memory;
pub mod exception_stream;
pub mod handle_data_stream;
pub mod mappings;
pub mod memory_info_list_stream;
pub mod memory_list_stream;
Expand Down
84 changes: 84 additions & 0 deletions src/linux/sections/handle_data_stream.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
use std::{
ffi::{CString, OsString},
fs::{self, DirEntry},
mem::{self},
os::unix::prelude::OsStrExt,
path::{Path, PathBuf},
};

use crate::mem_writer::MemoryWriter;

use super::*;

fn file_stat(path: &Path) -> Option<libc::stat> {
let c_path = CString::new(path.as_os_str().as_bytes()).ok()?;
let mut stat = unsafe { std::mem::zeroed::<libc::stat>() };
let result = unsafe { libc::stat(c_path.as_ptr(), &mut stat) };

if result == 0 {
Some(stat)
} else {
None
}
}

fn direntry_to_descriptor(buffer: &mut DumpBuf, entry: &DirEntry) -> Option<MDRawHandleDescriptor> {
let handle = filename_to_fd(&entry.file_name())?;
let realpath = fs::read_link(entry.path()).ok()?;
let path_rva = write_string_to_location(buffer, realpath.to_string_lossy().as_ref()).ok()?;
let stat = file_stat(&entry.path())?;

// TODO: We store the contents of `st_mode` into the `attributes` field, but
// we could also store a human-readable string of the file type inside
// `type_name_rva`. We might move this missing information (and
// more) inside a custom `MINIDUMP_HANDLE_OBJECT_INFORMATION_TYPE` blob.
// That would make this conversion loss-less.
Some(MDRawHandleDescriptor {
handle,
type_name_rva: 0,
object_name_rva: path_rva.rva,
attributes: stat.st_mode,
granted_access: 0,
handle_count: 0,
pointer_count: 0,
})
}

fn filename_to_fd(filename: &OsString) -> Option<u64> {
let filename = filename.to_string_lossy();
filename.parse::<u64>().ok()
}

pub fn write(
config: &mut MinidumpWriter,
buffer: &mut DumpBuf,
) -> Result<MDRawDirectory, errors::SectionHandleDataStreamError> {
let proc_fd_path = PathBuf::from(format!("/proc/{}/fd", config.process_id));
let proc_fd_iter = fs::read_dir(proc_fd_path)?;
let descriptors: Vec<_> = proc_fd_iter
.filter_map(|entry| entry.ok())
.filter_map(|entry| direntry_to_descriptor(buffer, &entry))
.collect();
let number_of_descriptors = descriptors.len() as u32;

let stream_header = MemoryWriter::<MDRawHandleDataStream>::alloc_with_val(
buffer,
MDRawHandleDataStream {
size_of_header: mem::size_of::<MDRawHandleDataStream>() as u32,
size_of_descriptor: mem::size_of::<MDRawHandleDescriptor>() as u32,
number_of_descriptors,
reserved: 0,
},
)?;

let mut dirent = MDRawDirectory {
stream_type: MDStreamType::HandleDataStream as u32,
location: stream_header.location(),
};

let descriptor_list =
MemoryArrayWriter::<MDRawHandleDescriptor>::alloc_from_iter(buffer, descriptors)?;

dirent.location.data_size += descriptor_list.location().data_size;
Ok(dirent)
}
4 changes: 3 additions & 1 deletion src/minidump_format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ pub use minidump_common::format::{
self, ArmElfHwCaps as MDCPUInformationARMElfHwCaps, PlatformId,
ProcessorArchitecture as MDCPUArchitecture, GUID, MINIDUMP_DIRECTORY as MDRawDirectory,
MINIDUMP_EXCEPTION as MDException, MINIDUMP_EXCEPTION_STREAM as MDRawExceptionStream,
MINIDUMP_HEADER as MDRawHeader, MINIDUMP_LOCATION_DESCRIPTOR as MDLocationDescriptor,
MINIDUMP_HANDLE_DATA_STREAM as MDRawHandleDataStream,
MINIDUMP_HANDLE_DESCRIPTOR as MDRawHandleDescriptor, MINIDUMP_HEADER as MDRawHeader,
MINIDUMP_LOCATION_DESCRIPTOR as MDLocationDescriptor,
MINIDUMP_MEMORY_DESCRIPTOR as MDMemoryDescriptor, MINIDUMP_MEMORY_INFO as MDMemoryInfo,
MINIDUMP_MEMORY_INFO_LIST as MDMemoryInfoList, MINIDUMP_MODULE as MDRawModule,
MINIDUMP_SIGNATURE as MD_HEADER_SIGNATURE, MINIDUMP_STREAM_TYPE as MDStreamType,
Expand Down
5 changes: 5 additions & 0 deletions tests/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@ pub fn start_child_and_wait_for_named_threads(num: usize) -> Child {
start_child_and_wait_for_threads_helper("spawn_name_wait", num)
}

#[allow(unused)]
pub fn start_child_and_wait_for_create_files(num: usize) -> Child {
start_child_and_wait_for_threads_helper("create_files_wait", num)
}

#[allow(unused)]
pub fn wait_for_threads(child: &mut Child, num: usize) {
let mut f = BufReader::new(child.stdout.as_mut().expect("Can't open stdout"));
Expand Down
40 changes: 40 additions & 0 deletions tests/linux_minidump_writer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,46 @@ contextual_tests! {
expected.insert(format!("thread_{}", id));
}
assert_eq!(expected, names);

}

fn test_file_descriptors(context: Context) {
let num_of_files = 5;
let mut child = start_child_and_wait_for_create_files(num_of_files);
let pid = child.id() as i32;

let mut tmpfile = tempfile::Builder::new()
.prefix("testfiles")
.tempfile()
.unwrap();

let mut tmp = context.minidump_writer(pid);
let _ = tmp.dump(&mut tmpfile).expect("Could not write minidump");
child.kill().expect("Failed to kill process");

// Reap child
let waitres = child.wait().expect("Failed to wait for child");
let status = waitres.signal().expect("Child did not die due to signal");
assert_eq!(waitres.code(), None);
assert_eq!(status, Signal::SIGKILL as i32);

// Read dump file and check its contents. There should be a truncated minidump available
let dump = Minidump::read_path(tmpfile.path()).expect("Failed to read minidump");
let fds: MinidumpHandleDataStream = dump.get_stream().expect("Couldn't find MinidumpHandleDataStream");
// We check that we create num_of_files plus stdin, stdout and stderr
for i in 0..2 {
let descriptor = fds.handles.get(i).expect("Descriptor should be present");
let fd = descriptor.raw.handle().expect("Handle should be populated").clone();
assert_eq!(fd, i as u64);
}

for i in 3..num_of_files {
let descriptor = fds.handles.get(i).expect("Descriptor should be present");
let object_name = descriptor.object_name.as_ref().expect("The path should be populated");
let file_name = object_name.split('/').last().expect("The filename should be present");
assert_eq!(file_name.starts_with("test_file"), true);
assert_eq!(file_name.ends_with(&(i - 3).to_string()), true);
}
}
}

Expand Down

0 comments on commit f32b32c

Please sign in to comment.