tsffs/os/windows/
mod.rs

1use anyhow::{anyhow, ensure, Result};
2use debug_info::{Module, Process, SymbolInfo};
3use ffi2::ffi;
4use intervaltree::IntervalTree;
5use kernel::{find_kernel_with_idt, KernelInfo};
6use raw_cstr::AsRawCstr;
7use simics::{
8    debug, get_interface, get_object, get_processor_number, info, sys::cpu_cb_handle_t, warn,
9    ConfObject, CpuInstrumentationSubscribeInterface, IntRegisterInterface,
10    ProcessorInfoV2Interface,
11};
12use std::{
13    collections::{hash_map::Entry, HashMap, HashSet},
14    ffi::c_void,
15    path::{Path, PathBuf},
16};
17use structs::WindowsKpcr;
18use util::read_virtual;
19
20use vergilius::bindings::*;
21
22use crate::{source_cov::SourceCache, Tsffs};
23
24use super::DebugInfoConfig;
25
26pub mod debug_info;
27pub mod idt;
28pub mod kernel;
29pub mod paging;
30pub mod pdb;
31pub mod structs;
32pub mod util;
33
34const KUSER_SHARED_DATA_ADDRESS_X86_64: u64 = 0xFFFFF78000000000;
35
36#[derive(Debug)]
37pub struct CpuInstrumentationCbHandle(usize);
38
39impl From<*mut cpu_cb_handle_t> for CpuInstrumentationCbHandle {
40    fn from(value: *mut cpu_cb_handle_t) -> Self {
41        Self(value as usize)
42    }
43}
44
45impl From<CpuInstrumentationCbHandle> for *mut cpu_cb_handle_t {
46    fn from(value: CpuInstrumentationCbHandle) -> Self {
47        value.0 as *mut _
48    }
49}
50
51#[derive(Debug, Default)]
52/// Container for various types of information about a running Windows OS
53pub struct WindowsOsInfo {
54    /// Kernel info
55    pub kernel_info: Option<KernelInfo>,
56    /// Per-CPU current process, there may be overlap between them.
57    pub processes: HashMap<i32, Process>,
58    /// Per-CPU kernel module list, there may be overlap between them.
59    pub modules: HashMap<i32, Vec<Module>>,
60    /// Per-CPU Symbol lookup trees
61    pub symbol_lookup_trees: HashMap<i32, IntervalTree<u64, SymbolInfo>>,
62    /// Cache of full names of both processes and kernel modules which are not found from
63    /// the pdb server
64    pub not_found_full_name_cache: HashSet<String>,
65    /// Callbacks on instruction to do coverage lookups
66    pub instruction_callbacks: HashMap<i32, CpuInstrumentationCbHandle>,
67}
68
69impl WindowsOsInfo {
70    /// Collect or refresh OS info. Typically run on new CR3 writes to refresh for
71    /// possibly-changed address space mappings.
72    pub fn collect<P>(
73        &mut self,
74        processor: *mut ConfObject,
75        download_directory: P,
76        user_debug_info: &mut DebugInfoConfig,
77        source_cache: &SourceCache,
78    ) -> Result<()>
79    where
80        P: AsRef<Path>,
81    {
82        info!(get_object("tsffs")?, "Collecting Windows OS information");
83        let processor_nr = get_processor_number(processor)?;
84        let mut processor_info_v2: ProcessorInfoV2Interface = get_interface(processor)?;
85
86        if self.kernel_info.is_none() {
87            info!(get_object("tsffs")?, "Collecting kernel information");
88            // Make sure we're running 64-bit Windows
89            ensure!(
90                processor_info_v2.get_logical_address_width()? == 64,
91                "Only 64-bit Windows is supported"
92            );
93
94            let kuser_shared_data = read_virtual::<windows_10_0_22631_2428_x64::_KUSER_SHARED_DATA>(
95                processor,
96                KUSER_SHARED_DATA_ADDRESS_X86_64,
97            )?;
98
99            let (maj, min, build) = (
100                kuser_shared_data.NtMajorVersion,
101                kuser_shared_data.NtMinorVersion,
102                kuser_shared_data.NtBuildNumber,
103            );
104
105            ensure!(maj == 10, "Only Windows 10/11 is supported");
106
107            // Initialize the KPCR to make sure we have a valid one at gs_base
108            let _ = WindowsKpcr::new(processor, maj, min, build)?;
109            let kernel_base = find_kernel_with_idt(processor, build)?;
110
111            info!(get_object("tsffs")?, "Found kernel base {kernel_base:#x}");
112
113            self.kernel_info = Some(KernelInfo::new(
114                processor,
115                "ntoskrnl.exe",
116                kernel_base,
117                download_directory.as_ref(),
118                &mut self.not_found_full_name_cache,
119                user_debug_info,
120            )?);
121        }
122
123        info!(get_object("tsffs")?, "Collecting process list");
124
125        self.processes.insert(
126            processor_nr,
127            self.kernel_info
128                .as_mut()
129                .expect("Kernel Info must be set at this point")
130                .current_process(
131                    processor,
132                    download_directory.as_ref(),
133                    &mut self.not_found_full_name_cache,
134                    user_debug_info,
135                )?,
136        );
137
138        info!(get_object("tsffs")?, "Collecting module list");
139
140        self.modules.insert(
141            processor_nr,
142            self.kernel_info
143                .as_mut()
144                .expect("Kernel Info must be set at this point")
145                .loaded_module_list(
146                    processor,
147                    download_directory.as_ref(),
148                    &mut self.not_found_full_name_cache,
149                    user_debug_info,
150                )?,
151        );
152
153        let elements = self
154            .modules
155            .get_mut(&processor_nr)
156            .ok_or_else(|| anyhow!("No modules for processor {processor_nr}"))?
157            .iter_mut()
158            .filter_map(|m| {
159                m.intervals(source_cache).ok().or_else(|| {
160                    get_object("tsffs")
161                        .and_then(|obj| {
162                            debug!(
163                                obj,
164                                "Failed (or skipped) getting intervals for module {}", &m.full_name
165                            );
166                            Err(
167                                anyhow!("Failed to get intervals for module {}", &m.full_name)
168                                    .into(),
169                            )
170                        })
171                        .ok()
172                })
173            })
174            .collect::<Vec<_>>()
175            .into_iter()
176            .chain(
177                self.kernel_info
178                    .as_mut()
179                    .expect("Kernel Info must be set at this point")
180                    .current_process(
181                        processor,
182                        download_directory.as_ref(),
183                        &mut self.not_found_full_name_cache,
184                        user_debug_info,
185                    )?
186                    .modules
187                    .iter_mut()
188                    .filter_map(|m| {
189                        m.intervals(source_cache).ok().or_else(|| {
190                            get_object("tsffs")
191                                .and_then(|obj| {
192                                    debug!(
193                                        obj,
194                                        "Failed (or skipped) getting intervals for module {}",
195                                        &m.full_name
196                                    );
197                                    Err(anyhow!(
198                                        "Failed to get intervals for module {}",
199                                        &m.full_name
200                                    )
201                                    .into())
202                                })
203                                .ok()
204                        })
205                    })
206                    .collect::<Vec<_>>(),
207            )
208            .flatten()
209            .collect::<Vec<_>>();
210
211        let mut filtered_elements = HashSet::new();
212
213        // Deduplicate elements by their range
214        let elements = elements
215            .into_iter()
216            .filter(|e| filtered_elements.insert(e.range.clone()))
217            .collect::<Vec<_>>();
218
219        // Populate elements into the coverage record set
220        elements.iter().map(|e| &e.value).for_each(|si| {
221            if let Some(first) = si.lines.first() {
222                let record = user_debug_info.coverage.get_or_insert_mut(&first.file_path);
223                record.add_function_if_not_exists(
224                    first.start_line as usize,
225                    si.lines.last().map(|l| l.end_line as usize),
226                    &si.name,
227                );
228                si.lines.iter().for_each(|l| {
229                    (l.start_line..=l.end_line).for_each(|line| {
230                        record.add_line_if_not_exists(line as usize);
231                    });
232                });
233            }
234        });
235
236        self.symbol_lookup_trees.insert(
237            processor_nr,
238            elements.iter().cloned().collect::<IntervalTree<_, _>>(),
239        );
240
241        Ok(())
242    }
243}
244
245impl Tsffs {
246    /// Triggered on control register write to refresh windows OS information if necessary
247    pub fn on_control_register_write_windows_symcov(
248        &mut self,
249        trigger_obj: *mut ConfObject,
250        register_nr: i64,
251        value: i64,
252    ) -> Result<()> {
253        let mut int_register: IntRegisterInterface = get_interface(trigger_obj)?;
254        let processor_nr = get_processor_number(trigger_obj)?;
255
256        if self.processors.contains_key(&processor_nr)
257            && self.coverage_enabled
258            && self.windows
259            && self.symbolic_coverage
260            && register_nr == int_register.get_number("cr3".as_raw_cstr()?)? as i64
261            && self
262                .cr3_cache
263                .get(&processor_nr)
264                .is_some_and(|v| *v != value)
265        {
266            info!(
267                    get_object("tsffs")?,
268                    "Got write {value:#x} to CR3 for processor {processor_nr}, refreshing kernel & process mappings"
269                );
270
271            self.windows_os_info.collect(
272                trigger_obj,
273                &self.debuginfo_download_directory,
274                &mut DebugInfoConfig {
275                    system: self.symbolic_coverage_system,
276                    user_debug_info: &self.debug_info,
277                    coverage: &mut self.coverage,
278                },
279                &self.source_file_cache,
280            )?;
281
282            self.cr3_cache.insert(processor_nr, value);
283        }
284
285        Ok(())
286    }
287}