use {
super::CpuInfoError,
crate::minidump_format::*,
scroll::Pwrite,
std::{
collections::HashSet,
fs::File,
io::{BufRead, BufReader, Read},
path,
},
};
type Result<T> = std::result::Result<T, CpuInfoError>;
pub fn parse_cpus_from_sysfile(file: &mut File) -> Result<HashSet<u32>> {
let mut res = HashSet::new();
let mut content = String::new();
file.read_to_string(&mut content)?;
for items in content.split(',') {
let items = items.trim();
if items.is_empty() {
continue;
}
let cores: std::result::Result<Vec<_>, _> =
items.split('-').map(|x| x.parse::<u32>()).collect();
let cores = cores?;
match cores.as_slice() {
[x] => {
res.insert(*x);
}
[x, y] => {
for core in *x..=*y {
res.insert(core);
}
}
_ => {
return Err(CpuInfoError::UnparsableCores(format!("{:?}", cores)));
}
}
}
Ok(res)
}
struct CpuInfoEntry {
field: &'static str,
format: char,
bit_lshift: u8,
bit_length: u8,
}
impl CpuInfoEntry {
fn new(field: &'static str, format: char, bit_lshift: u8, bit_length: u8) -> Self {
CpuInfoEntry {
field,
format,
bit_lshift,
bit_length,
}
}
}
#[cfg(target_arch = "arm")]
fn parse_features(val: &str) -> u32 {
struct CpuFeaturesEntry {
tag: &'static str,
hwcaps: u32,
}
impl CpuFeaturesEntry {
fn new(tag: &'static str, hwcaps: u32) -> Self {
CpuFeaturesEntry { tag, hwcaps }
}
}
let cpu_features_entries = [
CpuFeaturesEntry::new("swp", MDCPUInformationARMElfHwCaps::HWCAP_SWP.bits()),
CpuFeaturesEntry::new("half", MDCPUInformationARMElfHwCaps::HWCAP_HALF.bits()),
CpuFeaturesEntry::new("thumb", MDCPUInformationARMElfHwCaps::HWCAP_THUMB.bits()),
CpuFeaturesEntry::new("bit26", MDCPUInformationARMElfHwCaps::HWCAP_26BIT.bits()),
CpuFeaturesEntry::new(
"fastmult",
MDCPUInformationARMElfHwCaps::HWCAP_FAST_MULT.bits(),
),
CpuFeaturesEntry::new("fpa", MDCPUInformationARMElfHwCaps::HWCAP_FPA.bits()),
CpuFeaturesEntry::new("vfp", MDCPUInformationARMElfHwCaps::HWCAP_VFP.bits()),
CpuFeaturesEntry::new("edsp", MDCPUInformationARMElfHwCaps::HWCAP_EDSP.bits()),
CpuFeaturesEntry::new("java", MDCPUInformationARMElfHwCaps::HWCAP_JAVA.bits()),
CpuFeaturesEntry::new("iwmmxt", MDCPUInformationARMElfHwCaps::HWCAP_IWMMXT.bits()),
CpuFeaturesEntry::new("crunch", MDCPUInformationARMElfHwCaps::HWCAP_CRUNCH.bits()),
CpuFeaturesEntry::new(
"thumbee",
MDCPUInformationARMElfHwCaps::HWCAP_THUMBEE.bits(),
),
CpuFeaturesEntry::new("neon", MDCPUInformationARMElfHwCaps::HWCAP_NEON.bits()),
CpuFeaturesEntry::new("vfpv3", MDCPUInformationARMElfHwCaps::HWCAP_VFPv3.bits()),
CpuFeaturesEntry::new(
"vfpv3d16",
MDCPUInformationARMElfHwCaps::HWCAP_VFPv3D16.bits(),
),
CpuFeaturesEntry::new("tls", MDCPUInformationARMElfHwCaps::HWCAP_TLS.bits()),
CpuFeaturesEntry::new("vfpv4", MDCPUInformationARMElfHwCaps::HWCAP_VFPv4.bits()),
CpuFeaturesEntry::new("idiva", MDCPUInformationARMElfHwCaps::HWCAP_IDIVA.bits()),
CpuFeaturesEntry::new("idivt", MDCPUInformationARMElfHwCaps::HWCAP_IDIVT.bits()),
CpuFeaturesEntry::new("idiv", MDCPUInformationARMElfHwCaps::HWCAP_IDIV.bits()),
];
let mut ehwc = 0;
for tag in val.split_whitespace() {
for entry in &cpu_features_entries {
if entry.tag == tag {
ehwc |= entry.hwcaps;
break;
}
}
}
ehwc
}
#[cfg(target_arch = "aarch64")]
fn parse_features(_val: &str) -> u32 {
0
}
pub fn write_cpu_information(sys_info: &mut MDRawSystemInfo) -> Result<()> {
let cpu_id_entries = [
CpuInfoEntry::new("CPU implementer", 'x', 24, 8),
CpuInfoEntry::new("CPU variant", 'x', 20, 4),
CpuInfoEntry::new("CPU part", 'x', 4, 12),
CpuInfoEntry::new("CPU revision", 'd', 0, 4),
];
if cfg!(target_arch = "aarch64") {
sys_info.processor_architecture =
MDCPUArchitecture::PROCESSOR_ARCHITECTURE_ARM64_OLD as u16;
} else {
sys_info.processor_architecture = MDCPUArchitecture::PROCESSOR_ARCHITECTURE_ARM as u16;
}
sys_info.number_of_processors = 0;
sys_info.processor_level = 1; sys_info.processor_revision = 42;
if let Ok(mut present_file) = File::open("/sys/devices/system/cpu/present") {
let cpus_present = parse_cpus_from_sysfile(&mut present_file).unwrap_or_default();
if let Ok(mut possible_file) = File::open("/sys/devices/system/cpu/possible") {
let cpus_possible = parse_cpus_from_sysfile(&mut possible_file).unwrap_or_default();
let intersection = cpus_present.intersection(&cpus_possible).count();
let cpu_count = std::cmp::min(255, intersection) as u8;
sys_info.number_of_processors = cpu_count;
}
}
let cpuinfo_file = match File::open(path::PathBuf::from("/proc/cpuinfo")) {
Ok(x) => x,
Err(_) => {
return Ok(());
}
};
let mut cpuid = 0;
let mut elf_hwcaps = 0;
for line in BufReader::new(cpuinfo_file).lines() {
let line = line?;
if line.trim().is_empty() {
continue;
}
let (field, value) = if let Some(ind) = line.find(':') {
(&line[..ind], Some(&line[ind + 1..]))
} else {
(line.as_str(), None)
};
if let Some(val) = value {
for entry in &cpu_id_entries {
if field != entry.field {
continue;
}
let rr = if val.starts_with("0x") || entry.format == 'x' {
usize::from_str_radix(val.trim_start_matches("0x"), 16)
} else {
val.parse()
};
if let Ok(mut result) = rr {
result &= (1 << entry.bit_length) - 1;
result <<= entry.bit_lshift;
cpuid |= result as u32;
}
}
}
if cfg!(target_arch = "arm") {
if field == "Processor" {
if let Some(val) = value.and_then(|v| v.split_whitespace().last()) {
sys_info.processor_level = val[2..val.len() - 2].parse::<u16>().unwrap_or(5);
}
}
} else {
if field == "CPU architecture" {
sys_info.processor_level = match value.and_then(|v| v.parse::<u16>().ok()) {
Some(v) => v,
None => {
continue;
}
};
}
}
if field == "Features" {
if let Some(val) = value {
elf_hwcaps = parse_features(val);
}
}
}
sys_info
.cpu
.data
.pwrite_with(cpuid, 0, scroll::Endian::Little)
.expect("impossible");
sys_info
.cpu
.data
.pwrite_with(
elf_hwcaps,
std::mem::size_of::<u32>(),
scroll::Endian::Little,
)
.expect("impossible");
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
extern crate std;
use std::io::Write;
fn new_file(content: &str) -> File {
let mut file = tempfile::Builder::new()
.prefix("cpu_sets")
.tempfile()
.unwrap();
write!(file, "{}", content).unwrap();
std::fs::File::open(file).unwrap()
}
#[test]
fn test_empty_count() {
let mut file = new_file("");
let set = parse_cpus_from_sysfile(&mut file).expect("Failed to parse empty file");
assert_eq!(set.len(), 0);
}
#[test]
fn test_one_cpu() {
let mut file = new_file("10");
let set = parse_cpus_from_sysfile(&mut file).expect("Failed to file");
assert_eq!(set, [10,].iter().copied().collect());
}
#[test]
fn test_one_cpu_newline() {
let mut file = new_file("10\n");
let set = parse_cpus_from_sysfile(&mut file).expect("Failed to file");
assert_eq!(set, [10,].iter().copied().collect());
}
#[test]
fn test_two_cpus() {
let mut file = new_file("1,10\n");
let set = parse_cpus_from_sysfile(&mut file).expect("Failed to file");
assert_eq!(set, [1, 10].iter().copied().collect());
}
#[test]
fn test_two_cpus_with_range() {
let mut file = new_file("1-2\n");
let set = parse_cpus_from_sysfile(&mut file).expect("Failed to file");
assert_eq!(set, [1, 2].iter().copied().collect());
}
#[test]
fn test_ten_cpus_with_range() {
let mut file = new_file("9-18\n");
let set = parse_cpus_from_sysfile(&mut file).expect("Failed to file");
assert_eq!(set, (9..=18).collect());
}
#[test]
fn test_multiple_items() {
let mut file = new_file("0, 2-4, 128\n");
let set = parse_cpus_from_sysfile(&mut file).expect("Failed to file");
assert_eq!(set, [0, 2, 3, 4, 128].iter().copied().collect());
}
#[test]
fn test_intersects_with() {
let mut file1 = new_file("9-19\n");
let mut set1 = parse_cpus_from_sysfile(&mut file1).expect("Failed to file");
assert_eq!(set1, (9..=19).collect());
let mut file2 = new_file("16-24\n");
let set2 = parse_cpus_from_sysfile(&mut file2).expect("Failed to file");
assert_eq!(set2, (16..=24).collect());
set1 = set1.intersection(&set2).copied().collect();
assert_eq!(set1, (16..=19).collect());
}
#[test]
fn test_intersects_with_discontinuous() {
let mut file1 = new_file("0, 2-4, 7, 10\n");
let mut set1 = parse_cpus_from_sysfile(&mut file1).expect("Failed to file");
assert_eq!(set1, [0, 2, 3, 4, 7, 10].iter().copied().collect());
let mut file2 = new_file("0-2, 5, 8-10\n");
let set2 = parse_cpus_from_sysfile(&mut file2).expect("Failed to file");
assert_eq!(set2, [0, 1, 2, 5, 8, 9, 10].iter().copied().collect());
set1 = set1.intersection(&set2).copied().collect();
assert_eq!(set1, [0, 2, 10].iter().copied().collect());
}
#[test]
fn test_bad_input() {
let mut file = new_file("abc\n");
let _set = parse_cpus_from_sysfile(&mut file).expect_err("Did not fail to parse");
}
#[test]
fn test_bad_input_range() {
let mut file = new_file("1-abc\n");
let _set = parse_cpus_from_sysfile(&mut file).expect_err("Did not fail to parse");
}
}