1
+ use core:: mem:: align_of;
2
+
1
3
use crate :: {
2
4
binary:: { level_4_entries:: UsedLevel4Entries , PAGE_SIZE } ,
3
5
boot_info:: TlsTemplate ,
4
6
} ;
5
7
use x86_64:: {
6
8
align_up,
7
9
structures:: paging:: {
8
- mapper:: MapperAllSizes , FrameAllocator , Page , PageSize , PageTableFlags as Flags , PhysFrame ,
9
- Size4KiB ,
10
+ mapper:: { MappedFrame , MapperAllSizes , TranslateResult } ,
11
+ FrameAllocator , Page , PageSize , PageTableFlags as Flags , PhysFrame , Size4KiB , Translate ,
10
12
} ,
11
13
PhysAddr , VirtAddr ,
12
14
} ;
@@ -17,6 +19,9 @@ use xmas_elf::{
17
19
ElfFile ,
18
20
} ;
19
21
22
+ /// Used by [`Inner::make_mut`] and [`Inner::clean_copied_flag`].
23
+ const COPIED : Flags = Flags :: BIT_9 ;
24
+
20
25
struct Loader < ' a , M , F > {
21
26
elf_file : ElfFile < ' a > ,
22
27
inner : Inner < ' a , M , F > ,
@@ -31,7 +36,7 @@ struct Inner<'a, M, F> {
31
36
32
37
impl < ' a , M , F > Loader < ' a , M , F >
33
38
where
34
- M : MapperAllSizes ,
39
+ M : MapperAllSizes + Translate ,
35
40
F : FrameAllocator < Size4KiB > ,
36
41
{
37
42
fn new (
75
80
program:: sanity_check ( program_header, & self . elf_file ) ?;
76
81
}
77
82
78
- // Apply relocations in physical memory.
79
- for program_header in self . elf_file . program_iter ( ) {
80
- if let Type :: Dynamic = program_header. get_type ( ) ? {
81
- self . inner
82
- . handle_dynamic_segment ( program_header, & self . elf_file ) ?
83
- }
84
- }
85
-
86
83
// Load the segments into virtual memory.
87
84
let mut tls_template = None ;
88
85
for program_header in self . elf_file . program_iter ( ) {
@@ -106,6 +103,17 @@ where
106
103
| Type :: ProcessorSpecific ( _) => { }
107
104
}
108
105
}
106
+
107
+ // Apply relocations in virtual memory.
108
+ for program_header in self . elf_file . program_iter ( ) {
109
+ if let Type :: Dynamic = program_header. get_type ( ) ? {
110
+ self . inner
111
+ . handle_dynamic_segment ( program_header, & self . elf_file ) ?
112
+ }
113
+ }
114
+
115
+ self . inner . remove_copied_flags ( & self . elf_file ) . unwrap ( ) ;
116
+
109
117
Ok ( tls_template)
110
118
}
111
119
@@ -123,7 +131,7 @@ where
123
131
124
132
impl < ' a , M , F > Inner < ' a , M , F >
125
133
where
126
- M : MapperAllSizes ,
134
+ M : MapperAllSizes + Translate ,
127
135
F : FrameAllocator < Size4KiB > ,
128
136
{
129
137
fn handle_load_segment ( & mut self , segment : ProgramHeader ) -> Result < ( ) , & ' static str > {
@@ -175,7 +183,6 @@ where
175
183
log:: info!( "Mapping bss section" ) ;
176
184
177
185
let virt_start_addr = VirtAddr :: new ( segment. virtual_addr ( ) ) + self . virtual_address_offset ;
178
- let phys_start_addr = self . kernel_offset + segment. offset ( ) ;
179
186
let mem_size = segment. mem_size ( ) ;
180
187
let file_size = segment. file_size ( ) ;
181
188
@@ -220,47 +227,16 @@ where
220
227
// the remaining part of the frame since the frame is no longer shared with other
221
228
// segments now.
222
229
223
- // calculate the frame where the last segment page is mapped
224
- let orig_frame: PhysFrame =
225
- PhysFrame :: containing_address ( phys_start_addr + file_size - 1u64 ) ;
226
- // allocate a new frame to replace `orig_frame`
227
- let new_frame = self . frame_allocator . allocate_frame ( ) . unwrap ( ) ;
228
-
229
- // zero new frame, utilizing that it's identity-mapped
230
- {
231
- let new_frame_ptr = new_frame. start_address ( ) . as_u64 ( ) as * mut PageArray ;
232
- unsafe { new_frame_ptr. write ( ZERO_ARRAY ) } ;
233
- }
234
-
235
- // copy the data bytes from orig_frame to new_frame
236
- {
237
- log:: info!( "Copy contents" ) ;
238
- let orig_bytes_ptr = orig_frame. start_address ( ) . as_u64 ( ) as * mut u8 ;
239
- let new_bytes_ptr = new_frame. start_address ( ) . as_u64 ( ) as * mut u8 ;
240
-
241
- for offset in 0 ..( data_bytes_before_zero as isize ) {
242
- unsafe {
243
- let orig_byte = orig_bytes_ptr. offset ( offset) . read ( ) ;
244
- new_bytes_ptr. offset ( offset) . write ( orig_byte) ;
245
- }
246
- }
247
- }
248
-
249
- // remap last page from orig_frame to `new_frame`
250
- log:: info!( "Remap last page" ) ;
251
230
let last_page = Page :: containing_address ( virt_start_addr + file_size - 1u64 ) ;
252
- self . page_table
253
- . unmap ( last_page . clone ( ) )
254
- . map_err ( |_err| "Failed to unmap last segment page because of bss memory" ) ?
255
- . 1
256
- . ignore ( ) ;
257
- let flusher = unsafe {
258
- self . page_table
259
- . map_to ( last_page , new_frame , segment_flags , self . frame_allocator )
231
+ let new_frame = unsafe { self . make_mut ( last_page ) } ;
232
+ let new_bytes_ptr = new_frame . start_address ( ) . as_u64 ( ) as * mut u8 ;
233
+ unsafe {
234
+ core :: ptr :: write_bytes (
235
+ new_bytes_ptr ,
236
+ 0 ,
237
+ ( Size4KiB :: SIZE - data_bytes_before_zero ) as usize ,
238
+ ) ;
260
239
}
261
- . map_err ( |_err| "Failed to remap last segment page because of bss memory" ) ?;
262
- // we operate on an inactive page table, so we don't need to flush our changes
263
- flusher. ignore ( ) ;
264
240
}
265
241
266
242
// map additional frames for `.bss` memory that is not present in source file
@@ -288,6 +264,104 @@ where
288
264
Ok ( ( ) )
289
265
}
290
266
267
+ /// This method is intended for making the memory loaded by a Load segment mutable.
268
+ ///
269
+ /// All memory from a Load segment starts out by mapped to the same frames that
270
+ /// contain the elf file. Thus writing to memory in that state will cause aliasing issues.
271
+ /// To avoid that, we allocate a new frame, copy all bytes from the old frame to the new frame,
272
+ /// and remap the page to the new frame. At this point the page no longer aliases the elf file
273
+ /// and we can write to it.
274
+ ///
275
+ /// When we map the new frame we also set [`COPIED`] flag in the page table flags, so that
276
+ /// we can detect if the frame has already been copied when we try to modify the page again.
277
+ ///
278
+ /// ## Safety
279
+ /// - `page` should be a page mapped by a Load segment.
280
+ ///
281
+ /// ## Panics
282
+ /// Panics if the page is not mapped in `self.page_table`.
283
+ unsafe fn make_mut ( & mut self , page : Page ) -> PhysFrame {
284
+ let ( frame, flags) = match self . page_table . translate ( page. start_address ( ) ) {
285
+ TranslateResult :: Mapped {
286
+ frame,
287
+ offset : _,
288
+ flags,
289
+ } => ( frame, flags) ,
290
+ TranslateResult :: NotMapped => panic ! ( "{:?} is not mapped" , page) ,
291
+ TranslateResult :: InvalidFrameAddress ( _) => unreachable ! ( ) ,
292
+ } ;
293
+ let frame = if let MappedFrame :: Size4KiB ( frame) = frame {
294
+ frame
295
+ } else {
296
+ // We only map 4k pages.
297
+ unreachable ! ( )
298
+ } ;
299
+
300
+ if flags. contains ( COPIED ) {
301
+ // The frame was already copied, we are free to modify it.
302
+ return frame;
303
+ }
304
+
305
+ // Allocate a new frame and copy the memory, utilizing that both frames are identity mapped.
306
+ let new_frame = self . frame_allocator . allocate_frame ( ) . unwrap ( ) ;
307
+ let frame_ptr = frame. start_address ( ) . as_u64 ( ) as * const u8 ;
308
+ let new_frame_ptr = new_frame. start_address ( ) . as_u64 ( ) as * mut u8 ;
309
+ unsafe {
310
+ core:: ptr:: copy_nonoverlapping ( frame_ptr, new_frame_ptr, Size4KiB :: SIZE as usize ) ;
311
+ }
312
+
313
+ // Replace the underlying frame and update the flags.
314
+ self . page_table . unmap ( page) . unwrap ( ) . 1 . ignore ( ) ;
315
+ let new_flags = flags | COPIED ;
316
+ unsafe {
317
+ self . page_table
318
+ . map_to ( page, new_frame, new_flags, self . frame_allocator )
319
+ . unwrap ( )
320
+ . ignore ( ) ;
321
+ }
322
+
323
+ new_frame
324
+ }
325
+
326
+ /// Cleans up the custom flags set by [`Inner::make_mut`].
327
+ fn remove_copied_flags ( & mut self , elf_file : & ElfFile ) -> Result < ( ) , & ' static str > {
328
+ for program_header in elf_file. program_iter ( ) {
329
+ if let Type :: Load = program_header. get_type ( ) ? {
330
+ let start = self . virtual_address_offset + program_header. virtual_addr ( ) ;
331
+ let end = start + program_header. mem_size ( ) ;
332
+ let start = VirtAddr :: new ( start) ;
333
+ let end = VirtAddr :: new ( end) ;
334
+ let start_page = Page :: containing_address ( start) ;
335
+ let end_page = Page :: containing_address ( end - 1u64 ) ;
336
+ for page in Page :: < Size4KiB > :: range_inclusive ( start_page, end_page) {
337
+ // Translate the page and get the flags.
338
+ let res = self . page_table . translate ( page. start_address ( ) ) ;
339
+ let flags = match res {
340
+ TranslateResult :: Mapped {
341
+ frame : _,
342
+ offset : _,
343
+ flags,
344
+ } => flags,
345
+ TranslateResult :: NotMapped | TranslateResult :: InvalidFrameAddress ( _) => {
346
+ unreachable ! ( "has the elf file not been mapped correctly?" )
347
+ }
348
+ } ;
349
+
350
+ if flags. contains ( COPIED ) {
351
+ // Remove the flag.
352
+ unsafe {
353
+ self . page_table
354
+ . update_flags ( page, flags & !COPIED )
355
+ . unwrap ( )
356
+ . ignore ( ) ;
357
+ }
358
+ }
359
+ }
360
+ }
361
+ }
362
+ Ok ( ( ) )
363
+ }
364
+
291
365
fn handle_tls_segment ( & mut self , segment : ProgramHeader ) -> Result < TlsTemplate , & ' static str > {
292
366
Ok ( TlsTemplate {
293
367
start_addr : segment. virtual_addr ( ) + self . virtual_address_offset ,
@@ -385,19 +459,27 @@ where
385
459
match rela. get_type ( ) {
386
460
// R_AMD64_RELATIVE
387
461
8 => {
388
- let offset_in_file = find_offset ( elf_file, rela. get_offset ( ) ) ?
389
- . ok_or ( "Destination of relocation is not mapped in physical memory" ) ?;
390
- let dest_addr = self . kernel_offset + offset_in_file;
391
- let dest_ptr = dest_addr. as_u64 ( ) as * mut u64 ;
392
-
462
+ check_is_in_load ( elf_file, rela. get_offset ( ) ) ?;
463
+ let addr = self . virtual_address_offset + rela. get_offset ( ) ;
393
464
let value = self
394
465
. virtual_address_offset
395
466
. checked_add ( rela. get_addend ( ) )
396
467
. unwrap ( ) ;
397
468
469
+ let ptr = addr as * mut u64 ;
470
+ if ptr as usize % align_of :: < u64 > ( ) != 0 {
471
+ return Err ( "destination of relocation is not aligned" ) ;
472
+ }
473
+
474
+ let virt_addr = VirtAddr :: from_ptr ( ptr) ;
475
+ let page = Page :: containing_address ( virt_addr) ;
476
+ let offset_in_page = virt_addr - page. start_address ( ) ;
477
+
478
+ let new_frame = unsafe { self . make_mut ( page) } ;
479
+ let phys_addr = new_frame. start_address ( ) + offset_in_page;
480
+ let addr = phys_addr. as_u64 ( ) as * mut u64 ;
398
481
unsafe {
399
- // write new value, utilizing that the address identity-mapped
400
- dest_ptr. write ( value) ;
482
+ addr. write ( value) ;
401
483
}
402
484
}
403
485
ty => unimplemented ! ( "relocation type {:x} not supported" , ty) ,
@@ -408,19 +490,19 @@ where
408
490
}
409
491
}
410
492
411
- /// Locate the offset into the elf file corresponding to a virtual address .
412
- fn find_offset ( elf_file : & ElfFile , virt_offset : u64 ) -> Result < Option < u64 > , & ' static str > {
493
+ /// Check that the virtual offset belongs to a load segment .
494
+ fn check_is_in_load ( elf_file : & ElfFile , virt_offset : u64 ) -> Result < ( ) , & ' static str > {
413
495
for program_header in elf_file. program_iter ( ) {
414
496
if let Type :: Load = program_header. get_type ( ) ? {
415
497
if program_header. virtual_addr ( ) <= virt_offset {
416
498
let offset_in_segment = virt_offset - program_header. virtual_addr ( ) ;
417
499
if offset_in_segment < program_header. file_size ( ) {
418
- return Ok ( Some ( program_header . offset ( ) + offset_in_segment ) ) ;
500
+ return Ok ( ( ) ) ;
419
501
}
420
502
}
421
503
}
422
504
}
423
- Ok ( None )
505
+ Err ( "offset is not in load segment" )
424
506
}
425
507
426
508
/// Loads the kernel ELF file given in `bytes` in the given `page_table`.
@@ -429,7 +511,7 @@ fn find_offset(elf_file: &ElfFile, virt_offset: u64) -> Result<Option<u64>, &'st
429
511
/// and a structure describing which level 4 page table entries are in use.
430
512
pub fn load_kernel (
431
513
bytes : & [ u8 ] ,
432
- page_table : & mut impl MapperAllSizes ,
514
+ page_table : & mut ( impl MapperAllSizes + Translate ) ,
433
515
frame_allocator : & mut impl FrameAllocator < Size4KiB > ,
434
516
) -> Result < ( VirtAddr , Option < TlsTemplate > , UsedLevel4Entries ) , & ' static str > {
435
517
let mut loader = Loader :: new ( bytes, page_table, frame_allocator) ?;
0 commit comments