use std::{
cell::Cell,
fmt,
num::NonZeroUsize,
os::fd::RawFd,
ptr::{self, NonNull},
sync::{
atomic::{AtomicU32, AtomicU64, Ordering},
OnceLock, RwLock,
},
};
use libc::{c_long, c_void};
use libseccomp::{ScmpArch, ScmpNotifResp};
use memchr::arch::all::is_equal;
use nix::{
errno::Errno,
fcntl::SealFlag,
sys::mman::{mmap, munmap, MapFlags, ProtFlags},
unistd::Pid,
};
use serde::{Serialize, Serializer};
use crate::{
config::{KCOV_HEART_BEAT, SAFE_MFD_FLAGS},
confine::scmp_arch_is_compat32,
cookie::{safe_ftruncate, safe_memfd_create},
fd::{fd_dev_inode, seal_memfd},
hash::SydHashMap,
ioctl::{Ioctl, IoctlMap},
kcov::{
clear_tls_sink, get_kcov_tid, get_kcov_tid_local, get_kcov_tid_remote, get_tls_sink,
set_kcov_tid, set_tls_sink, unbind_kcov_tid, Kcov, KcovId, TraceMode,
},
lookup::MaybeFd,
proc::proc_kcov_read_id,
req::UNotifyEventRequest,
};
extern "C" {
fn syd_kcov_set_syscall(nr: c_long);
}
thread_local! {
static TLS_SYS: Cell<c_long> = const { Cell::new(-1) };
}
thread_local! {
static TLS_CTX: Cell<(usize, u64, u8, i32)> = const { Cell::new((0, 0, 8, -1)) };
}
thread_local! {
static TLS_REC: Cell<bool> = const { Cell::new(false) };
}
pub(crate) struct KcovSnap {
prev: bool,
}
impl KcovSnap {
pub(crate) fn new() -> Self {
let prev = TLS_REC.with(|c| {
let p = c.get();
c.set(true);
p
});
Self { prev }
}
}
impl Drop for KcovSnap {
fn drop(&mut self) {
TLS_REC.with(|c| c.set(self.prev));
}
}
pub(crate) fn kcov_set_syscall(nr: c_long) {
TLS_SYS.with(|c| c.set(nr));
unsafe { syd_kcov_set_syscall(nr) };
}
fn mix_syscall(mut pc: u64) -> u64 {
let nr = TLS_SYS.with(|c| c.get());
if nr > 0 {
pc ^= (nr as u64).wrapping_mul(0x517c_c1b7_2722_0a95);
}
pc
}
const fn to_canon_pc(v: u64) -> u64 {
let v = v & !0xFu64;
if cfg!(target_pointer_width = "64") {
0xFFFF_FFFF_8000_0000u64 | (v & 0x3FFF_FFF0u64)
} else {
let x = (v as u32) & 0x0FFF_FFF0u32;
(0x8000_0000u32 | x) as u64
}
}
fn kcov_cmp_type(size_bytes: u8, is_const: bool) -> u64 {
let size_code = match size_bytes {
1 => 0u64,
2 => 2u64,
4 => 4u64,
8 => 6u64,
_ => 6u64,
};
size_code | u64::from(is_const)
}
const fn kcov_wordsize(arch: ScmpArch) -> u8 {
if scmp_arch_is_compat32(arch) {
4
} else {
8
}
}
fn store_count(map: usize, narrow: bool, val: u64) {
if narrow {
unsafe { (*(map as *const AtomicU32)).store(val as u32, Ordering::Release) };
} else {
unsafe { (*(map as *const AtomicU64)).store(val, Ordering::Release) };
}
}
macro_rules! pc_append {
($ty:ty, $map:expr, $cap:expr, $val:expr) => {{
let count = unsafe { &*($map as *const $ty) };
let mut cnt = count.load(Ordering::Acquire);
loop {
if u64::from(cnt) >= $cap {
return;
}
match count.compare_exchange_weak(cnt, cnt + 1, Ordering::AcqRel, Ordering::Acquire) {
Ok(_) => {
let slot = unsafe { &*($map as *const $ty).add(1 + cnt as usize) };
slot.store($val, Ordering::Relaxed);
return;
}
Err(actual) => cnt = actual,
}
}
}};
}
fn zero_map(map: usize, len: usize) {
if map == 0 || len == 0 {
return;
}
unsafe { ptr::write_bytes(map as *mut u8, 0, len) };
}
fn live_update_pc_clamped(ctx: &KcovCtx, pc: u64) {
if ctx.mode != Some(TraceMode::Pc) || ctx.words <= 1 || ctx.map == 0 {
return;
}
let cap = (ctx.words - 1) as u64;
if ctx.wordsize == 4 {
pc_append!(AtomicU32, ctx.map, cap, pc as u32);
} else {
pc_append!(AtomicU64, ctx.map, cap, pc);
}
}
pub(crate) struct KcovCtx {
pub(crate) dev: u64,
pub(crate) map: usize,
pub(crate) map_len: usize,
pub(crate) words: usize,
pub(crate) wordsize: u8,
pub(crate) mode: Option<TraceMode>,
}
impl Drop for KcovCtx {
fn drop(&mut self) {
if let Some(ptr) = NonNull::new(self.map as *mut c_void) {
let _ = unsafe { munmap(ptr, self.map_len) };
}
}
}
static KCOV_REG: OnceLock<RwLock<SydHashMap<KcovId, KcovCtx>>> = OnceLock::new();
pub(crate) fn kcov_reg() -> &'static RwLock<SydHashMap<KcovId, KcovCtx>> {
KCOV_REG.get_or_init(|| RwLock::new(SydHashMap::default()))
}
static KCOV_MGR: OnceLock<Kcov> = OnceLock::new();
pub(crate) fn kcov_mgr() -> &'static Kcov {
KCOV_MGR.get_or_init(Kcov::new)
}
pub(crate) fn kcov_check_mmap(dev: u64, inode: u64, offset: u64, length: u64) -> Result<(), Errno> {
let (words, wordsize) = {
let reg = kcov_reg().read().unwrap_or_else(|e| e.into_inner());
match reg.get(&KcovId(inode)) {
Some(ctx) if ctx.dev == dev && ctx.map != 0 => (ctx.words, ctx.wordsize),
_ => return Ok(()),
}
};
let size = words as u64 * u64::from(wordsize);
let page = *crate::config::PAGE_SIZE;
let vma_len = match length.checked_add(page - 1) {
Some(sum) => sum & !(page - 1),
None => return Err(Errno::EINVAL),
};
if offset != 0 || vma_len != size {
return Err(Errno::EINVAL);
}
Ok(())
}
pub(crate) fn kcov_open(_tid: Pid) -> Result<MaybeFd, Errno> {
let memfd = safe_memfd_create(c"syd-kcov", *SAFE_MFD_FLAGS)?;
let (dev, kcov_id) = fd_dev_inode(&memfd)?;
kcov_mgr().open(kcov_id)?;
{
let kcov_id = KcovId(kcov_id);
let _snap = KcovSnap::new(); let mut map = kcov_reg().write().unwrap_or_else(|e| e.into_inner());
map.insert(
kcov_id,
KcovCtx {
dev,
map: 0,
map_len: 0,
words: 0,
wordsize: 8,
mode: None,
},
);
}
Ok(memfd.into())
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
struct KcovRemoteArg {
trace_mode: u32,
area_size: u32,
num_handles: u32,
common_handle: u64,
}
const KCOV_SUBSYSTEM_MASK: u64 = 0xffu64 << 56;
const KCOV_INSTANCE_MASK: u64 = 0xffff_ffff;
const KCOV_SUBSYSTEM_COMMON: u64 = 0x00u64 << 56;
const KCOV_SUBSYSTEM_USB: u64 = 0x01u64 << 56;
const KCOV_REMOTE_MAX_HANDLES: u32 = 0x100;
fn kcov_check_handle(
handle: u64,
common_valid: bool,
uncommon_valid: bool,
zero_valid: bool,
) -> bool {
if handle & !(KCOV_SUBSYSTEM_MASK | KCOV_INSTANCE_MASK) != 0 {
return false;
}
match handle & KCOV_SUBSYSTEM_MASK {
KCOV_SUBSYSTEM_COMMON => {
if handle & KCOV_INSTANCE_MASK != 0 {
common_valid
} else {
zero_valid
}
}
KCOV_SUBSYSTEM_USB => uncommon_valid,
_ => false,
}
}
static KCOV_HANDLE_MAP: OnceLock<RwLock<SydHashMap<u64, KcovId>>> = OnceLock::new();
fn kcov_handle_map() -> &'static RwLock<SydHashMap<u64, KcovId>> {
KCOV_HANDLE_MAP.get_or_init(|| RwLock::new(SydHashMap::default()))
}
pub(crate) fn kcov_handle_drop(id: KcovId) {
let mut map = kcov_handle_map().write().unwrap_or_else(|e| e.into_inner());
map.retain(|_, owner| *owner != id);
}
pub(crate) fn kcov_clear_mode(id: KcovId) {
let _snap = KcovSnap::new(); let mut map = kcov_reg().write().unwrap_or_else(|e| e.into_inner());
if let Some(ctx) = map.get_mut(&id) {
ctx.mode = None;
}
}
pub(crate) fn kcov_reg_remove(id: KcovId) {
let _snap = KcovSnap::new(); let mut map = kcov_reg().write().unwrap_or_else(|e| e.into_inner());
map.remove(&id);
}
#[allow(clippy::cognitive_complexity)]
pub(crate) fn kcov_ioctl(request: &UNotifyEventRequest) -> Result<ScmpNotifResp, Errno> {
let tid = request.scmpreq.pid();
let fd = match RawFd::try_from(request.scmpreq.data.args[0]) {
Ok(fd) if fd >= 0 => fd,
_ => return Err(Errno::EBADF),
};
let kcov_id = match proc_kcov_read_id(tid, fd) {
Ok(id) => id,
Err(_) => return Err(Errno::ENOTTY),
};
#[allow(clippy::cast_possible_truncation)]
let kcov_req = Ioctl::from(request.scmpreq.data.args[1] as u32);
let kcov_arg = request.scmpreq.data.args[2];
let kcov_cmd = match KcovIoctl::try_from((kcov_req, request.scmpreq.data.arch)) {
Ok(cmd) => cmd,
Err(_) => return Err(Errno::ENOTTY),
};
#[allow(clippy::cast_possible_truncation)]
let result = match kcov_cmd {
KcovIoctl::InitTrace => {
let words = kcov_arg;
let wordsize = kcov_wordsize(request.scmpreq.data.arch);
kcov_mgr().init_trace(kcov_id, words, wordsize)?;
let words = words as usize;
let Some(len) = words.checked_mul(usize::from(wordsize)) else {
kcov_mgr().reset(kcov_id);
return Err(Errno::EOVERFLOW);
};
let mapped = (|| -> Result<NonNull<c_void>, Errno> {
let memfd = request.get_fd(fd)?;
safe_ftruncate(&memfd, i64::try_from(len).or(Err(Errno::EINVAL))?)?;
let flags = SealFlag::F_SEAL_SEAL | SealFlag::F_SEAL_SHRINK | SealFlag::F_SEAL_GROW;
seal_memfd(&memfd, flags)?;
let nz = NonZeroUsize::new(len).ok_or(Errno::EINVAL)?;
unsafe {
mmap(
None,
nz,
ProtFlags::PROT_READ | ProtFlags::PROT_WRITE,
MapFlags::MAP_SHARED,
&memfd,
0,
)
}
})();
let base = match mapped {
Ok(base) => base,
Err(err) => {
kcov_mgr().reset(kcov_id);
return Err(err);
}
};
let _snap = KcovSnap::new(); let mut map = kcov_reg().write().unwrap_or_else(|e| e.into_inner());
let ctx = match map.get_mut(&kcov_id) {
Some(ctx) => ctx,
None => {
let _ = unsafe { munmap(base, len) };
kcov_mgr().reset(kcov_id);
return Err(Errno::ENOTTY);
}
};
if let Some(old) = NonNull::new(ctx.map as *mut c_void) {
let _ = unsafe { munmap(old, ctx.map_len) };
}
ctx.words = words;
ctx.wordsize = wordsize;
ctx.map = base.as_ptr() as usize;
ctx.map_len = len;
zero_map(ctx.map, ctx.map_len);
Ok(ok0(request))
}
KcovIoctl::Enable => {
{
let map = kcov_reg().read().unwrap_or_else(|e| e.into_inner());
let ctx = map.get(&kcov_id).ok_or(Errno::ENOTTY)?;
if ctx.words == 0 {
return Err(Errno::EINVAL);
}
}
if get_kcov_tid_local(tid).is_some_and(|other| other != kcov_id) {
return Err(Errno::EBUSY);
}
let mode = match kcov_arg {
0 => TraceMode::Pc,
1 => TraceMode::Cmp,
_ => return Err(Errno::EINVAL),
};
kcov_mgr().enable(kcov_id, mode)?;
{
let _snap = KcovSnap::new(); let mut map = kcov_reg().write().unwrap_or_else(|e| e.into_inner());
let ctx = map.get_mut(&kcov_id).ok_or(Errno::ENOTTY)?;
ctx.mode = Some(mode);
}
set_kcov_tid(tid, kcov_id, false);
Ok(ok0(request))
}
KcovIoctl::RemoteEnable => {
let mut arg = KcovRemoteArg::default();
let buf = unsafe {
std::slice::from_raw_parts_mut(&raw mut arg as *mut u8, size_of::<KcovRemoteArg>())
};
if request.read_mem(buf, kcov_arg, buf.len())? != buf.len() {
return Err(Errno::EFAULT);
}
if arg.num_handles > KCOV_REMOTE_MAX_HANDLES {
return Err(Errno::EINVAL);
}
let num = arg.num_handles as usize;
let mut handles: Vec<u64> = Vec::new();
handles.try_reserve(num).or(Err(Errno::ENOMEM))?;
handles.resize(num, 0);
if num > 0 {
let hbuf = unsafe {
std::slice::from_raw_parts_mut(
handles.as_mut_ptr() as *mut u8,
num * size_of::<u64>(),
)
};
let off = kcov_arg
.checked_add(size_of::<KcovRemoteArg>() as u64)
.ok_or(Errno::EOVERFLOW)?;
if request.read_mem(hbuf, off, hbuf.len())? != hbuf.len() {
return Err(Errno::EFAULT);
}
}
let mut recheck = 0u32;
let rbuf = unsafe { std::slice::from_raw_parts_mut(&raw mut recheck as *mut u8, 4) };
let noff = kcov_arg
.checked_add(std::mem::offset_of!(KcovRemoteArg, num_handles) as u64)
.ok_or(Errno::EOVERFLOW)?;
if request.read_mem(rbuf, noff, 4)? != 4 {
return Err(Errno::EFAULT);
}
if recheck != arg.num_handles {
return Err(Errno::EINVAL);
}
let wordsize = {
let map = kcov_reg().read().unwrap_or_else(|e| e.into_inner());
let ctx = map.get(&kcov_id).ok_or(Errno::ENOTTY)?;
if ctx.words == 0 {
return Err(Errno::EINVAL);
}
ctx.wordsize
};
if get_kcov_tid_remote(tid).is_some_and(|other| other != kcov_id) {
return Err(Errno::EBUSY);
}
let mode = match arg.trace_mode {
0 => TraceMode::Pc,
1 => TraceMode::Cmp,
_ => return Err(Errno::EINVAL),
};
let long_max = if wordsize == 4 {
i32::MAX as u64
} else {
i64::MAX as u64
};
if u64::from(arg.area_size) > long_max / u64::from(wordsize) {
return Err(Errno::EINVAL);
}
for &handle in &handles {
if !kcov_check_handle(handle, false, true, false) {
return Err(Errno::EINVAL);
}
}
if arg.common_handle != 0 && !kcov_check_handle(arg.common_handle, true, false, false) {
return Err(Errno::EINVAL);
}
let mut added: Vec<u64> = Vec::new();
added
.try_reserve(num.saturating_add(1))
.or(Err(Errno::ENOMEM))?;
kcov_mgr().enable(kcov_id, mode)?;
let registered = {
let mut hmap = kcov_handle_map().write().unwrap_or_else(|e| e.into_inner());
let all = handles
.iter()
.copied()
.chain((arg.common_handle != 0).then_some(arg.common_handle));
let mut dup = false;
for handle in all {
if hmap.contains_key(&handle) {
for done in &added {
hmap.remove(done);
}
dup = true;
break;
}
hmap.insert(handle, kcov_id);
added.push(handle);
}
!dup
};
if !registered {
let _ = kcov_mgr().disable(kcov_id);
return Err(Errno::EEXIST);
}
{
let _snap = KcovSnap::new(); let mut map = kcov_reg().write().unwrap_or_else(|e| e.into_inner());
let ctx = map.get_mut(&kcov_id).ok_or(Errno::ENOTTY)?;
ctx.mode = Some(mode);
}
set_kcov_tid(tid, kcov_id, true);
Ok(ok0(request))
}
KcovIoctl::Disable => {
if kcov_arg != 0 {
return Err(Errno::EINVAL);
}
let is_local = get_kcov_tid_local(tid) == Some(kcov_id);
let is_remote = get_kcov_tid_remote(tid) == Some(kcov_id);
if !is_local && !is_remote {
return Err(Errno::EINVAL);
}
kcov_mgr().disable(kcov_id)?;
unbind_kcov_tid(tid, kcov_id);
kcov_clear_mode(kcov_id);
if is_remote {
kcov_handle_drop(kcov_id);
}
Ok(ok0(request))
}
KcovIoctl::ResetTrace => {
let _snap = KcovSnap::new(); let reg = kcov_reg().read().unwrap_or_else(|e| e.into_inner());
let ctx = reg.get(&kcov_id).ok_or(Errno::ENOTTY)?;
if ctx.map != 0 {
let narrow = matches!(ctx.mode, Some(TraceMode::Pc)) && ctx.wordsize == 4;
store_count(ctx.map, narrow, 0);
}
if let Some(mode) = ctx.mode {
emit_heartbeats(ctx.map, ctx.words, ctx.wordsize, mode);
}
Ok(ok0(request))
}
_ => Err(Errno::ENOTTY),
};
result
}
pub(crate) fn kcov_enter_for(tid: Pid) -> Result<(), Errno> {
let kcov_id = match get_kcov_tid(tid) {
Some(id) => {
set_tls_sink(id);
id
}
None => {
clear_tls_sink();
TLS_CTX.with(|c| c.set((0, 0, 8, -1)));
return Ok(());
}
};
let reg = kcov_reg().read().unwrap_or_else(|e| e.into_inner());
let ctx = match reg.get(&kcov_id) {
Some(ctx) => ctx,
None => {
TLS_CTX.with(|c| c.set((0, 0, 8, -1)));
return Ok(());
}
};
if ctx.mode.is_none() || ctx.map == 0 {
TLS_CTX.with(|c| c.set((0, 0, 8, -1)));
return Ok(());
}
let cached_mode = match ctx.mode {
Some(TraceMode::Pc) => 0,
Some(TraceMode::Cmp) => 1,
None => -1,
};
TLS_CTX.with(|c| c.set((ctx.map, ctx.words as u64, ctx.wordsize, cached_mode)));
if ctx.words > 0 {
let (map, words, wordsize, mode) = (ctx.map, ctx.words, ctx.wordsize, ctx.mode.unwrap());
drop(reg);
emit_heartbeats(map, words, wordsize, mode);
}
Ok(())
}
fn emit_heartbeats(map: usize, words: usize, wordsize: u8, mode: TraceMode) {
if map == 0 {
return;
}
let pc = to_canon_pc(mix_syscall(KCOV_HEART_BEAT));
match mode {
TraceMode::Pc if words > 1 => {
if wordsize == 4 {
unsafe { (*(map as *const AtomicU32).add(1)).store(pc as u32, Ordering::Relaxed) };
} else {
unsafe { (*(map as *const AtomicU64).add(1)).store(pc, Ordering::Relaxed) };
}
store_count(map, wordsize == 4, 1);
}
TraceMode::Cmp if (words as u64) * u64::from(wordsize) >= 40 => {
let area = map as *const AtomicU64;
unsafe {
(*area.add(1)).store(kcov_cmp_type(8, false), Ordering::Relaxed);
(*area.add(2)).store(1, Ordering::Relaxed);
(*area.add(3)).store(0, Ordering::Relaxed);
(*area.add(4)).store(pc, Ordering::Relaxed);
}
store_count(map, false, 1);
}
_ => {}
}
}
pub(crate) fn kcov_attach(pid: Pid) {
if let Some(id) = get_kcov_tid(pid) {
set_tls_sink(id);
let _ = kcov_enter_for(pid);
} else {
clear_tls_sink();
}
}
pub(crate) struct KcovScope;
impl KcovScope {
pub(crate) fn enter(pid: Pid, nr: c_long) -> Self {
kcov_attach(pid);
kcov_set_syscall(nr);
let _ = kcov_enter_for(pid);
crate::kcov_edge!();
Self
}
}
impl Drop for KcovScope {
fn drop(&mut self) {
crate::kcov_edge!();
TLS_CTX.with(|c| c.set((0, 0, 8, -1)));
}
}
fn ok0(req: &UNotifyEventRequest) -> ScmpNotifResp {
ScmpNotifResp::new(req.scmpreq.id, 0, 0, 0)
}
#[repr(C)]
pub(crate) struct kcov_ctx {
pub(crate) base: usize,
pub(crate) words: u64,
pub(crate) wordsize: u8,
pub(crate) mode: i32,
}
#[no_mangle]
pub extern "C" fn syd_kcov_get_ctx(out_ctx: *mut kcov_ctx) -> bool {
if out_ctx.is_null() {
return false;
}
let (base, words, wordsize, mode) = TLS_CTX.with(|c| c.get());
if base == 0 || words == 0 {
return false;
}
unsafe {
(*out_ctx).base = base;
(*out_ctx).words = words;
(*out_ctx).wordsize = wordsize;
(*out_ctx).mode = mode;
}
true
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
enum KcovIoctl {
InitTrace,
ResetTrace,
Enable,
RemoteEnable,
UniqueEnable,
Disable,
}
impl TryFrom<(Ioctl, ScmpArch)> for KcovIoctl {
type Error = Errno;
fn try_from(value: (Ioctl, ScmpArch)) -> Result<Self, Errno> {
let (val, arch) = value;
let map = IoctlMap::new(None, true);
let names = map.get_names(val, arch)?.ok_or(Errno::ENOTTY)?;
for name in names {
let name = name.as_bytes();
if is_equal(name, b"KCOV_INIT_TRACE") {
return Ok(Self::InitTrace);
} else if is_equal(name, b"KCOV_RESET_TRACE") {
return Ok(Self::ResetTrace);
} else if is_equal(name, b"KCOV_ENABLE") {
return Ok(Self::Enable);
} else if is_equal(name, b"KCOV_REMOTE_ENABLE") {
return Ok(Self::RemoteEnable);
} else if is_equal(name, b"KCOV_UNIQUE_ENABLE") {
return Ok(Self::UniqueEnable);
} else if is_equal(name, b"KCOV_DISABLE") {
return Ok(Self::Disable);
}
}
Err(Errno::ENOTTY)
}
}
impl fmt::Display for KcovIoctl {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let name = match self {
Self::InitTrace => "kcov_init_trace",
Self::ResetTrace => "kcov_reset_trace",
Self::Enable => "kcov_enable",
Self::RemoteEnable => "kcov_remote_enable",
Self::UniqueEnable => "kcov_unique_enable",
Self::Disable => "kcov_disable",
};
write!(f, "{name}")
}
}
impl Serialize for KcovIoctl {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&self.to_string())
}
}
#[inline(never)]
pub(crate) fn record_pc(pc: u64) -> Result<(), Errno> {
if TLS_REC.with(|c| {
if c.get() {
true
} else {
c.set(true);
false
}
}) {
return Ok(());
}
let pc = to_canon_pc(pc);
if let Some(id) = get_tls_sink() {
let map = kcov_reg().read().unwrap_or_else(|e| e.into_inner());
if let Some(ctx) = map.get(&id) {
live_update_pc_clamped(ctx, pc);
}
}
TLS_REC.with(|c| c.set(false));
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_to_canon_pc_alignment_1() {
let pc = to_canon_pc(0x10);
assert_eq!(pc & 0xF, 0, "must be 16-byte aligned");
}
#[test]
fn test_to_canon_pc_alignment_2() {
let pc = to_canon_pc(0x1F);
assert_eq!(pc & 0xF, 0, "unaligned input must be rounded down");
}
#[test]
fn test_to_canon_pc_zero_1() {
let pc = to_canon_pc(0);
assert_eq!(pc & 0xF, 0);
}
#[test]
fn test_to_canon_pc_kernel_range_1() {
if cfg!(target_pointer_width = "64") {
let pc = to_canon_pc(0x100);
assert!(pc >= 0xFFFF_FFFF_8000_0000u64);
}
}
#[test]
fn test_kcov_cmp_type_size1_const_1() {
assert_eq!(kcov_cmp_type(1, true), 0u64 | 1u64);
}
#[test]
fn test_kcov_cmp_type_size1_not_const_1() {
assert_eq!(kcov_cmp_type(1, false), 0u64);
}
#[test]
fn test_kcov_cmp_type_size2_const_1() {
assert_eq!(kcov_cmp_type(2, true), 2u64 | 1u64);
}
#[test]
fn test_kcov_cmp_type_size4_1() {
assert_eq!(kcov_cmp_type(4, false), 4u64);
}
#[test]
fn test_kcov_cmp_type_size8_1() {
assert_eq!(kcov_cmp_type(8, false), 6u64);
}
#[test]
fn test_kcov_cmp_type_unknown_size_1() {
assert_eq!(kcov_cmp_type(16, false), 6u64);
}
#[test]
fn test_kcov_cmp_type_const_bit_1() {
let with_const = kcov_cmp_type(8, true);
let without_const = kcov_cmp_type(8, false);
assert_eq!(with_const, without_const | 1);
}
#[test]
fn test_kcov_check_mmap_1() {
let dev = 0xdead_beef_u64;
let ino = 0x0f1e_2d3c_4b5a_6979_u64;
let page = *crate::config::PAGE_SIZE;
let wordsize = 8u8;
let words = (page / u64::from(wordsize)) as usize;
{
let mut reg = kcov_reg().write().unwrap_or_else(|e| e.into_inner());
reg.insert(
KcovId(ino),
KcovCtx {
dev,
map: 0x1000,
map_len: 0,
words,
wordsize,
mode: None,
},
);
}
assert!(kcov_check_mmap(dev, ino, 0, page).is_ok());
assert!(kcov_check_mmap(dev ^ 1, ino, 0, 1).is_ok());
assert!(kcov_check_mmap(dev, ino ^ 0xffff, 0, 1).is_ok());
assert_eq!(
kcov_check_mmap(dev, ino, 0, page + page),
Err(Errno::EINVAL)
);
assert_eq!(kcov_check_mmap(dev, ino, page, page), Err(Errno::EINVAL));
{
let mut reg = kcov_reg().write().unwrap_or_else(|e| e.into_inner());
reg.insert(
KcovId(ino),
KcovCtx {
dev,
map: 0,
map_len: 0,
words,
wordsize,
mode: None,
},
);
}
assert!(kcov_check_mmap(dev, ino, 0, page + page).is_ok());
{
let mut reg = kcov_reg().write().unwrap_or_else(|e| e.into_inner());
reg.remove(&KcovId(ino));
}
}
}