@@ -53,29 +53,13 @@ impl MappedRegion {
5353 }
5454}
5555
56- /// Allows to read the contents of a running process' memory.
57- pub struct ReadableProcessMemory {
58- mem_file : File ,
59- }
60-
61- impl ReadableProcessMemory {
62- pub fn from_file ( mem_file : File ) -> Self {
63- Self { mem_file }
64- }
65-
56+ // Represents a handle to read another process' memory.
57+ pub trait MemoryReader {
6658 /// Reads a slice `\[offset..(offset+length)\]` of the opened process' memory
6759 /// and returns it as a [`Vec<u8>`]. `offset` is considered as an absolute offset
6860 /// in the process' address space. If the slice falls outside the memory's address space,
6961 /// the returned buffer will be truncated.
70- pub fn read_chunk ( & mut self , offset : u64 , length : u64 ) -> std:: io:: Result < Vec < u8 > > {
71- self . mem_file . seek ( SeekFrom :: Start ( offset) ) ?;
72- let mut buf = Vec :: new ( ) ;
73- let mem_file = self . mem_file . by_ref ( ) ;
74- // Limit amount of bytes that can be read
75- let mut mem_file_limited = mem_file. take ( length) ;
76- mem_file_limited. read_to_end ( & mut buf) ?;
77- Ok ( buf)
78- }
62+ fn read_chunk ( & mut self , offset : u64 , length : u64 ) -> std:: io:: Result < Vec < u8 > > ;
7963}
8064
8165#[ cfg( target_os = "linux" ) ]
@@ -210,12 +194,37 @@ mod linux {
210194 }
211195 }
212196
197+ /// Allows to read the contents of a running process' memory.
198+ pub struct ReadableProcessMemory {
199+ mem_file : File ,
200+ }
201+
213202 impl ReadableProcessMemory {
214203 /// Opens the contents of process `pid`'s memory for reading.
215204 pub fn open ( pid : u32 ) -> std:: io:: Result < Self > {
216205 let mem_file = File :: open ( format ! ( "/proc/{pid}/mem" ) ) ?;
217206 Ok ( Self :: from_file ( mem_file) )
218207 }
208+
209+ pub fn from_file ( mem_file : File ) -> Self {
210+ Self { mem_file }
211+ }
212+ }
213+
214+ impl MemoryReader for ReadableProcessMemory {
215+ /// Reads a slice `\[offset..(offset+length)\]` of the opened process' memory
216+ /// and returns it as a [`Vec<u8>`]. `offset` is considered as an absolute offset
217+ /// in the process' address space. If the slice falls outside the memory's address space,
218+ /// the returned buffer will be truncated.
219+ fn read_chunk ( & mut self , offset : u64 , length : u64 ) -> std:: io:: Result < Vec < u8 > > {
220+ self . mem_file . seek ( SeekFrom :: Start ( offset) ) ?;
221+ let mut buf = Vec :: new ( ) ;
222+ let mem_file = self . mem_file . by_ref ( ) ;
223+ // Limit amount of bytes that can be read
224+ let mut mem_file_limited = mem_file. take ( length) ;
225+ mem_file_limited. read_to_end ( & mut buf) ?;
226+ Ok ( buf)
227+ }
219228 }
220229
221230 /// Iterator over a running process' memory mappings.
@@ -258,36 +267,9 @@ mod linux {
258267 }
259268
260269 #[ cfg( test) ]
261- mod tests {
262-
270+ mod test {
263271 use super :: * ;
264272
265- #[ test]
266- fn iterate_this_process_regions ( ) {
267- let pid = std:: process:: id ( ) ;
268- let iterator = MappedRegionIter :: from_pid ( pid)
269- . expect ( "Could not read memory regions of current process" ) ;
270- assert ! ( iterator. collect:: <Result <Vec <MappedRegion >, _>>( ) . is_ok( ) ) ;
271- }
272-
273- #[ test]
274- fn can_read_this_process_memory ( ) {
275- let pid = std:: process:: id ( ) ;
276- let regions: Vec < MappedRegion > = MappedRegionIter :: from_pid ( pid)
277- . expect ( "Could not read memory regions of current process" )
278- . collect :: < Result < _ , _ > > ( )
279- . expect ( "Reading maps" ) ;
280-
281- let mut memory =
282- ReadableProcessMemory :: open ( pid) . expect ( "Could not open process memory" ) ;
283- assert ! {
284- regions. iter( ) . any( |region| memory. read_chunk(
285- region. start_address( ) ,
286- region. size( ) ,
287- ) . is_ok( ) )
288- }
289- }
290-
291273 #[ test]
292274 fn region_iter_detects_mmap ( ) {
293275 // `mmap` a file and check that the mapping is detected among those returned by
@@ -349,6 +331,7 @@ mod windows {
349331 use core:: ffi:: c_void;
350332 use std:: mem:: MaybeUninit ;
351333 use windows_sys:: Win32 :: Foundation :: { CloseHandle , HANDLE } ;
334+ use windows_sys:: Win32 :: System :: Diagnostics :: Debug :: ReadProcessMemory ;
352335 use windows_sys:: Win32 :: System :: Memory :: { MEMORY_BASIC_INFORMATION , VirtualQueryEx } ;
353336 use windows_sys:: Win32 :: System :: Threading :: { OpenProcess , PROCESS_ALL_ACCESS } ;
354337
@@ -429,18 +412,42 @@ mod windows {
429412 }
430413 }
431414
432- #[ cfg( test) ]
433- mod tests {
434- use super :: * ;
415+ /// Allows to read the contents of a running process' memory.
416+ pub struct ReadableProcessMemory {
417+ process : ProcessHandle ,
418+ }
435419
436- #[ test]
437- fn iterate_this_process_regions ( ) {
438- let pid = std:: process:: id ( ) ;
439- let iterator = MappedRegionIter :: from_pid ( pid)
440- . expect ( "Could not read memory regions of current process" ) ;
441- let result = iterator. collect :: < Result < Vec < MappedRegion > , _ > > ( ) . unwrap ( ) ;
442- dbg ! ( & result) ;
443- assert ! ( !result. is_empty( ) ) ;
420+ impl ReadableProcessMemory {
421+ /// Opens the contents of process `pid`'s memory for reading.
422+ pub fn open ( pid : u32 ) -> std:: io:: Result < Self > {
423+ Ok ( Self {
424+ process : ProcessHandle :: open ( pid) ?,
425+ } )
426+ }
427+ }
428+
429+ impl MemoryReader for ReadableProcessMemory {
430+ /// Reads a slice `\[offset..(offset+length)\]` of the opened process' memory
431+ /// and returns it as a [`Vec<u8>`]. `offset` is considered as an absolute offset
432+ /// in the process' address space. If the slice falls outside the memory's address space,
433+ /// the returned buffer will be truncated.
434+ fn read_chunk ( & mut self , offset : u64 , length : u64 ) -> std:: io:: Result < Vec < u8 > > {
435+ let mut buf = vec ! [ 0 ; length as usize ] ;
436+ let mut bytes_read = 0 ;
437+ if unsafe {
438+ ReadProcessMemory (
439+ self . process . 0 ,
440+ std:: ptr:: without_provenance_mut ( offset as usize ) ,
441+ buf. as_mut_ptr ( ) . cast ( ) ,
442+ buf. len ( ) ,
443+ & mut bytes_read,
444+ )
445+ } == 0
446+ {
447+ return Err ( std:: io:: Error :: last_os_error ( ) ) ;
448+ }
449+ buf. truncate ( bytes_read) ;
450+ Ok ( buf)
444451 }
445452 }
446453}
@@ -683,15 +690,16 @@ pub fn sort_by_priority(
683690/// Reads the contents of all of process `pid`'s memory mappings
684691/// and sends them to the session.
685692/// `regions` and `memory` are passed as arguments for testability.
686- pub fn dump_regions < S > (
693+ pub fn dump_regions < S , Mem > (
687694 session : & mut S ,
688695 regions : impl Iterator < Item = MappedRegion > ,
689- memory : & mut ReadableProcessMemory ,
696+ memory : & mut Mem ,
690697 pid : u32 ,
691698 total_size_left : & mut u64 ,
692699) -> crate :: session:: Result < ( ) >
693700where
694701 S : crate :: session:: Session ,
702+ Mem : MemoryReader ,
695703{
696704 use sha2:: Digest as _;
697705
@@ -738,7 +746,7 @@ where
738746 Ok ( ( ) )
739747}
740748
741- #[ cfg( not( target_os = "linux" ) ) ]
749+ #[ cfg( not( any ( target_os = "linux" , target_os = "windows" ) ) ) ]
742750pub fn handle < S > ( _session : & mut S , _args : Args ) -> crate :: session:: Result < ( ) >
743751where
744752 S : crate :: session:: Session ,
@@ -749,7 +757,7 @@ where
749757 ) ) )
750758}
751759
752- #[ cfg( target_os = "linux" ) ]
760+ #[ cfg( any ( target_os = "linux" , target_os = "windows" ) ) ]
753761pub fn handle < S > ( session : & mut S , mut args : Args ) -> crate :: session:: Result < ( ) >
754762where
755763 S : crate :: session:: Session ,
@@ -813,7 +821,46 @@ where
813821#[ cfg( test) ]
814822mod test {
815823 use super :: * ;
816- use std:: io:: Write ;
824+
825+ struct FakeProcessMemory {
826+ contents : Vec < u8 > ,
827+ }
828+
829+ impl MemoryReader for FakeProcessMemory {
830+ fn read_chunk ( & mut self , offset : u64 , length : u64 ) -> std:: io:: Result < Vec < u8 > > {
831+ let start = offset as usize ;
832+ let end = ( offset + length) as usize ;
833+ Ok ( self . contents [ start..end] . to_owned ( ) )
834+ }
835+ }
836+
837+ #[ cfg( any( target_os = "linux" , target_os = "windows" ) ) ]
838+ #[ test]
839+ fn iterate_this_process_regions ( ) {
840+ let pid = std:: process:: id ( ) ;
841+ let iterator = MappedRegionIter :: from_pid ( pid)
842+ . expect ( "Could not read memory regions of current process" ) ;
843+ let result = iterator. collect :: < Result < Vec < MappedRegion > , _ > > ( ) . unwrap ( ) ;
844+ assert ! ( !result. is_empty( ) ) ;
845+ }
846+
847+ #[ cfg( any( target_os = "linux" , target_os = "windows" ) ) ]
848+ #[ test]
849+ fn can_read_this_process_memory ( ) {
850+ let pid = std:: process:: id ( ) ;
851+ let regions: Vec < MappedRegion > = MappedRegionIter :: from_pid ( pid)
852+ . expect ( "Could not read memory regions of current process" )
853+ . collect :: < Result < _ , _ > > ( )
854+ . expect ( "Reading maps" ) ;
855+
856+ let mut memory = ReadableProcessMemory :: open ( pid) . expect ( "Could not open process memory" ) ;
857+ assert ! {
858+ regions. iter( ) . any( |region| memory. read_chunk(
859+ region. start_address( ) ,
860+ region. size( ) ,
861+ ) . is_ok( ) )
862+ }
863+ }
817864
818865 #[ test]
819866 fn offset_priority ( ) {
@@ -886,25 +933,19 @@ mod test {
886933
887934 #[ test]
888935 fn dumps_regions ( ) {
889- let tempdir = tempfile:: tempdir ( ) . unwrap ( ) ;
890-
891936 let regions = vec ! [
892937 MappedRegion :: from_bounds( 0 , 1000 ) ,
893938 MappedRegion :: from_bounds( 1000 , 3000 ) ,
894939 MappedRegion :: from_bounds( 3000 , 3500 ) ,
895940 ] ;
896941
897942 // Write fake memory contents and check that they're read correctly
898- let file_path = tempdir. path ( ) . join ( "memfile.txt" ) ;
899- let mut file = File :: create_new ( file_path) . expect ( "could not create memfile" ) ;
900- file. write_all ( vec ! [ 1 ; 1000 ] . as_slice ( ) )
901- . expect ( "could not write to memfile" ) ;
902- file. write_all ( vec ! [ 2 ; 2000 ] . as_slice ( ) )
903- . expect ( "could not write to memfile" ) ;
904- file. write_all ( vec ! [ 3 ; 500 ] . as_slice ( ) )
905- . expect ( "could not write to memfile" ) ;
943+ let mut fake_mem = Vec :: new ( ) ;
944+ fake_mem. extend ( std:: iter:: repeat_n ( 1 , 1000 ) ) ;
945+ fake_mem. extend ( std:: iter:: repeat_n ( 2 , 2000 ) ) ;
946+ fake_mem. extend ( std:: iter:: repeat_n ( 3 , 500 ) ) ;
906947
907- let mut memory = ReadableProcessMemory :: from_file ( file ) ;
948+ let mut memory = FakeProcessMemory { contents : fake_mem } ;
908949
909950 let mut session = crate :: session:: FakeSession :: new ( ) ;
910951 let mut limit = u64:: MAX ;
@@ -928,24 +969,19 @@ mod test {
928969
929970 #[ test]
930971 fn size_limit ( ) {
931- let tempdir = tempfile:: tempdir ( ) . unwrap ( ) ;
932-
933972 let regions = vec ! [
934973 MappedRegion :: from_bounds( 0 , 1000 ) ,
935974 MappedRegion :: from_bounds( 1000 , 3000 ) ,
936975 MappedRegion :: from_bounds( 3000 , 3500 ) ,
937976 ] ;
938977
939- let file_path = tempdir. path ( ) . join ( "memfile.txt" ) ;
940- let mut file = File :: create_new ( file_path) . expect ( "could not create memfile" ) ;
941- file. write_all ( vec ! [ 1 ; 1000 ] . as_slice ( ) )
942- . expect ( "could not write to memfile" ) ;
943- file. write_all ( vec ! [ 2 ; 2000 ] . as_slice ( ) )
944- . expect ( "could not write to memfile" ) ;
945- file. write_all ( vec ! [ 3 ; 500 ] . as_slice ( ) )
946- . expect ( "could not write to memfile" ) ;
978+ // Write fake memory contents and check that they're read correctly
979+ let mut fake_mem = Vec :: new ( ) ;
980+ fake_mem. extend ( std:: iter:: repeat_n ( 1 , 1000 ) ) ;
981+ fake_mem. extend ( std:: iter:: repeat_n ( 2 , 2000 ) ) ;
982+ fake_mem. extend ( std:: iter:: repeat_n ( 3 , 500 ) ) ;
947983
948- let mut memory = ReadableProcessMemory :: from_file ( file ) ;
984+ let mut memory = FakeProcessMemory { contents : fake_mem } ;
949985
950986 let mut session = crate :: session:: FakeSession :: new ( ) ;
951987 // Should dump first region fully, second partially
@@ -976,7 +1012,7 @@ mod test {
9761012 assert_eq ! ( second_region. region. end_address( ) , 3000 ) ;
9771013 }
9781014
979- #[ cfg( target_os = "linux" ) ]
1015+ #[ cfg( any ( target_os = "linux" , target_os = "windows" ) ) ]
9801016 #[ test]
9811017 fn handle_dumps_current_process_regions ( ) {
9821018 let mut session = crate :: session:: FakeSession :: new ( ) ;
@@ -995,7 +1031,7 @@ mod test {
9951031 assert ! ( session. replies:: <Item >( ) . any( Result :: is_ok) ) ;
9961032 }
9971033
998- #[ cfg( target_os = "linux" ) ]
1034+ #[ cfg( any ( target_os = "linux" , target_os = "windows" ) ) ]
9991035 #[ test]
10001036 fn filters_regions ( ) {
10011037 let mut session = crate :: session:: FakeSession :: new ( ) ;
0 commit comments