1
1
use nix:: unistd:: { getpid, Pid } ;
2
2
use procfs:: process:: Process ;
3
3
use std:: {
4
+ cmp:: min,
4
5
fs:: File ,
5
6
io:: { BufRead , BufReader } ,
6
7
marker:: PhantomData ,
@@ -77,6 +78,50 @@ impl ReadOp {
77
78
iov_len : self . len ,
78
79
}
79
80
}
81
+
82
+ /// Splits ReadOp so that each resulting ReadOp resides in only one memory page.
83
+ fn split_on_page_boundary ( & self ) -> Vec < ReadOp > {
84
+ let page_size: usize ;
85
+
86
+ unsafe {
87
+ page_size = libc:: sysconf ( libc:: _SC_PAGESIZE) as usize ;
88
+ }
89
+
90
+ let mut out = Vec :: new ( ) ;
91
+
92
+ let mut left = self . len ;
93
+
94
+ let to_next_readop = std:: cmp:: min ( left, page_size - ( ( page_size - 1 ) & self . remote_base ) ) ;
95
+ out. push ( ReadOp {
96
+ remote_base : self . remote_base ,
97
+ len : to_next_readop,
98
+ local_ptr : self . local_ptr ,
99
+ } ) ;
100
+ left -= to_next_readop;
101
+
102
+ loop {
103
+ if left == 0 {
104
+ break ;
105
+ }
106
+ if left < page_size {
107
+ out. push ( ReadOp {
108
+ remote_base : self . remote_base + ( self . len - left) ,
109
+ len : left,
110
+ local_ptr : ( self . local_ptr as usize + ( self . len - left) ) as * mut libc:: c_void ,
111
+ } ) ;
112
+ break ;
113
+ } else {
114
+ out. push ( ReadOp {
115
+ remote_base : self . remote_base + ( self . len - left) ,
116
+ len : page_size,
117
+ local_ptr : ( self . local_ptr as usize + ( self . len - left) ) as * mut libc:: c_void ,
118
+ } ) ;
119
+ left -= page_size;
120
+ }
121
+ }
122
+
123
+ out
124
+ }
80
125
}
81
126
82
127
/// Allows to read memory from different locations in debuggee's memory as a single operation.
@@ -109,11 +154,14 @@ impl<'a> ReadMemory<'a> {
109
154
/// In case of doubt, wrap the type in [`mem::MaybeUninit`].
110
155
// todo: further document mem safety - e.g., what happens in the case of partial read
111
156
pub unsafe fn read < T > ( mut self , val : & ' a mut T , remote_base : usize ) -> Self {
112
- self . read_ops . push ( ReadOp {
113
- remote_base,
114
- len : mem:: size_of :: < T > ( ) ,
115
- local_ptr : val as * mut T as * mut libc:: c_void ,
116
- } ) ;
157
+ self . read_ops . append (
158
+ & mut ReadOp {
159
+ remote_base,
160
+ len : mem:: size_of :: < T > ( ) ,
161
+ local_ptr : val as * mut T as * mut libc:: c_void ,
162
+ }
163
+ . split_on_page_boundary ( ) ,
164
+ ) ;
117
165
118
166
self
119
167
}
@@ -154,65 +202,58 @@ impl<'a> ReadMemory<'a> {
154
202
)
155
203
} ;
156
204
157
- if bytes_read == -1 {
158
- // Check for errors related to permissions.
159
- // todo: restructure the code to be more readable
160
- match nix:: Error :: last ( ) {
161
- nix:: Error :: Sys ( nix:: errno:: Errno :: EFAULT ) => {
162
- let maps = Process :: new ( self . pid . as_raw ( ) ) ?. maps ( ) ?;
163
-
164
- let protected_maps = maps
165
- . iter ( )
166
- . filter ( |map| map. perms . chars ( ) . nth ( 0 ) != Some ( 'r' ) )
167
- . collect :: < Vec < _ > > ( ) ;
168
-
169
- // Splits readOps to those that read from read protected memory and those that do not.
170
- let ( protected, readable) : ( _ , Vec < _ > ) =
171
- self . read_ops . into_iter ( ) . partition ( |read_op| {
172
- protected_maps. iter ( ) . any ( |map| {
173
- map. address . 0 as usize <= read_op. remote_base
174
- && read_op. remote_base < map. address . 1 as usize
175
- } )
176
- } ) ;
177
-
178
- // Read data that can be read using libc::process_vm_readv
179
- let new_remote_iov = readable
180
- . iter ( )
181
- . map ( ReadOp :: as_remote_iovec)
182
- . collect :: < Vec < _ > > ( ) ;
183
-
184
- let new_local_iov = readable
185
- . iter ( )
186
- . map ( ReadOp :: as_local_iovec)
187
- . collect :: < Vec < _ > > ( ) ;
188
-
189
- let new_bytes_read = unsafe {
190
- // todo: document unsafety
191
- libc:: process_vm_readv (
192
- self . pid . into ( ) ,
193
- new_local_iov. as_ptr ( ) ,
194
- new_local_iov. len ( ) as libc:: c_ulong ,
195
- new_remote_iov. as_ptr ( ) ,
196
- new_remote_iov. len ( ) as libc:: c_ulong ,
197
- 0 ,
198
- )
199
- } ;
200
-
201
- if new_bytes_read == -1 {
202
- return Err ( Box :: new ( nix:: Error :: last ( ) ) ) ;
203
- }
205
+ if bytes_read < read_len as isize {
206
+ let maps = Process :: new ( self . pid . as_raw ( ) ) ?. maps ( ) ?;
207
+
208
+ let protected_maps = maps
209
+ . iter ( )
210
+ . filter ( |map| map. perms . chars ( ) . nth ( 0 ) != Some ( 'r' ) )
211
+ . collect :: < Vec < _ > > ( ) ;
212
+
213
+ // Splits readOps to those that read from read protected memory and those that do not.
214
+ let ( protected, readable) : ( _ , Vec < _ > ) =
215
+ self . read_ops . into_iter ( ) . partition ( |read_op| {
216
+ protected_maps. iter ( ) . any ( |map| {
217
+ map. address . 0 as usize <= read_op. remote_base
218
+ && read_op. remote_base < map. address . 1 as usize
219
+ } )
220
+ } ) ;
221
+
222
+ // Read data that can be read using libc::process_vm_readv
223
+ let new_remote_iov = readable
224
+ . iter ( )
225
+ . map ( ReadOp :: as_remote_iovec)
226
+ . collect :: < Vec < _ > > ( ) ;
227
+
228
+ let new_local_iov = readable
229
+ . iter ( )
230
+ . map ( ReadOp :: as_local_iovec)
231
+ . collect :: < Vec < _ > > ( ) ;
232
+
233
+ let new_bytes_read = unsafe {
234
+ // todo: document unsafety
235
+ libc:: process_vm_readv (
236
+ self . pid . into ( ) ,
237
+ new_local_iov. as_ptr ( ) ,
238
+ new_local_iov. len ( ) as libc:: c_ulong ,
239
+ new_remote_iov. as_ptr ( ) ,
240
+ new_remote_iov. len ( ) as libc:: c_ulong ,
241
+ 0 ,
242
+ )
243
+ } ;
244
+
245
+ if new_bytes_read == -1 {
246
+ return Err ( Box :: new ( nix:: Error :: last ( ) ) ) ;
247
+ }
204
248
205
- // Read rest of the data using PTRACE_PEEKDATA
206
- for read_op in protected {
207
- unix:: read (
208
- self . pid ,
209
- read_op. remote_base ,
210
- read_op. len ,
211
- read_op. local_ptr ,
212
- ) ?;
213
- }
214
- }
215
- error => return Err ( Box :: new ( error) ) ,
249
+ // Read rest of the data using PTRACE_PEEKDATA
250
+ for read_op in protected {
251
+ unix:: read (
252
+ self . pid ,
253
+ read_op. remote_base ,
254
+ read_op. len ,
255
+ read_op. local_ptr ,
256
+ ) ?;
216
257
}
217
258
}
218
259
@@ -330,10 +371,14 @@ mod tests {
330
371
331
372
#[ test]
332
373
fn read_cross_page_memory ( ) {
333
- let mut read_var_op = [ 0u32 ; 2 ] ;
374
+ let mut read_var_op = [ 0u32 ; PAGE_SIZE + 2 ] ;
375
+
376
+ let mut var = [ 123 ; PAGE_SIZE + 2 ] ;
377
+ var[ 0 ] = 321 ;
378
+ var[ PAGE_SIZE + 1 ] = 234 ;
334
379
335
380
unsafe {
336
- let layout = Layout :: from_size_align ( PAGE_SIZE * 2 , PAGE_SIZE ) . unwrap ( ) ;
381
+ let layout = Layout :: from_size_align ( PAGE_SIZE * 3 , PAGE_SIZE ) . unwrap ( ) ;
337
382
let ptr = alloc_zeroed ( layout) ;
338
383
339
384
let array_ptr = ( ptr as usize + PAGE_SIZE - std:: mem:: size_of :: < u32 > ( ) ) as * mut u8 ;
@@ -342,12 +387,8 @@ mod tests {
342
387
343
388
match fork ( ) {
344
389
Ok ( ForkResult :: Child ) => {
345
-
346
- * ( array_ptr as * mut [ u32 ; 2 ] ) = [ 123 , 456 ] ;
347
- mprotect (
348
- second_page_ptr,
349
- PAGE_SIZE ,
350
- ProtFlags :: PROT_WRITE )
390
+ * ( array_ptr as * mut [ u32 ; PAGE_SIZE + 2 ] ) = var;
391
+ mprotect ( second_page_ptr, PAGE_SIZE , ProtFlags :: PROT_WRITE )
351
392
. expect ( "Failed to mprotect" ) ;
352
393
353
394
// Parent reads memory
@@ -368,8 +409,10 @@ mod tests {
368
409
. read ( & mut read_var_op, array_ptr as * const _ as usize )
369
410
. apply ( )
370
411
. expect ( "Failed to apply memop" ) ;
371
-
372
- assert_eq ! ( [ 123 , 456 ] , read_var_op) ;
412
+
413
+ for i in 0 ..PAGE_SIZE + 2 {
414
+ assert_eq ! ( var[ i] , read_var_op[ i] ) ;
415
+ }
373
416
374
417
dealloc ( ptr, layout) ;
375
418
0 commit comments