const RAW_MAGIC: u64 = 0xff6c70726f667281;
const EXPECTED_VERSION: u64 = 10;
#[repr(C)]
struct RawHeader {
magic: u64,
version: u64,
binary_ids_size: u64,
num_data: u64,
padding_before_counters: u64,
num_counters: u64,
padding_after_counters: u64,
num_bitmap_bytes: u64,
padding_after_bitmap: u64,
names_size: u64,
counters_delta: u64,
bitmap_delta: u64,
names_delta: u64,
num_vtables: u64,
vnames_size: u64,
value_kind_last: u64,
}
#[allow(dead_code)]
struct ProfileData {
name_ref: u64,
func_hash: u64,
counter_ptr: u64,
bitmap_ptr: u64,
function_ptr: u64,
values_ptr: u64,
num_counters: u32,
num_value_sites: [u16; 3],
num_bitmap_bytes: u32,
}
const DATA_RECORD_SIZE: usize = 64;
#[allow(dead_code)]
pub(crate) struct RawProfile {
pub(crate) num_data: u64,
pub(crate) num_counters: u64,
pub(crate) functions: Vec<FunctionCounters>,
pub(crate) names_size: u64,
}
pub(crate) struct FunctionCounters {
pub(crate) num_counters: u32,
pub(crate) counters: Vec<u64>,
pub(crate) covered: u32,
}
pub(crate) fn parse_raw_profile(data: &[u8]) -> Result<RawProfile, String> {
if data.len() < 128 {
return Err(format!(
"file too small: {} bytes (need at least 128)",
data.len()
));
}
let h = unsafe { &*(data.as_ptr() as *const RawHeader) };
if h.magic != RAW_MAGIC {
return Err(format!(
"bad magic: 0x{:016x} (expected 0x{:016x})",
h.magic, RAW_MAGIC
));
}
let version = h.version & 0x00000000ffffffff;
if version != EXPECTED_VERSION {
return Err(format!(
"unsupported profile version: {} (expected {})",
version, EXPECTED_VERSION
));
}
let mut offset: usize = 128;
let bin_ids_size = h.binary_ids_size as usize;
offset += bin_ids_size;
let num_data = h.num_data as usize;
let data_size = num_data * DATA_RECORD_SIZE;
if offset + data_size > data.len() {
return Err(format!(
"data records extend past end of file (offset={}, need {}, file={})",
offset,
data_size,
data.len()
));
}
let mut functions = Vec::with_capacity(num_data);
for i in 0..num_data {
let rec_offset = offset + i * DATA_RECORD_SIZE;
let rec = read_data_record(&data[rec_offset..]);
functions.push(FunctionCounters {
num_counters: rec.num_counters,
counters: Vec::new(),
covered: 0,
});
}
offset += data_size;
let num_counters = h.num_counters as usize;
let counters_end = offset + num_counters * 8;
if counters_end > data.len() {
return Err(format!(
"counters extend past end of file (offset={}, need {}, file={})",
offset,
num_counters * 8,
data.len()
));
}
let mut ci = 0usize;
for func in &mut functions {
let n = func.num_counters as usize;
let mut covered = 0u32;
let mut vals = Vec::with_capacity(n);
for j in 0..n {
let val = u64::from_le_bytes(
data[offset + (ci + j) * 8..offset + (ci + j) * 8 + 8]
.try_into()
.unwrap(),
);
if val > 0 {
covered += 1;
}
vals.push(val);
}
func.counters = vals;
func.covered = covered;
ci += n;
}
offset += num_counters * 8;
let names_size = h.names_size as usize;
let _names = &data[offset..offset + names_size.min(data.len().saturating_sub(offset))];
Ok(RawProfile {
num_data: h.num_data,
num_counters: h.num_counters,
functions,
names_size: h.names_size,
})
}
fn read_data_record(buf: &[u8]) -> ProfileData {
let get = |off: usize| -> u64 {
u64::from_le_bytes(buf[off..off + 8].try_into().unwrap())
};
ProfileData {
name_ref: get(0),
func_hash: get(8),
counter_ptr: get(16),
bitmap_ptr: get(24),
function_ptr: get(32),
values_ptr: get(40),
num_counters: {
let arr: [u8; 4] = buf[48..52].try_into().unwrap();
u32::from_le_bytes(arr)
},
num_value_sites: [
u16::from_le_bytes(buf[52..54].try_into().unwrap()),
u16::from_le_bytes(buf[54..56].try_into().unwrap()),
u16::from_le_bytes(buf[56..58].try_into().unwrap()),
],
num_bitmap_bytes: {
let arr: [u8; 4] = buf[60..64].try_into().unwrap();
u32::from_le_bytes(arr)
},
}
}