linux_bzimage_builder/
lib.rs

1// SPDX-License-Identifier: MPL-2.0
2
3//! The linux bzImage builder.
4//!
5//! This crate is responsible for building the bzImage. It contains methods to build
6//! the setup binary (with source provided in another crate) and methods to build the
7//! bzImage from the setup binary and the kernel ELF.
8//!
9//! We should build the asterinas kernel as an ELF file, and feed it to the builder to
10//! generate the bzImage. The builder will generate the PE/COFF header for the setup
11//! code and concatenate it to the ELF file to make the bzImage.
12//!
13//! The setup code should be built into the ELF target and we convert it to a flat binary
14//! in the builder.
15
16pub mod encoder;
17mod mapping;
18mod pe_header;
19
20use std::{
21    fs::File,
22    io::{Read, Seek, SeekFrom, Write},
23    path::Path,
24};
25
26use encoder::encode_kernel;
27pub use encoder::PayloadEncoding;
28use mapping::{SetupFileOffset, SetupVA};
29use xmas_elf::program::SegmentData;
30
31/// The type of the bzImage that we are building through `make_bzimage`.
32///
33/// Currently, Legacy32 and Efi64 are mutually exclusive.
34pub enum BzImageType {
35    Legacy32,
36    Efi64,
37}
38
39/// Making a bzImage given the kernel ELF and setup source.
40///
41/// Explanations for the arguments:
42///  - `target_image_path`: The path to the target bzImage;
43///  - `image_type`: The type of the bzImage that we are building;
44///  - `kernel_path`: The path to the kernel ELF;
45///  - `setup_elf_path`: The path to the setup ELF;
46///  - `encoding`: The encoding format for compressing the kernel ELF.
47///
48pub fn make_bzimage(
49    target_image_path: &Path,
50    image_type: BzImageType,
51    kernel_path: &Path,
52    setup_elf_path: &Path,
53    encoding: PayloadEncoding,
54) {
55    let mut setup_elf = Vec::new();
56    File::open(setup_elf_path)
57        .unwrap()
58        .read_to_end(&mut setup_elf)
59        .unwrap();
60    let mut setup = to_flat_binary(&setup_elf);
61    // Pad the header with 8-byte alignment.
62    setup.resize((setup.len() + 7) & !7, 0x00);
63
64    let mut kernel = Vec::new();
65    File::open(kernel_path)
66        .unwrap()
67        .read_to_end(&mut kernel)
68        .unwrap();
69    let payload = match image_type {
70        BzImageType::Legacy32 => kernel,
71        BzImageType::Efi64 => encode_kernel(kernel, encoding),
72    };
73
74    let setup_len = setup.len();
75    let payload_len = payload.len();
76    let payload_offset = SetupFileOffset::from(setup_len);
77    fill_legacy_header_fields(&mut setup, payload_len, setup_len, payload_offset.into());
78
79    let mut kernel_image = File::create(target_image_path).unwrap();
80    kernel_image.write_all(&setup).unwrap();
81    kernel_image.write_all(&payload).unwrap();
82
83    let image_size = setup_len + payload_len;
84
85    if matches!(image_type, BzImageType::Efi64) {
86        // Write the PE/COFF header to the start of the file.
87        // Since the Linux boot header starts at 0x1f1, we can write the PE/COFF header directly to the
88        // start of the file without overwriting the Linux boot header.
89        let pe_header = pe_header::make_pe_coff_header(&setup_elf, image_size);
90        assert!(
91            pe_header.header_at_zero.len() <= 0x1f1,
92            "PE/COFF header is too large"
93        );
94
95        kernel_image.seek(SeekFrom::Start(0)).unwrap();
96        kernel_image.write_all(&pe_header.header_at_zero).unwrap();
97        kernel_image
98            .seek(SeekFrom::Start(usize::from(pe_header.relocs.0) as u64))
99            .unwrap();
100        kernel_image.write_all(&pe_header.relocs.1).unwrap();
101    }
102}
103
104/// To build the legacy32 bzImage setup header, the OSDK should use this target.
105pub fn legacy32_rust_target_json() -> &'static str {
106    include_str!("x86_64-i386_pm-none.json")
107}
108
109/// We need a flat binary which satisfies PA delta == File offset delta,
110/// and objcopy does not satisfy us well, so we should parse the ELF and
111/// do our own objcopy job.
112///
113/// Interestingly, the resulting binary should be the same as the memory
114/// dump of the kernel setup header when it's loaded by the bootloader.
115fn to_flat_binary(elf_file: &[u8]) -> Vec<u8> {
116    let elf = xmas_elf::ElfFile::new(elf_file).unwrap();
117    let mut bin = Vec::<u8>::new();
118
119    for program in elf.program_iter() {
120        if program.get_type().unwrap() == xmas_elf::program::Type::Load {
121            let SegmentData::Undefined(header_data) = program.get_data(&elf).unwrap() else {
122                panic!("Unexpected segment data type");
123            };
124            let dst_file_offset = usize::from(SetupFileOffset::from(SetupVA::from(
125                program.virtual_addr() as usize,
126            )));
127            let dst_file_length = program.file_size() as usize;
128            if bin.len() < dst_file_offset + dst_file_length {
129                bin.resize(dst_file_offset + dst_file_length, 0);
130            }
131            let dest_slice = bin[dst_file_offset..dst_file_offset + dst_file_length].as_mut();
132            dest_slice.copy_from_slice(header_data);
133        }
134    }
135
136    bin
137}
138
139/// This function should be used when generating the Linux x86 Boot setup header.
140/// Some fields in the Linux x86 Boot setup header should be filled after assembled.
141/// And the filled fields must have the bytes with values of 0xAB. See
142/// `ostd/src/arch/x86/boot/linux_boot/setup/src/header.S` for more
143/// info on this mechanism.
144fn fill_header_field(header: &mut [u8], offset: usize, value: &[u8]) {
145    let size = value.len();
146    assert_eq!(
147        &header[offset..offset + size],
148        vec![0xABu8; size].as_slice(),
149        "The field {:#x} to be filled must be marked with 0xAB",
150        offset
151    );
152    header[offset..offset + size].copy_from_slice(value);
153}
154
155fn fill_legacy_header_fields(
156    header: &mut [u8],
157    kernel_len: usize,
158    setup_len: usize,
159    payload_offset: SetupVA,
160) {
161    fill_header_field(
162        header,
163        0x248, /* payload_offset */
164        &(usize::from(payload_offset) as u32).to_le_bytes(),
165    );
166
167    fill_header_field(
168        header,
169        0x24C, /* payload_length */
170        &(kernel_len as u32).to_le_bytes(),
171    );
172
173    fill_header_field(
174        header,
175        0x260, /* init_size */
176        &((setup_len + kernel_len) as u32).to_le_bytes(),
177    );
178}