xtask\tasks/
verify_size.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4use crate::Xtask;
5use anyhow::Context;
6use object::read::Object;
7use object::read::ObjectSection;
8use std::collections::HashSet;
9
10/// Runs a size comparison and outputs a diff of two given binaries
11#[derive(Debug, clap::Parser)]
12#[clap(about = "Verify the size of a binary hasn't changed more than allowed.")]
13pub struct VerifySize {
14    /// Old binary path
15    #[clap(short, long)]
16    original: std::path::PathBuf,
17
18    /// New binary path
19    #[clap(short, long)]
20    new: std::path::PathBuf,
21}
22
23fn verify_sections_size(
24    new: &object::File<'_>,
25    original: &object::File<'_>,
26) -> anyhow::Result<(u64, i64)> {
27    println!(
28        "{:20} {:>15} {:>15} {:>16}",
29        "Section", "Old Size (KiB)", "New Size (KiB)", "Difference (KiB)"
30    );
31
32    let mut total_diff: u64 = 0;
33    let mut net_diff: i64 = 0;
34    let mut total_size: u64 = 0;
35
36    let all_original_sections: Vec<_> = original
37        .sections()
38        .filter_map(|s| s.name().ok().map(|name| name.to_string()))
39        .collect();
40    let all_new_sections: Vec<_> = new
41        .sections()
42        .filter_map(|s| s.name().ok().map(|name| name.to_string()))
43        .collect();
44
45    let all_sections: HashSet<_> = all_original_sections
46        .into_iter()
47        .chain(all_new_sections)
48        .collect();
49
50    for section in all_sections {
51        let name = section;
52
53        let new_size = new
54            .section_by_name(name.as_str())
55            .map(|s| s.size() / 1024)
56            .unwrap_or(0);
57        let original_size = original
58            .section_by_name(name.as_str())
59            .map(|s| s.size() / 1024)
60            .unwrap_or(0);
61        let diff = (new_size as i64) - (original_size as i64);
62        total_diff += diff.unsigned_abs();
63        net_diff += diff;
64        total_size += new_size;
65
66        // Print any sections that have changed in size
67        if new_size != original_size {
68            println!("{name:20} {original_size:15} {new_size:15} {diff:16}");
69        }
70    }
71
72    println!("Total Size: {total_size} KiB.");
73
74    Ok((total_diff, net_diff))
75}
76
77impl Xtask for VerifySize {
78    fn run(self, _ctx: crate::XtaskCtx) -> anyhow::Result<()> {
79        let original = fs_err::read(&self.original)?;
80        let new = fs_err::read(&self.new)?;
81
82        let original_elf = object::File::parse(&*original).with_context(|| {
83            format!(
84                r#"Unable to parse target file "{}"."#,
85                &self.original.display()
86            )
87        })?;
88
89        let new_elf = object::File::parse(&*new).with_context(|| {
90            format!(r#"Unable to parse target file "{}"."#, &self.new.display(),)
91        })?;
92
93        println!("Verifying size for {}:", (&self.new.display()));
94        let (total_diff, net_diff) = verify_sections_size(&new_elf, &original_elf)?;
95
96        println!("Net difference: {net_diff} KiB.");
97        println!("Total difference: {total_diff} KiB.");
98
99        const ALLOWED: u64 = 50;
100        if total_diff > ALLOWED {
101            anyhow::bail!(
102                "{} size verification failed: \
103            The total difference ({} KiB) is greater than the allowed difference ({} KiB).",
104                self.new.display(),
105                total_diff,
106                ALLOWED
107            );
108        }
109
110        Ok(())
111    }
112}