kernel_abi_check/
macos.rs

1use std::collections::BTreeSet;
2
3use eyre::{Context, Result};
4use object::macho::{BuildVersionCommand, LC_BUILD_VERSION};
5use object::read::macho::{MachHeader, MachOFile};
6use object::File;
7
8use crate::Version;
9
10#[derive(Debug, Eq, Ord, PartialEq, PartialOrd)]
11pub enum MacOSViolation {
12    MissingMinOS,
13    IncompatibleMinOS { version: Version },
14}
15
16fn build_version<Mach: MachHeader>(macho64: &MachOFile<Mach>) -> Result<Option<Version>> {
17    let mut load_commands = macho64
18        .macho_load_commands()
19        .context("Cannot get Mach-O binary load commands")?;
20    while let Some(load_command) = load_commands.next().context("Cannot get load command")? {
21        if load_command.cmd() == LC_BUILD_VERSION {
22            let command = load_command.data::<BuildVersionCommand<Mach::Endian>>()?;
23            let version_u32 = command.minos.get(macho64.endian());
24            let major = (version_u32 >> 16) as usize;
25            let minor = ((version_u32 >> 8) as u8) as usize;
26            let patch = (version_u32 as u8) as usize;
27
28            return Ok(Some(Version::from(vec![major, minor, patch])));
29        }
30    }
31
32    Ok(None)
33}
34
35/// Check that kernel binary is compatible with the given macOS version.
36pub fn check_macos(file: &File, macos_version: &Version) -> Result<BTreeSet<MacOSViolation>> {
37    let mut violations = BTreeSet::new();
38
39    let minos = if let File::MachO64(macho64) = &file {
40        build_version(macho64)?
41    } else {
42        return Ok(violations);
43    };
44
45    match minos {
46        Some(object_version) => {
47            if &object_version > macos_version {
48                violations.insert(MacOSViolation::IncompatibleMinOS {
49                    version: object_version,
50                });
51            }
52        }
53        None => {
54            violations.insert(MacOSViolation::MissingMinOS);
55        }
56    }
57
58    Ok(violations)
59}