igvmfilegen/
file_loader.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Implements a loader that serializes the loaded state into the IGVM binary format.
5
6use crate::identity_mapping::Measurement;
7use crate::identity_mapping::SnpMeasurement;
8use crate::identity_mapping::TdxMeasurement;
9use crate::identity_mapping::VbsMeasurement;
10use crate::signed_measurement::generate_snp_measurement;
11use crate::signed_measurement::generate_tdx_measurement;
12use crate::signed_measurement::generate_vbs_measurement;
13use crate::vp_context_builder::VpContextBuilder;
14use crate::vp_context_builder::VpContextPageState;
15use crate::vp_context_builder::VpContextState;
16use crate::vp_context_builder::snp::InjectionType;
17use crate::vp_context_builder::snp::SnpHardwareContext;
18use crate::vp_context_builder::tdx::TdxHardwareContext;
19use crate::vp_context_builder::vbs::VbsRegister;
20use crate::vp_context_builder::vbs::VbsVpContext;
21use anyhow::Context;
22use hvdef::Vtl;
23use igvm::IgvmDirectiveHeader;
24use igvm::IgvmFile;
25use igvm::IgvmInitializationHeader;
26use igvm::IgvmPlatformHeader;
27use igvm::IgvmRelocatableRegion;
28use igvm::IgvmRevision;
29use igvm::snp_defs::SevVmsa;
30use igvm_defs::IGVM_VHS_PARAMETER;
31use igvm_defs::IGVM_VHS_PARAMETER_INSERT;
32use igvm_defs::IGVM_VHS_SUPPORTED_PLATFORM;
33use igvm_defs::IgvmPageDataFlags;
34use igvm_defs::IgvmPageDataType;
35use igvm_defs::IgvmPlatformType;
36use igvm_defs::PAGE_SIZE_4K;
37use igvm_defs::SnpPolicy;
38use igvm_defs::TdxPolicy;
39use loader::importer::Aarch64Register;
40use loader::importer::BootPageAcceptance;
41use loader::importer::GuestArch;
42use loader::importer::GuestArchKind;
43use loader::importer::IgvmParameterType;
44use loader::importer::ImageLoad;
45use loader::importer::IsolationConfig;
46use loader::importer::IsolationType;
47use loader::importer::ParameterAreaIndex;
48use loader::importer::X86Register;
49use memory_range::MemoryRange;
50use range_map_vec::Entry;
51use range_map_vec::RangeMap;
52use sha2::Digest;
53use sha2::Sha384;
54use std::collections::BTreeMap;
55use std::fmt::Debug;
56use std::fmt::Display;
57use zerocopy::FromBytes;
58use zerocopy::IntoBytes;
59
60pub const DEFAULT_COMPATIBILITY_MASK: u32 = 0x1;
61
62const TDX_SHARED_GPA_BOUNDARY_BITS: u8 = 47;
63
64fn to_igvm_vtl(vtl: Vtl) -> igvm::hv_defs::Vtl {
65    match vtl {
66        Vtl::Vtl0 => igvm::hv_defs::Vtl::Vtl0,
67        Vtl::Vtl1 => igvm::hv_defs::Vtl::Vtl1,
68        Vtl::Vtl2 => igvm::hv_defs::Vtl::Vtl2,
69    }
70}
71
72/// Page table relocation information kept for debugging purposes.
73// Allow dead code because clippy doesn't count #[derive(Debug)] as non-dead code usage.
74#[expect(dead_code)]
75#[derive(Debug, Clone)]
76struct PageTableRegion {
77    gpa: u64,
78    size_pages: u64,
79    used_size_pages: u64,
80}
81
82#[derive(Debug, Clone)]
83enum RelocationType {
84    PageTable(PageTableRegion),
85    Normal(IgvmRelocatableRegion),
86}
87
88#[derive(Debug, Clone, PartialEq, Eq)]
89struct RangeInfo {
90    tag: String,
91    acceptance: BootPageAcceptance,
92}
93
94pub struct IgvmLoader<R: VbsRegister + GuestArch> {
95    accepted_ranges: RangeMap<u64, RangeInfo>,
96    relocatable_regions: RangeMap<u64, RelocationType>,
97    required_memory: Vec<RequiredMemory>,
98    page_table_region: Option<PageTableRegion>,
99    platform_header: IgvmPlatformHeader,
100    initialization_headers: Vec<IgvmInitializationHeader>,
101    directives: Vec<IgvmDirectiveHeader>,
102    page_data_directives: Vec<IgvmDirectiveHeader>,
103    vp_context: Option<Box<dyn VpContextBuilder<Register = R>>>,
104    max_vtl: Vtl,
105    parameter_areas: BTreeMap<(u64, u32), u32>,
106    isolation_type: LoaderIsolationType,
107    paravisor_present: bool,
108    imported_regions_config_page: Option<u64>,
109}
110
111pub struct IgvmVtlLoader<'a, R: VbsRegister + GuestArch> {
112    loader: &'a mut IgvmLoader<R>,
113    vtl: Vtl,
114    vp_context: Option<VbsVpContext<R>>,
115}
116
117impl<R: VbsRegister + GuestArch> IgvmVtlLoader<'_, R> {
118    pub fn loader(&self) -> &IgvmLoader<R> {
119        self.loader
120    }
121
122    /// Returns a loader for importing an inner image as part of the actual
123    /// (paravisor) image to load.
124    ///
125    /// Use `take_vp_context` on the returned loader to get the VP context that
126    /// the paravisor should load.
127    pub fn nested_loader(&mut self) -> IgvmVtlLoader<'_, R> {
128        IgvmVtlLoader {
129            loader: &mut *self.loader,
130            vtl: Vtl::Vtl0,
131            vp_context: Some(VbsVpContext::new(self.vtl)),
132        }
133    }
134
135    pub fn take_vp_context(&mut self) -> Vec<u8> {
136        self.vp_context
137            .take()
138            .map_or_else(Vec::new, |vp| vp.as_page())
139    }
140}
141
142#[derive(Copy, Clone, Debug, PartialEq, Eq)]
143pub enum LoaderIsolationType {
144    None,
145    Vbs {
146        enable_debug: bool,
147    },
148    Snp {
149        shared_gpa_boundary_bits: Option<u8>,
150        policy: SnpPolicy,
151        injection_type: InjectionType,
152        // TODO SNP: SNP Keys? Other data?
153    },
154    Tdx {
155        policy: TdxPolicy,
156    },
157}
158
159/// A trait to specialize behavior based on different register types for
160/// different architectures.
161pub trait IgvmLoaderRegister: VbsRegister {
162    /// Perform arch specific initialization.
163    fn init(
164        with_paravisor: bool,
165        max_vtl: Vtl,
166        isolation: LoaderIsolationType,
167    ) -> (
168        IgvmPlatformHeader,
169        Vec<IgvmInitializationHeader>,
170        Box<dyn VpContextBuilder<Register = Self>>,
171    );
172
173    /// Generate a measurement based on isolation type.
174    fn generate_measurement(
175        isolation: LoaderIsolationType,
176        initialization_headers: &[IgvmInitializationHeader],
177        directive_headers: &[IgvmDirectiveHeader],
178        svn: u32,
179        debug_enabled: bool,
180    ) -> anyhow::Result<Option<Measurement>>;
181
182    /// The IGVM file revision to use for the built igvm file.
183    fn igvm_revision() -> IgvmRevision;
184}
185
186impl IgvmLoaderRegister for X86Register {
187    fn init(
188        with_paravisor: bool,
189        max_vtl: Vtl,
190        isolation: LoaderIsolationType,
191    ) -> (
192        IgvmPlatformHeader,
193        Vec<IgvmInitializationHeader>,
194        Box<dyn VpContextBuilder<Register = Self>>,
195    ) {
196        match isolation {
197            LoaderIsolationType::None | LoaderIsolationType::Vbs { .. } => {
198                unreachable!("should be handled by common code")
199            }
200            LoaderIsolationType::Snp {
201                shared_gpa_boundary_bits,
202                policy,
203                injection_type,
204            } => {
205                // TODO SNP: assumed that shared_gpa_boundary is always available.
206                let shared_gpa_boundary =
207                    1 << shared_gpa_boundary_bits.expect("shared gpa boundary must be set");
208
209                // Add SNP Platform header
210                let info = IGVM_VHS_SUPPORTED_PLATFORM {
211                    compatibility_mask: DEFAULT_COMPATIBILITY_MASK,
212                    highest_vtl: max_vtl as u8,
213                    platform_type: IgvmPlatformType::SEV_SNP,
214                    platform_version: igvm_defs::IGVM_SEV_SNP_PLATFORM_VERSION,
215                    shared_gpa_boundary,
216                };
217
218                let platform_header = IgvmPlatformHeader::SupportedPlatform(info);
219
220                let init_header = IgvmInitializationHeader::GuestPolicy {
221                    policy: policy.into(),
222                    compatibility_mask: DEFAULT_COMPATIBILITY_MASK,
223                };
224
225                let vp_context_builder = Box::new(SnpHardwareContext::new(
226                    max_vtl,
227                    !with_paravisor,
228                    shared_gpa_boundary,
229                    injection_type,
230                ));
231
232                (platform_header, vec![init_header], vp_context_builder)
233            }
234            LoaderIsolationType::Tdx { policy } => {
235                // NOTE: TDX always has a shared_gpa_boundary and has it at 47 bits.
236                let info = IGVM_VHS_SUPPORTED_PLATFORM {
237                    compatibility_mask: DEFAULT_COMPATIBILITY_MASK,
238                    highest_vtl: max_vtl as u8,
239                    platform_type: IgvmPlatformType::TDX,
240                    platform_version: igvm_defs::IGVM_TDX_PLATFORM_VERSION,
241                    shared_gpa_boundary: 1 << TDX_SHARED_GPA_BOUNDARY_BITS,
242                };
243
244                let platform_header = IgvmPlatformHeader::SupportedPlatform(info);
245
246                let mut init_headers = Vec::new();
247                if u64::from(policy) != 0 {
248                    init_headers.push(IgvmInitializationHeader::GuestPolicy {
249                        policy: policy.into(),
250                        compatibility_mask: DEFAULT_COMPATIBILITY_MASK,
251                    });
252                }
253
254                let vp_context_builder = Box::new(TdxHardwareContext::new(!with_paravisor));
255
256                (platform_header, init_headers, vp_context_builder)
257            }
258        }
259    }
260
261    fn generate_measurement(
262        isolation: LoaderIsolationType,
263        initialization_headers: &[IgvmInitializationHeader],
264        directive_headers: &[IgvmDirectiveHeader],
265        svn: u32,
266        debug_enabled: bool,
267    ) -> anyhow::Result<Option<Measurement>> {
268        let measurement = match isolation {
269            LoaderIsolationType::Snp { .. } => {
270                let ld = generate_snp_measurement(initialization_headers, directive_headers, svn)
271                    .context("generating snp measurement failed")?;
272                Some(Measurement::Snp(SnpMeasurement::new(
273                    ld,
274                    svn,
275                    debug_enabled,
276                )))
277            }
278            LoaderIsolationType::Tdx { .. } => {
279                let mrtd = generate_tdx_measurement(directive_headers)
280                    .context("generating tdx measurement failed")?;
281                Some(Measurement::Tdx(TdxMeasurement::new(
282                    mrtd,
283                    svn,
284                    debug_enabled,
285                )))
286            }
287            LoaderIsolationType::Vbs { enable_debug } => {
288                let boot_digest = generate_vbs_measurement(directive_headers, enable_debug, svn)
289                    .context("generating vbs measurement failed")?;
290                Some(Measurement::Vbs(VbsMeasurement::new(
291                    boot_digest,
292                    svn,
293                    debug_enabled,
294                )))
295            }
296            _ => None,
297        };
298        Ok(measurement)
299    }
300
301    fn igvm_revision() -> IgvmRevision {
302        // For now, x86 built files always uses V1 of the IGVM format. This is
303        // to maintain compatibility with older OS repo loaders that do not
304        // understand the V2 format.
305        IgvmRevision::V1
306    }
307}
308
309impl IgvmLoaderRegister for Aarch64Register {
310    fn init(
311        _with_paravisor: bool,
312        _max_vtl: Vtl,
313        _isolation: LoaderIsolationType,
314    ) -> (
315        IgvmPlatformHeader,
316        Vec<IgvmInitializationHeader>,
317        Box<dyn VpContextBuilder<Register = Self>>,
318    ) {
319        unreachable!("should never be called")
320    }
321
322    fn generate_measurement(
323        _isolation: LoaderIsolationType,
324        _initialization_headers: &[IgvmInitializationHeader],
325        _directive_headers: &[IgvmDirectiveHeader],
326        _svn: u32,
327        _debug_enabled: bool,
328    ) -> anyhow::Result<Option<Measurement>> {
329        Ok(None)
330    }
331
332    fn igvm_revision() -> IgvmRevision {
333        // AArch64 IGVM files are always V2.
334        IgvmRevision::V2 {
335            arch: igvm::Arch::AArch64,
336            page_size: 4096,
337        }
338    }
339}
340
341#[derive(Debug, Clone)]
342struct RequiredMemory {
343    range: MemoryRange,
344    vtl2_protectable: bool,
345}
346
347/// A map file representing information about a given generated IGVM file from a
348/// loader.
349///
350/// This can be used to save additional information about the layout of the
351/// address space that importing an IGVM file will create.
352#[derive(Debug)]
353pub struct MapFile {
354    isolation: LoaderIsolationType,
355    required_memory: Vec<RequiredMemory>,
356    accepted_ranges: Vec<(MemoryRange, RangeInfo)>,
357    relocatable_regions: Vec<(MemoryRange, RelocationType)>,
358}
359
360impl MapFile {
361    /// Emit this map file information to tracing::info.
362    pub fn emit_tracing(&self) {
363        tracing::info!(isolation = ?self.isolation, "IGVM file isolation");
364        tracing::info!("IGVM file layout:");
365        for (range, info) in self.accepted_ranges.iter() {
366            tracing::info!(
367                tag = info.tag,
368                size_bytes = range.len(),
369                "{:#x} - {:#x}",
370                range.start(),
371                range.end(),
372            );
373        }
374
375        if !self.required_memory.is_empty() {
376            tracing::info!("IGVM file required memory:");
377            for region in &self.required_memory {
378                tracing::info!(
379                    size_bytes = region.range.len(),
380                    vtl2_protectable = region.vtl2_protectable,
381                    "{:#x} - {:#x}",
382                    region.range.start(),
383                    region.range.end(),
384                );
385            }
386        }
387
388        if !self.relocatable_regions.is_empty() {
389            tracing::info!("IGVM file relocatable regions:");
390            for (range, info) in self.relocatable_regions.iter().rev() {
391                match info {
392                    RelocationType::PageTable(region) => {
393                        tracing::info!(
394                            size_bytes = region.size_pages * PAGE_SIZE_4K,
395                            "{:#x} - {:#x} pagetable relocation region",
396                            region.gpa,
397                            range.end(),
398                        );
399                    }
400                    RelocationType::Normal(region) => {
401                        tracing::info!(
402                            base_gpa = format_args!("{:#x}", region.base_gpa),
403                            size_bytes = region.size,
404                            minimum_relocation_gpa =
405                                format_args!("{:#x}", region.minimum_relocation_gpa),
406                            maximum_relocation_gpa =
407                                format_args!("{:#x}", region.maximum_relocation_gpa),
408                            relocation_alignment = region.relocation_alignment,
409                            "{:#x} - {:#x} relocation region",
410                            region.base_gpa,
411                            range.end(),
412                        );
413                    }
414                }
415            }
416        }
417    }
418}
419
420impl Display for MapFile {
421    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
422        writeln!(f, "IGVM file isolation: {:?}", self.isolation)?;
423
424        writeln!(f, "IGVM file layout:")?;
425        for (range, info) in &self.accepted_ranges {
426            writeln!(
427                f,
428                "  {:016x} - {:016x} ({:#x} bytes) {}",
429                range.start(),
430                range.end(),
431                range.len(),
432                info.tag
433            )?;
434        }
435
436        if !self.required_memory.is_empty() {
437            writeln!(f, "IGVM file required memory:")?;
438            for region in &self.required_memory {
439                writeln!(
440                    f,
441                    "  {:016x} - {:016x} ({:#x} bytes) {}",
442                    region.range.start(),
443                    region.range.end(),
444                    region.range.len(),
445                    if region.vtl2_protectable {
446                        "VTL2 protectable"
447                    } else {
448                        ""
449                    }
450                )?;
451            }
452        }
453
454        if !self.relocatable_regions.is_empty() {
455            writeln!(f, "IGVM file relocatable regions:")?;
456            for (range, info) in &self.relocatable_regions {
457                match info {
458                    RelocationType::PageTable(region) => {
459                        writeln!(
460                            f,
461                            "  {:016x} - {:016x} ({:#x} bytes) pagetable relocation region",
462                            region.gpa,
463                            range.end(),
464                            region.size_pages * PAGE_SIZE_4K,
465                        )?;
466                    }
467                    RelocationType::Normal(region) => {
468                        writeln!(
469                            f,
470                            "  {:016x} - {:016x} ({:#x} bytes) relocation region",
471                            region.base_gpa,
472                            range.end(),
473                            region.size
474                        )?;
475                    }
476                }
477            }
478        }
479
480        Ok(())
481    }
482}
483
484/// Returns output from finalize
485#[derive(Debug)]
486pub struct IgvmOutput {
487    pub guest: IgvmFile,
488    pub map: MapFile,
489    pub doc: Option<Measurement>,
490}
491
492impl<R: IgvmLoaderRegister + GuestArch + 'static> IgvmLoader<R> {
493    pub fn new(with_paravisor: bool, isolation_type: LoaderIsolationType) -> Self {
494        let vp_context_builder: Option<Box<dyn VpContextBuilder<Register = R>>>;
495        let platform_header;
496        let max_vtl = if with_paravisor { Vtl::Vtl2 } else { Vtl::Vtl0 };
497        let initialization_headers;
498
499        match isolation_type {
500            LoaderIsolationType::None | LoaderIsolationType::Vbs { .. } => {
501                vp_context_builder = Some(Box::new(VbsVpContext::<R>::new(max_vtl)));
502
503                // Add VBS platform header
504                let info = IGVM_VHS_SUPPORTED_PLATFORM {
505                    compatibility_mask: DEFAULT_COMPATIBILITY_MASK,
506                    highest_vtl: max_vtl as u8,
507                    platform_type: IgvmPlatformType::VSM_ISOLATION,
508                    platform_version: igvm_defs::IGVM_VSM_ISOLATION_PLATFORM_VERSION,
509                    shared_gpa_boundary: 0,
510                };
511
512                platform_header = IgvmPlatformHeader::SupportedPlatform(info);
513                initialization_headers = Vec::new();
514            }
515            _ => {
516                let (header, init_headers, vp_builder) =
517                    R::init(with_paravisor, max_vtl, isolation_type);
518                platform_header = header;
519                initialization_headers = init_headers;
520                vp_context_builder = Some(vp_builder);
521            }
522        }
523
524        IgvmLoader {
525            accepted_ranges: RangeMap::new(),
526            relocatable_regions: RangeMap::new(),
527            required_memory: Vec::new(),
528            page_table_region: None,
529            platform_header,
530            initialization_headers,
531            directives: Vec::new(),
532            page_data_directives: Vec::new(),
533            vp_context: vp_context_builder,
534            max_vtl,
535            parameter_areas: BTreeMap::new(),
536            isolation_type,
537            paravisor_present: with_paravisor,
538            imported_regions_config_page: None,
539        }
540    }
541
542    fn generate_cryptographic_hash_of_shared_pages(&mut self) -> Vec<u8> {
543        // Sort the page data directives by GPA to ensure the hash is consistent.
544        self.page_data_directives
545            .sort_unstable_by_key(|directive| match directive {
546                IgvmDirectiveHeader::PageData { gpa, .. } => *gpa,
547                _ => unreachable!("all directives should be IgvmDirectiveHeader::PageData"),
548            });
549
550        // Generate the hash of the unaccepted pages.
551        let mut hasher = Sha384::new();
552        self.page_data_directives.iter().for_each(|directive| {
553            if let IgvmDirectiveHeader::PageData {
554                gpa: _,
555                compatibility_mask: _,
556                flags,
557                data_type,
558                data,
559            } = directive
560            {
561                if *data_type == IgvmPageDataType::NORMAL && flags.shared() {
562                    // Measure the pages. If the data length is smaller than a page then zero extend
563                    // the data to a full page.
564                    let mut zero_data;
565                    let data_to_hash = if data.len() < PAGE_SIZE_4K as usize {
566                        zero_data = vec![0; PAGE_SIZE_4K as usize];
567                        zero_data[..data.len()].copy_from_slice(data);
568                        &zero_data
569                    } else {
570                        data
571                    };
572
573                    hasher.update(data_to_hash);
574                }
575            }
576        });
577        hasher.finalize().to_vec()
578    }
579
580    /// Finalize the loader state, returning an IGVM file.
581    pub fn finalize(mut self, guest_svn: u32) -> anyhow::Result<IgvmOutput> {
582        // Finalize any VP state.
583        let mut state = Vec::new();
584        self.vp_context.take().unwrap().finalize(&mut state);
585
586        for context in state {
587            match context {
588                VpContextState::Page(VpContextPageState {
589                    page_base,
590                    page_count,
591                    acceptance,
592                    data,
593                }) => {
594                    self.import_pages(page_base, page_count, "vp-context-page", acceptance, &data)
595                        .context("failed to import vp context page")?;
596                }
597                VpContextState::Directive(directive) => {
598                    self.directives.push(directive);
599                }
600            }
601        }
602
603        // Merge adjacent accepted ranges with the same tag and acceptance
604        // to undo fragmentation from chunked imports.
605        self.accepted_ranges
606            .merge_adjacent(range_map_vec::u64_is_adjacent);
607
608        // Put list of accepted pages into the config region, if there
609        if let Some(page_base) = self.imported_regions_config_page {
610            let mut imported_regions_data: Vec<_> = self.imported_regions();
611
612            // Add this config page as well
613            imported_regions_data.push(loader_defs::paravisor::ImportedRegionDescriptor::new(
614                page_base, 1, true,
615            ));
616
617            // The accepted regions have been guaranteed to not overlap,
618            // so just sort by the base page number
619            imported_regions_data.sort_by_key(|region| region.base_page_number);
620
621            // All shared pages have been imported. Generate the secure cryptographic hash of the unaccepted
622            // imported pages.
623            let hash = self.generate_cryptographic_hash_of_shared_pages();
624            let page_header = loader_defs::paravisor::ImportedRegionsPageHeader {
625                sha384_hash: hash
626                    .as_bytes()
627                    .try_into()
628                    .expect("hash should be correct size"),
629            };
630
631            let mut imported_regions_page = page_header.as_bytes().to_vec();
632
633            // Append the (sorted) imported region data.
634            imported_regions_page.extend_from_slice(imported_regions_data.as_bytes());
635
636            // This list should be measured
637            self.import_pages(
638                page_base,
639                1,
640                "loader-imported-regions",
641                BootPageAcceptance::Exclusive,
642                imported_regions_page.as_bytes(),
643            )
644            .context("failed to import config regions")?;
645        }
646
647        // Finalize parameter pages with insert directives.
648        for ((page_base, _page_count), index) in self.parameter_areas.iter() {
649            self.directives.push(IgvmDirectiveHeader::ParameterInsert(
650                IGVM_VHS_PARAMETER_INSERT {
651                    gpa: page_base * PAGE_SIZE_4K,
652                    compatibility_mask: DEFAULT_COMPATIBILITY_MASK,
653                    parameter_area_index: *index,
654                },
655            ));
656        }
657
658        // Merge the page_data_directives into the others directives. This must be done before
659        // generating the launch measurement.
660        self.directives.append(&mut self.page_data_directives);
661
662        // Generate the launch measurement for the isolation type being used.
663        // The measurement is output for external signing.
664        let doc = R::generate_measurement(
665            self.isolation_type,
666            &self.initialization_headers,
667            &self.directives,
668            guest_svn,
669            self.confidential_debug(),
670        )?;
671
672        // Display a report about the build igvm file's layout.
673        let map_file = MapFile {
674            isolation: self.isolation_type,
675            required_memory: self.required_memory,
676            accepted_ranges: self
677                .accepted_ranges
678                .iter()
679                .rev()
680                .map(|(range, info)| {
681                    (
682                        MemoryRange::from_4k_gpn_range(*range.start()..(range.end() + 1)),
683                        info.clone(),
684                    )
685                })
686                .collect(),
687            relocatable_regions: self
688                .relocatable_regions
689                .iter()
690                .rev()
691                .map(|(range, info)| {
692                    (
693                        MemoryRange::new(*range.start()..(range.end() + 1)),
694                        info.clone(),
695                    )
696                })
697                .collect(),
698        };
699
700        map_file.emit_tracing();
701
702        // Create an IGVM file with the loader's internal state.
703        let igvm_file = IgvmFile::new(
704            R::igvm_revision(),
705            vec![self.platform_header],
706            self.initialization_headers,
707            self.directives,
708        )
709        .context("unable to create igvm file")?;
710
711        let output = IgvmOutput {
712            guest: igvm_file,
713            map: map_file,
714            doc,
715        };
716        Ok(output)
717    }
718
719    /// Accept a new page range with a given acceptance into the map of accepted
720    /// ranges.
721    fn accept_new_range(
722        &mut self,
723        page_base: u64,
724        page_count: u64,
725        tag: &str,
726        acceptance: BootPageAcceptance,
727    ) -> anyhow::Result<()> {
728        let page_end = page_base + page_count - 1;
729        match self.accepted_ranges.entry(page_base..=page_end) {
730            Entry::Overlapping(entry) => {
731                let (overlap_start, overlap_end, ref overlap_info) = *entry.get();
732                Err(anyhow::anyhow!(
733                    "{} at {} ({:?}) overlaps {} at {}",
734                    tag,
735                    MemoryRange::from_4k_gpn_range(page_base..page_end + 1),
736                    acceptance,
737                    overlap_info.tag,
738                    MemoryRange::from_4k_gpn_range(overlap_start..overlap_end + 1),
739                ))
740            }
741            Entry::Vacant(entry) => {
742                entry.insert(RangeInfo {
743                    tag: tag.to_string(),
744                    acceptance,
745                });
746                Ok(())
747            }
748        }
749    }
750
751    fn imported_regions(&self) -> Vec<loader_defs::paravisor::ImportedRegionDescriptor> {
752        // N.B. If the imported regions page grows too large, contiguous
753        // regions with the same acceptance type (but different tags) could
754        // be coalesced here to reduce the descriptor count.
755        self.accepted_ranges
756            .iter()
757            .map(|(r, info)| {
758                loader_defs::paravisor::ImportedRegionDescriptor::new(
759                    *r.start(),
760                    r.end() - r.start() + 1,
761                    info.acceptance != BootPageAcceptance::Shared,
762                )
763            })
764            .collect()
765    }
766
767    /// The guest architecture used by this loader.
768    pub fn arch(&self) -> GuestArchKind {
769        R::arch()
770    }
771
772    /// Returns true if this is an isolated guest with debug enabled, false
773    /// otherwise.
774    pub fn confidential_debug(&self) -> bool {
775        match self.isolation_type {
776            LoaderIsolationType::Vbs { enable_debug } => enable_debug,
777            LoaderIsolationType::Snp { policy, .. } => policy.debug() == 1,
778            LoaderIsolationType::Tdx { policy } => policy.debug_allowed() == 1,
779            _ => false,
780        }
781    }
782
783    pub fn loader(&mut self) -> IgvmVtlLoader<'_, R> {
784        IgvmVtlLoader {
785            vtl: self.max_vtl,
786            loader: self,
787            vp_context: None,
788        }
789    }
790
791    fn import_pages(
792        &mut self,
793        page_base: u64,
794        page_count: u64,
795        debug_tag: &str,
796        acceptance: BootPageAcceptance,
797        mut data: &[u8],
798    ) -> Result<(), anyhow::Error> {
799        tracing::debug!(
800            page_base,
801            ?acceptance,
802            page_count,
803            data_size = data.len(),
804            "Importing page",
805        );
806
807        // Pages must not overlap already accepted ranges
808        self.accept_new_range(page_base, page_count, debug_tag, acceptance)?;
809
810        // Page count must be larger or equal to data.
811        if page_count * PAGE_SIZE_4K < data.len() as u64 {
812            anyhow::bail!(
813                "data len {:x} is larger than page_count {page_count:x}",
814                data.len()
815            );
816        }
817
818        // VpContext imports are handled differently, as they have a different IGVM header
819        // type than normal data pages.
820        if acceptance == BootPageAcceptance::VpContext {
821            // This is only supported on SNP currently.
822            match self.isolation_type {
823                LoaderIsolationType::Snp { .. } => {}
824                _ => {
825                    anyhow::bail!("vpcontext acceptance only supported on SNP");
826                }
827            }
828
829            // Data size must match SNP VMSA size.
830            if data.len() != size_of::<SevVmsa>() {
831                anyhow::bail!("data len {:x} does not match VMSA size", data.len());
832            }
833
834            // Page count must be 1.
835            if page_count != 1 {
836                anyhow::bail!("page count {page_count:x} for snp vmsa is not 1");
837            }
838
839            self.directives.push(IgvmDirectiveHeader::SnpVpContext {
840                gpa: page_base * PAGE_SIZE_4K,
841                compatibility_mask: DEFAULT_COMPATIBILITY_MASK,
842                vp_index: 0,
843                vmsa: Box::new(SevVmsa::read_from_bytes(data).expect("should be correct size")), // TODO: zerocopy: map_err (https://github.com/microsoft/openvmm/issues/759)
844            });
845        } else {
846            for page in page_base..page_base + page_count {
847                let (data_type, flags) = match acceptance {
848                    BootPageAcceptance::Exclusive => {
849                        (IgvmPageDataType::NORMAL, IgvmPageDataFlags::new())
850                    }
851                    BootPageAcceptance::ExclusiveUnmeasured => (
852                        IgvmPageDataType::NORMAL,
853                        IgvmPageDataFlags::new().with_unmeasured(true),
854                    ),
855                    BootPageAcceptance::ErrorPage => todo!(),
856                    BootPageAcceptance::SecretsPage => {
857                        (IgvmPageDataType::SECRETS, IgvmPageDataFlags::new())
858                    }
859                    BootPageAcceptance::CpuidPage => {
860                        (IgvmPageDataType::CPUID_DATA, IgvmPageDataFlags::new())
861                    }
862                    BootPageAcceptance::CpuidExtendedStatePage => {
863                        (IgvmPageDataType::CPUID_XF, IgvmPageDataFlags::new())
864                    }
865                    BootPageAcceptance::VpContext => unreachable!(),
866                    BootPageAcceptance::Shared => (
867                        IgvmPageDataType::NORMAL,
868                        IgvmPageDataFlags::new().with_shared(true),
869                    ),
870                };
871
872                // Split data slice into data to be imported for this page and remaining.
873                let import_data_len = std::cmp::min(PAGE_SIZE_4K as usize, data.len());
874                let (import_data, data_remaining) = data.split_at(import_data_len);
875                data = data_remaining;
876
877                self.page_data_directives
878                    .push(IgvmDirectiveHeader::PageData {
879                        gpa: page * PAGE_SIZE_4K,
880                        compatibility_mask: DEFAULT_COMPATIBILITY_MASK,
881                        flags,
882                        data_type,
883                        data: import_data.to_vec(),
884                    });
885            }
886        }
887
888        Ok(())
889    }
890}
891
892impl<R: IgvmLoaderRegister + GuestArch + 'static> ImageLoad<R> for IgvmVtlLoader<'_, R> {
893    fn isolation_config(&self) -> IsolationConfig {
894        match self.loader.isolation_type {
895            LoaderIsolationType::None => IsolationConfig {
896                paravisor_present: self.loader.paravisor_present,
897                isolation_type: IsolationType::None,
898                shared_gpa_boundary_bits: None,
899            },
900            LoaderIsolationType::Vbs { .. } => IsolationConfig {
901                paravisor_present: self.loader.paravisor_present,
902                isolation_type: IsolationType::Vbs,
903                shared_gpa_boundary_bits: None,
904            },
905            LoaderIsolationType::Snp {
906                shared_gpa_boundary_bits,
907                policy: _,
908                injection_type: _,
909            } => IsolationConfig {
910                paravisor_present: self.loader.paravisor_present,
911                isolation_type: IsolationType::Snp,
912                shared_gpa_boundary_bits,
913            },
914            LoaderIsolationType::Tdx { .. } => IsolationConfig {
915                paravisor_present: self.loader.paravisor_present,
916                isolation_type: IsolationType::Tdx,
917                shared_gpa_boundary_bits: Some(TDX_SHARED_GPA_BOUNDARY_BITS),
918            },
919        }
920    }
921
922    fn create_parameter_area(
923        &mut self,
924        page_base: u64,
925        page_count: u32,
926        debug_tag: &str,
927    ) -> anyhow::Result<ParameterAreaIndex> {
928        self.create_parameter_area_with_data(page_base, page_count, debug_tag, &[])
929    }
930
931    fn create_parameter_area_with_data(
932        &mut self,
933        page_base: u64,
934        page_count: u32,
935        debug_tag: &str,
936        initial_data: &[u8],
937    ) -> anyhow::Result<ParameterAreaIndex> {
938        let area_id = (page_base, page_count);
939
940        // Allocate a new parameter area, that must not overlap other accepted ranges.
941        self.loader.accept_new_range(
942            page_base,
943            page_count as u64,
944            debug_tag,
945            BootPageAcceptance::ExclusiveUnmeasured,
946        )?;
947
948        let index: u32 = self
949            .loader
950            .parameter_areas
951            .len()
952            .try_into()
953            .expect("parameter area greater than u32");
954        self.loader.parameter_areas.insert(area_id, index);
955
956        // Add the newly allocated parameter area index to headers.
957        self.loader
958            .directives
959            .push(IgvmDirectiveHeader::ParameterArea {
960                number_of_bytes: page_count as u64 * PAGE_SIZE_4K,
961                parameter_area_index: index,
962                initial_data: initial_data.to_vec(),
963            });
964
965        tracing::debug!(
966            index,
967            page_base,
968            page_count,
969            initial_data_len = initial_data.len(),
970            "Creating new parameter area",
971        );
972
973        Ok(ParameterAreaIndex(index))
974    }
975
976    fn import_parameter(
977        &mut self,
978        parameter_area: ParameterAreaIndex,
979        byte_offset: u32,
980        parameter_type: IgvmParameterType,
981    ) -> anyhow::Result<()> {
982        let index = parameter_area.0;
983
984        if index >= self.loader.parameter_areas.len() as u32 {
985            anyhow::bail!("invalid parameter area index: {:x}", index);
986        }
987
988        tracing::debug!(
989            ?parameter_type,
990            parameter_area_index = parameter_area.0,
991            byte_offset,
992            "Importing parameter",
993        );
994
995        let info = IGVM_VHS_PARAMETER {
996            parameter_area_index: index,
997            byte_offset,
998        };
999
1000        let header = match parameter_type {
1001            IgvmParameterType::VpCount => IgvmDirectiveHeader::VpCount(info),
1002            IgvmParameterType::Srat => IgvmDirectiveHeader::Srat(info),
1003            IgvmParameterType::Madt => IgvmDirectiveHeader::Madt(info),
1004            IgvmParameterType::Slit => IgvmDirectiveHeader::Slit(info),
1005            IgvmParameterType::Pptt => IgvmDirectiveHeader::Pptt(info),
1006            IgvmParameterType::MmioRanges => IgvmDirectiveHeader::MmioRanges(info),
1007            IgvmParameterType::MemoryMap => IgvmDirectiveHeader::MemoryMap(info),
1008            IgvmParameterType::CommandLine => IgvmDirectiveHeader::CommandLine(info),
1009            IgvmParameterType::DeviceTree => IgvmDirectiveHeader::DeviceTree(info),
1010        };
1011
1012        self.loader.directives.push(header);
1013
1014        Ok(())
1015    }
1016
1017    fn import_pages(
1018        &mut self,
1019        page_base: u64,
1020        page_count: u64,
1021        debug_tag: &str,
1022        acceptance: BootPageAcceptance,
1023        data: &[u8],
1024    ) -> anyhow::Result<()> {
1025        self.loader
1026            .import_pages(page_base, page_count, debug_tag, acceptance, data)
1027    }
1028
1029    fn import_vp_register(&mut self, register: R) -> anyhow::Result<()> {
1030        if let Some(vp_context) = &mut self.vp_context {
1031            vp_context.import_vp_register(register)
1032        } else {
1033            self.loader
1034                .vp_context
1035                .as_mut()
1036                .unwrap()
1037                .import_vp_register(register);
1038        }
1039
1040        Ok(())
1041    }
1042
1043    fn verify_startup_memory_available(
1044        &mut self,
1045        page_base: u64,
1046        page_count: u64,
1047        memory_type: loader::importer::StartupMemoryType,
1048    ) -> anyhow::Result<()> {
1049        let gpa = page_base * PAGE_SIZE_4K;
1050        let compatibility_mask = DEFAULT_COMPATIBILITY_MASK;
1051        let number_of_bytes = (page_count * PAGE_SIZE_4K)
1052            .try_into()
1053            .expect("startup memory request overflowed u32");
1054
1055        tracing::trace!(
1056            page_base,
1057            page_count,
1058            ?memory_type,
1059            number_of_bytes,
1060            "verify memory"
1061        );
1062
1063        // Set VTL2 protectable flag on isolation types which make sense
1064        // TODO SNP: Temporarily allow this on all isolation types to force the host to generate
1065        // the correct device tree structures.
1066        let vtl2_protectable =
1067            memory_type == loader::importer::StartupMemoryType::Vtl2ProtectableRam;
1068
1069        self.loader
1070            .directives
1071            .push(IgvmDirectiveHeader::RequiredMemory {
1072                gpa,
1073                compatibility_mask,
1074                number_of_bytes,
1075                vtl2_protectable,
1076            });
1077
1078        self.loader.required_memory.push(RequiredMemory {
1079            range: MemoryRange::new(gpa..gpa + number_of_bytes as u64),
1080            vtl2_protectable,
1081        });
1082
1083        Ok(())
1084    }
1085
1086    fn set_vp_context_page(&mut self, page_base: u64) -> anyhow::Result<()> {
1087        self.loader
1088            .vp_context
1089            .as_mut()
1090            .unwrap()
1091            .set_vp_context_memory(page_base);
1092
1093        Ok(())
1094    }
1095
1096    fn relocation_region(
1097        &mut self,
1098        gpa: u64,
1099        size_bytes: u64,
1100        relocation_alignment: u64,
1101        minimum_relocation_gpa: u64,
1102        maximum_relocation_gpa: u64,
1103        apply_rip_offset: bool,
1104        apply_gdtr_offset: bool,
1105        vp_index: u16,
1106    ) -> anyhow::Result<()> {
1107        if let Some(overlap) = self
1108            .loader
1109            .relocatable_regions
1110            .get_range(gpa..=(gpa + size_bytes - 1))
1111        {
1112            anyhow::bail!(
1113                "new relocation region overlaps existing region {:?}",
1114                overlap
1115            );
1116        }
1117
1118        if !size_bytes.is_multiple_of(PAGE_SIZE_4K) {
1119            anyhow::bail!("relocation size {size_bytes:#x} must be a multiple of 4K");
1120        }
1121
1122        if !relocation_alignment.is_multiple_of(PAGE_SIZE_4K) {
1123            anyhow::bail!(
1124                "relocation alignment {relocation_alignment:#x} must be a multiple of 4K"
1125            );
1126        }
1127
1128        if !gpa.is_multiple_of(relocation_alignment) {
1129            anyhow::bail!(
1130                "relocation base {gpa:#x} must be aligned to relocation alignment {relocation_alignment:#x}"
1131            );
1132        }
1133
1134        if !minimum_relocation_gpa.is_multiple_of(relocation_alignment) {
1135            anyhow::bail!(
1136                "relocation minimum GPA {minimum_relocation_gpa:#x} must be aligned to relocation alignment {relocation_alignment:#x}"
1137            );
1138        }
1139
1140        if !maximum_relocation_gpa.is_multiple_of(relocation_alignment) {
1141            anyhow::bail!(
1142                "relocation maximum GPA {maximum_relocation_gpa:#x} must be aligned to relocation alignment {relocation_alignment:#x}"
1143            );
1144        }
1145
1146        self.loader
1147            .initialization_headers
1148            .push(IgvmInitializationHeader::RelocatableRegion {
1149                compatibility_mask: DEFAULT_COMPATIBILITY_MASK,
1150                relocation_alignment,
1151                relocation_region_gpa: gpa,
1152                relocation_region_size: size_bytes,
1153                minimum_relocation_gpa,
1154                maximum_relocation_gpa,
1155                is_vtl2: self.vtl == Vtl::Vtl2,
1156                apply_rip_offset,
1157                apply_gdtr_offset,
1158                vp_index,
1159                vtl: to_igvm_vtl(self.vtl),
1160            });
1161
1162        self.loader.relocatable_regions.insert(
1163            gpa..=gpa + size_bytes - 1,
1164            RelocationType::Normal(IgvmRelocatableRegion {
1165                base_gpa: gpa,
1166                size: size_bytes,
1167                minimum_relocation_gpa,
1168                maximum_relocation_gpa,
1169                relocation_alignment,
1170                is_vtl2: self.vtl == Vtl::Vtl2,
1171                apply_rip_offset,
1172                apply_gdtr_offset,
1173                vp_index,
1174                vtl: to_igvm_vtl(self.vtl),
1175            }),
1176        );
1177
1178        Ok(())
1179    }
1180
1181    fn page_table_relocation(
1182        &mut self,
1183        page_table_gpa: u64,
1184        size_pages: u64,
1185        used_size_pages: u64,
1186        vp_index: u16,
1187    ) -> anyhow::Result<()> {
1188        // can only be one set
1189        if let Some(region) = &self.loader.page_table_region {
1190            anyhow::bail!("page table relocation already set {:?}", region)
1191        }
1192
1193        if used_size_pages > size_pages {
1194            anyhow::bail!(
1195                "used size pages {used_size_pages:#x} cannot be greater than size pages {size_pages:#x}"
1196            );
1197        }
1198
1199        let end_gpa = page_table_gpa + size_pages * PAGE_SIZE_4K - 1;
1200
1201        // cannot override other relocatable regions
1202        if let Some(overlap) = self
1203            .loader
1204            .relocatable_regions
1205            .get_range(page_table_gpa..=end_gpa)
1206        {
1207            anyhow::bail!(
1208                "new page table relocation region overlaps existing region {:?}",
1209                overlap
1210            );
1211        }
1212
1213        self.loader.initialization_headers.push(
1214            IgvmInitializationHeader::PageTableRelocationRegion {
1215                compatibility_mask: DEFAULT_COMPATIBILITY_MASK,
1216                gpa: page_table_gpa,
1217                size: size_pages * PAGE_SIZE_4K,
1218                used_size: used_size_pages * PAGE_SIZE_4K,
1219                vp_index,
1220                vtl: to_igvm_vtl(self.vtl),
1221            },
1222        );
1223
1224        let region = PageTableRegion {
1225            gpa: page_table_gpa,
1226            size_pages,
1227            used_size_pages,
1228        };
1229
1230        self.loader.relocatable_regions.insert(
1231            page_table_gpa..=end_gpa,
1232            RelocationType::PageTable(region.clone()),
1233        );
1234
1235        self.loader.page_table_region = Some(region);
1236
1237        Ok(())
1238    }
1239
1240    fn set_imported_regions_config_page(&mut self, page_base: u64) {
1241        self.loader.imported_regions_config_page = Some(page_base);
1242    }
1243}
1244
1245#[cfg(test)]
1246mod tests {
1247    use super::IgvmLoader;
1248    use super::*;
1249    use crate::identity_mapping::Measurement;
1250    use loader::importer::BootPageAcceptance;
1251    use loader::importer::ImageLoad;
1252    use loader_defs::paravisor::ImportedRegionDescriptor;
1253
1254    #[test]
1255    fn test_snp_measurement() {
1256        use igvm_defs::SnpPolicy;
1257        let ref_ld: [u8; 48] = [
1258            136, 154, 25, 56, 108, 130, 226, 33, 155, 222, 211, 233, 42, 118, 78, 140, 0, 194, 155,
1259            150, 109, 4, 166, 98, 188, 166, 207, 223, 236, 100, 123, 144, 81, 153, 86, 83, 57, 7,
1260            131, 132, 101, 87, 145, 50, 99, 215, 28, 79,
1261        ];
1262
1263        let mut loader = IgvmLoader::<X86Register>::new(
1264            true,
1265            LoaderIsolationType::Snp {
1266                shared_gpa_boundary_bits: Some(39),
1267                policy: SnpPolicy::from((0x1 << 17) | (0x1 << 16) | (0x1f)),
1268                injection_type: InjectionType::Restricted,
1269            },
1270        );
1271        let data = vec![0, 5];
1272        loader
1273            .import_pages(0, 5, "data", BootPageAcceptance::Exclusive, &data)
1274            .unwrap();
1275        loader
1276            .import_pages(5, 5, "data", BootPageAcceptance::ExclusiveUnmeasured, &data)
1277            .unwrap();
1278        loader
1279            .import_pages(10, 1, "data", BootPageAcceptance::Exclusive, &data)
1280            .unwrap();
1281        loader
1282            .import_pages(20, 1, "data", BootPageAcceptance::Shared, &data)
1283            .unwrap();
1284
1285        let igvm_output = loader.finalize(1).unwrap();
1286        let doc = igvm_output.doc.expect("doc");
1287        let Measurement::Snp(snp_measurement) = doc else {
1288            panic!("known to be snp")
1289        };
1290        assert_eq!(ref_ld, snp_measurement.series[0].reference.snp_ld);
1291    }
1292
1293    #[test]
1294    fn test_tdx_measurement() {
1295        let ref_mrtd: [u8; 48] = [
1296            214, 88, 54, 138, 48, 41, 223, 124, 152, 113, 236, 159, 157, 24, 134, 87, 36, 12, 163,
1297            162, 115, 128, 222, 247, 130, 13, 114, 103, 87, 67, 73, 89, 166, 251, 86, 245, 63, 209,
1298            246, 246, 164, 240, 96, 164, 22, 183, 142, 219,
1299        ];
1300
1301        let mut loader = IgvmLoader::<X86Register>::new(
1302            true,
1303            LoaderIsolationType::Tdx {
1304                policy: TdxPolicy::new()
1305                    .with_debug_allowed(0u8)
1306                    .with_sept_ve_disable(0u8),
1307            },
1308        );
1309        let data = vec![0, 5];
1310        loader
1311            .import_pages(0, 5, "data", BootPageAcceptance::Exclusive, &data)
1312            .unwrap();
1313        loader
1314            .import_pages(5, 5, "data", BootPageAcceptance::ExclusiveUnmeasured, &data)
1315            .unwrap();
1316        loader
1317            .import_pages(10, 1, "data", BootPageAcceptance::Exclusive, &data)
1318            .unwrap();
1319        loader
1320            .import_pages(20, 1, "data", BootPageAcceptance::Shared, &data)
1321            .unwrap();
1322
1323        let igvm_output = loader.finalize(1).unwrap();
1324        let doc = igvm_output.doc.expect("doc");
1325        let Measurement::Tdx(tdx_measurement) = doc else {
1326            panic!("known to be tdx")
1327        };
1328        assert_eq!(ref_mrtd, tdx_measurement.series[0].reference.tdx_mrtd);
1329    }
1330
1331    #[test]
1332    fn test_vbs_digest() {
1333        let ref_digest: [u8; 32] = [
1334            0x30, 0x13, 0x4C, 0x9B, 0xB8, 0x9C, 0xD7, 0x2D, 0x8A, 0x41, 0x8D, 0x1E, 0x7A, 0xFB,
1335            0x75, 0x92, 0x7F, 0x45, 0xE8, 0x57, 0x1D, 0xDA, 0x7A, 0xC7, 0xBE, 0x87, 0xD4, 0xB6,
1336            0xC7, 0x2C, 0xA6, 0x4C,
1337        ];
1338        let mut loader = IgvmLoader::<X86Register>::new(
1339            true,
1340            LoaderIsolationType::Vbs {
1341                enable_debug: false,
1342            },
1343        );
1344        {
1345            let mut loader = loader.loader();
1346
1347            let data = vec![0, 5];
1348            loader
1349                .import_pages(0, 5, "data", BootPageAcceptance::Exclusive, &data)
1350                .unwrap();
1351            loader
1352                .import_pages(5, 5, "data", BootPageAcceptance::ExclusiveUnmeasured, &data)
1353                .unwrap();
1354            loader
1355                .import_pages(10, 1, "data", BootPageAcceptance::Exclusive, &data)
1356                .unwrap();
1357            loader
1358                .import_pages(20, 1, "data", BootPageAcceptance::Shared, &data)
1359                .unwrap();
1360        }
1361
1362        let igvm_output = loader.finalize(1).unwrap();
1363        let doc = igvm_output.doc.expect("doc");
1364        let Measurement::Vbs(vbs_measurement) = doc else {
1365            panic!("known to be vbs")
1366        };
1367        assert_eq!(
1368            ref_digest,
1369            vbs_measurement.series[0].reference.vbs_boot_digest
1370        );
1371    }
1372
1373    #[test]
1374    fn test_accepted_regions() {
1375        let mut loader = IgvmLoader::<X86Register>::new(true, LoaderIsolationType::None);
1376
1377        let data = vec![0, 5];
1378        loader
1379            .import_pages(0, 5, "test1", BootPageAcceptance::Exclusive, &data)
1380            .unwrap();
1381
1382        loader
1383            .import_pages(15, 5, "test2", BootPageAcceptance::Exclusive, &data)
1384            .unwrap();
1385
1386        loader
1387            .import_pages(10, 5, "test3", BootPageAcceptance::Exclusive, &data)
1388            .unwrap();
1389
1390        assert_eq!(
1391            loader.imported_regions(),
1392            vec![
1393                ImportedRegionDescriptor::new(15, 5, true),
1394                ImportedRegionDescriptor::new(10, 5, true),
1395                ImportedRegionDescriptor::new(0, 5, true),
1396            ]
1397        );
1398
1399        loader
1400            .import_pages(20, 10, "test1", BootPageAcceptance::Exclusive, &data)
1401            .unwrap();
1402
1403        loader
1404            .import_pages(30, 1, "test2", BootPageAcceptance::Exclusive, &data)
1405            .unwrap();
1406
1407        assert_eq!(
1408            loader.imported_regions(),
1409            vec![
1410                ImportedRegionDescriptor::new(30, 1, true),
1411                ImportedRegionDescriptor::new(20, 10, true),
1412                ImportedRegionDescriptor::new(15, 5, true),
1413                ImportedRegionDescriptor::new(10, 5, true),
1414                ImportedRegionDescriptor::new(0, 5, true),
1415            ]
1416        );
1417    }
1418}