summaryrefslogtreecommitdiffstats
path: root/third_party/rust/embed-manifest/src/embed/coff.rs
blob: bf751c3bf522199c47f2af2fae98f682e33bcf6b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
//! Just as much COFF object file support as is needed to write a resource data segment
//! for GNU Windows targets. Inspired by the `write::Object` code from the `object` crate.
//!
//! Integers are converted from u64 to u32 and added without checking because the manifest
//! data cannot get anywhere close to overflowing unless the supplied application name or
//! number of dependencies was extremely long. If this code was used more generally or if
//! the input was less trustworthy then more checked conversions and checked arithmetic
//! would be needed.

use std::io::{self, Seek, SeekFrom, Write};
use std::time::SystemTime;

#[derive(Debug, Clone, Copy)]
pub enum MachineType {
    I386,
    X86_64,
    Aarch64,
}

impl MachineType {
    pub fn machine(&self) -> u16 {
        match self {
            Self::I386 => 0x014c,
            Self::X86_64 => 0x8664,
            Self::Aarch64 => 0xaa64,
        }
    }

    pub fn relocation_type(&self) -> u16 {
        match self {
            Self::I386 => 7,
            Self::X86_64 => 3,
            Self::Aarch64 => 2,
        }
    }
}

pub struct CoffWriter<W> {
    /// wrapped writer or buffer
    writer: W,
    /// machine type for file header
    machine_type: MachineType,
    // size in bytes of resource section data
    size_of_raw_data: u32,
    // number of relocations at the end of the section
    number_of_relocations: u16,
}

impl<W: Write + Seek> CoffWriter<W> {
    /// Create a new instance wrapping a writer.
    pub fn new(mut writer: W, machine_type: MachineType) -> io::Result<Self> {
        // Add space for file header and section table.
        writer.write_all(&[0; 60])?;
        Ok(Self {
            writer,
            machine_type,
            size_of_raw_data: 0,
            number_of_relocations: 0,
        })
    }

    /// Add data to a section and return its offset within the section.
    pub fn add_data(&mut self, data: &[u8]) -> io::Result<u32> {
        let start = self.size_of_raw_data;
        self.writer.write_all(data)?;
        self.size_of_raw_data = start + data.len() as u32;
        Ok(start)
    }

    // Pad the resource data to a multiple of `n` bytes.
    pub fn align_to(&mut self, n: u32) -> io::Result<()> {
        let offset = self.size_of_raw_data % n;
        if offset != 0 {
            let padding = n - offset;
            for _ in 0..padding {
                self.writer.write_all(&[0])?;
            }
            self.size_of_raw_data += padding;
        }
        Ok(())
    }

    /// Write a relocation for a symbol at the end of the section.
    pub fn add_relocation(&mut self, address: u32) -> io::Result<()> {
        self.number_of_relocations += 1;
        self.writer.write_all(&address.to_le_bytes())?;
        self.writer.write_all(&[0, 0, 0, 0])?;
        self.writer.write_all(&self.machine_type.relocation_type().to_le_bytes())
    }

    /// Write the object and section headers and write the symbol table.
    pub fn finish(mut self) -> io::Result<W> {
        // Get the timestamp for the header. `as` is correct here, as the low 32 bits
        // should be used.
        let timestamp = SystemTime::now()
            .duration_since(SystemTime::UNIX_EPOCH)
            .map_or(0, |d| d.as_secs() as u32);

        // Copy file location of the symbol table.
        let pointer_to_symbol_table = self.writer.stream_position()? as u32;

        // Write the symbols and auxiliary data for the section.
        self.writer.write_all(b".rsrc\0\0\0")?; // name
        self.writer.write_all(&[0, 0, 0, 0])?; // address
        self.writer.write_all(&[1, 0])?; // section number (1-based)
        self.writer.write_all(&[0, 0, 3, 1])?; // type = 0, class = static, aux symbols = 1
        self.writer.write_all(&self.size_of_raw_data.to_le_bytes())?;
        self.writer.write_all(&self.number_of_relocations.to_le_bytes())?;
        self.writer.write_all(&[0; 12])?;

        // Write the empty string table.
        self.writer.write_all(&[0; 4])?;

        // Write the object header.
        let end_of_file = self.writer.seek(SeekFrom::Start(0))?;
        self.writer.write_all(&self.machine_type.machine().to_le_bytes())?;
        self.writer.write_all(&[1, 0])?; // number of sections
        self.writer.write_all(&timestamp.to_le_bytes())?;
        self.writer.write_all(&pointer_to_symbol_table.to_le_bytes())?;
        self.writer.write_all(&[2, 0, 0, 0])?; // number of symbol table entries
        self.writer.write_all(&[0; 4])?; // optional header size = 0, characteristics = 0

        // Write the section header.
        self.writer.write_all(b".rsrc\0\0\0")?;
        self.writer.write_all(&[0; 8])?; // virtual size = 0 and virtual address = 0
        self.writer.write_all(&self.size_of_raw_data.to_le_bytes())?;
        self.writer.write_all(&[60, 0, 0, 0])?; // pointer to raw data
        self.writer.write_all(&(self.size_of_raw_data + 60).to_le_bytes())?; // pointer to relocations
        self.writer.write_all(&[0; 4])?; // pointer to line numbers
        self.writer.write_all(&self.number_of_relocations.to_le_bytes())?;
        self.writer.write_all(&[0; 2])?; // number of line numbers
        self.writer.write_all(&[0x40, 0, 0x30, 0xc0])?; // characteristics

        // Return the inner writer and dispose of this object.
        self.writer.seek(SeekFrom::Start(end_of_file))?;
        Ok(self.writer)
    }
}

/// Returns the bytes for a resource directory table.
///
/// Most of the fields are set to zero, including the timestamp, to aid
/// with making builds reproducible.
///
/// ```c
/// typedef struct {
///     DWORD Characteristics,
///     DWORD TimeDateStamp,
///     WORD MajorVersion,
///     WORD MinorVersion,
///     WORD NumberOfNamedEntries,
///     WORD NumberOfIdEntries
/// } IMAGE_RESOURCE_DIRECTORY;
/// ```
pub fn resource_directory_table(number_of_id_entries: u16) -> [u8; 16] {
    let mut table = [0; 16];
    table[14..16].copy_from_slice(&number_of_id_entries.to_le_bytes());
    table
}

/// Returns the bytes for a resource directory entry for an ID.
///
/// ```c
/// typedef struct {
///     DWORD Name,
///     DWORD OffsetToData
/// } IMAGE_RESOURCE_DIRECTORY_ENTRY;
/// ```
pub fn resource_directory_id_entry(id: u32, offset: u32, subdirectory: bool) -> [u8; 8] {
    let mut entry = [0; 8];
    entry[0..4].copy_from_slice(&id.to_le_bytes());
    let flag: u32 = if subdirectory { 0x80000000 } else { 0 };
    entry[4..8].copy_from_slice(&((offset & 0x7fffffff) | flag).to_le_bytes());
    entry
}

/// Returns the bytes for a resource data entry.
///
/// ```c
/// typedef struct {
///     DWORD OffsetToData,
///     DWORD Size,
///     DWORD CodePage,
///     DWORD Reserved
/// } IMAGE_RESOURCE_DATA_ENTRY;
/// ```
pub fn resource_data_entry(rva: u32, size: u32) -> [u8; 16] {
    let mut entry = [0; 16];
    entry[0..4].copy_from_slice(&rva.to_le_bytes());
    entry[4..8].copy_from_slice(&size.to_le_bytes());
    entry
}