Merge pull request #656 from aya-rs/kernel-version-fml

Handle WSL kernel version strings
reviewable/pr660/r1
Tamir Duberstein 2 years ago committed by GitHub
commit 232cd45e41
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -24,7 +24,6 @@ resolver = "2"
default-members = [ default-members = [
"aya", "aya",
"aya-bpf-macros",
"aya-log", "aya-log",
"aya-log-common", "aya-log-common",
"aya-log-parser", "aya-log-parser",

@ -2,7 +2,7 @@
name = "aya-obj" name = "aya-obj"
version = "0.1.0" version = "0.1.0"
description = "An eBPF object file parsing library with BTF and relocation support." description = "An eBPF object file parsing library with BTF and relocation support."
keywords = ["ebpf", "bpf", "btf", "elf", "object"] keywords = ["bpf", "btf", "ebpf", "elf", "object"]
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
authors = ["The Aya Contributors"] authors = ["The Aya Contributors"]
repository = "https://github.com/aya-rs/aya" repository = "https://github.com/aya-rs/aya"
@ -13,13 +13,16 @@ edition = "2021"
[dependencies] [dependencies]
bytes = "1" bytes = "1"
log = "0.4" log = "0.4"
object = { version = "0.31", default-features = false, features = ["read_core", "elf"] } object = { version = "0.31", default-features = false, features = [
"elf",
"read_core",
] }
hashbrown = { version = "0.14" } hashbrown = { version = "0.14" }
thiserror = { version = "1", default-features = false } thiserror = { version = "1", default-features = false }
core-error = { version = "0.0.0" } core-error = { version = "0.0.0" }
[dev-dependencies] [dev-dependencies]
matches = "0.1.8" assert_matches = "1.5.0"
rbpf = "0.2.0" rbpf = "0.2.0"
[features] [features]

@ -1486,7 +1486,7 @@ pub fn copy_instructions(data: &[u8]) -> Result<Vec<bpf_insn>, ParseError> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use alloc::vec; use alloc::vec;
use matches::assert_matches; use assert_matches::assert_matches;
use object::Endianness; use object::Endianness;
use super::*; use super::*;

@ -24,13 +24,12 @@ object = { version = "0.31", default-features = false, features = [
"std", "std",
] } ] }
parking_lot = { version = "0.12.0", features = ["send_guard"] } parking_lot = { version = "0.12.0", features = ["send_guard"] }
text_io = "0.1.12"
thiserror = "1" thiserror = "1"
tokio = { version = "1.24.0", features = ["rt"], optional = true } tokio = { version = "1.24.0", features = ["rt"], optional = true }
[dev-dependencies] [dev-dependencies]
assert_matches = "1.5.0"
futures = { version = "0.3.12", default-features = false, features = ["std"] } futures = { version = "0.3.12", default-features = false, features = ["std"] }
matches = "0.1.8"
tempfile = "3" tempfile = "3"
[features] [features]

@ -88,8 +88,8 @@ mod tests {
obj::{self, maps::LegacyMap, BpfSectionKind}, obj::{self, maps::LegacyMap, BpfSectionKind},
sys::{override_syscall, SysResult, Syscall}, sys::{override_syscall, SysResult, Syscall},
}; };
use assert_matches::assert_matches;
use libc::{EFAULT, ENOENT}; use libc::{EFAULT, ENOENT};
use matches::assert_matches;
use std::io; use std::io;
fn new_obj_map() -> obj::Map { fn new_obj_map() -> obj::Map {

@ -107,8 +107,8 @@ impl<T: Borrow<MapData>, K: Pod, V: Pod> IterableMap<K, V> for HashMap<T, K, V>
mod tests { mod tests {
use std::io; use std::io;
use assert_matches::assert_matches;
use libc::{EFAULT, ENOENT}; use libc::{EFAULT, ENOENT};
use matches::assert_matches;
use crate::{ use crate::{
bpf_map_def, bpf_map_def,

@ -207,8 +207,8 @@ mod tests {
obj::{self, maps::LegacyMap, BpfSectionKind}, obj::{self, maps::LegacyMap, BpfSectionKind},
sys::{override_syscall, SysResult, Syscall}, sys::{override_syscall, SysResult, Syscall},
}; };
use assert_matches::assert_matches;
use libc::{EFAULT, ENOENT}; use libc::{EFAULT, ENOENT};
use matches::assert_matches;
use std::{io, mem, net::Ipv4Addr}; use std::{io, mem, net::Ipv4Addr};
fn new_obj_map() -> obj::Map { fn new_obj_map() -> obj::Map {

@ -842,8 +842,8 @@ impl<T: Pod> Deref for PerCpuValues<T> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use assert_matches::assert_matches;
use libc::EFAULT; use libc::EFAULT;
use matches::assert_matches;
use crate::{ use crate::{
bpf_map_def, bpf_map_def,

@ -319,7 +319,7 @@ mod tests {
generated::perf_event_mmap_page, generated::perf_event_mmap_page,
sys::{override_syscall, Syscall, TEST_MMAP_RET}, sys::{override_syscall, Syscall, TEST_MMAP_RET},
}; };
use matches::assert_matches; use assert_matches::assert_matches;
use std::{fmt::Debug, mem}; use std::{fmt::Debug, mem};
const PAGE_SIZE: usize = 4096; const PAGE_SIZE: usize = 4096;

@ -353,7 +353,7 @@ pub enum LinkError {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use matches::assert_matches; use assert_matches::assert_matches;
use std::{cell::RefCell, fs::File, mem, os::unix::io::AsRawFd, rc::Rc}; use std::{cell::RefCell, fs::File, mem, os::unix::io::AsRawFd, rc::Rc};
use tempfile::tempdir; use tempfile::tempdir;

@ -25,18 +25,14 @@ pub struct KernelVersion {
pub(crate) patch: u16, pub(crate) patch: u16,
} }
/// An error encountered while fetching the current kernel version.
#[derive(thiserror::Error, Debug)] #[derive(thiserror::Error, Debug)]
pub enum CurrentKernelVersionError { enum CurrentKernelVersionError {
/// The kernel version string could not be read.
#[error("failed to read kernel version")] #[error("failed to read kernel version")]
IOError(#[from] io::Error), IO(#[from] io::Error),
/// The kernel version string could not be parsed.
#[error("failed to parse kernel version")] #[error("failed to parse kernel version")]
ParseError(#[from] text_io::Error), ParseError(String),
/// The kernel version string was not valid UTF-8.
#[error("kernel version string is not valid UTF-8")] #[error("kernel version string is not valid UTF-8")]
Utf8Error(#[from] Utf8Error), Utf8(#[from] Utf8Error),
} }
impl KernelVersion { impl KernelVersion {
@ -74,7 +70,7 @@ impl KernelVersion {
fn get_ubuntu_kernel_version() -> Result<Option<Self>, CurrentKernelVersionError> { fn get_ubuntu_kernel_version() -> Result<Option<Self>, CurrentKernelVersionError> {
const UBUNTU_KVER_FILE: &str = "/proc/version_signature"; const UBUNTU_KVER_FILE: &str = "/proc/version_signature";
let s = match fs::read(UBUNTU_KVER_FILE) { let s = match fs::read_to_string(UBUNTU_KVER_FILE) {
Ok(s) => s, Ok(s) => s,
Err(e) => { Err(e) => {
if e.kind() == io::ErrorKind::NotFound { if e.kind() == io::ErrorKind::NotFound {
@ -83,13 +79,16 @@ impl KernelVersion {
return Err(e.into()); return Err(e.into());
} }
}; };
let ubuntu: String; let mut parts = s.split_terminator(char::is_whitespace);
let ubuntu_version: String; let mut next = || {
let major: u8; parts
let minor: u8; .next()
let patch: u16; .ok_or_else(|| CurrentKernelVersionError::ParseError(s.to_string()))
text_io::try_scan!(s.iter().copied() => "{} {} {}.{}.{}\n", ubuntu, ubuntu_version, major, minor, patch); };
Ok(Some(Self::new(major, minor, patch))) let _ubuntu: &str = next()?;
let _ubuntu_version: &str = next()?;
let kernel_version_string = next()?;
Self::parse_kernel_version_string(kernel_version_string).map(Some)
} }
fn get_debian_kernel_version( fn get_debian_kernel_version(
@ -99,17 +98,13 @@ impl KernelVersion {
// //
// The length of the arrays in a struct utsname is unspecified (see NOTES); the fields are // The length of the arrays in a struct utsname is unspecified (see NOTES); the fields are
// terminated by a null byte ('\0'). // terminated by a null byte ('\0').
let p = unsafe { CStr::from_ptr(info.version.as_ptr()) }; let s = unsafe { CStr::from_ptr(info.version.as_ptr()) };
let p = p.to_str()?; let s = s.to_str()?;
let p = match p.split_once("Debian ") { let kernel_version_string = match s.split_once("Debian ") {
Some((_prefix, suffix)) => suffix, Some((_prefix, suffix)) => suffix,
None => return Ok(None), None => return Ok(None),
}; };
let major: u8; Self::parse_kernel_version_string(kernel_version_string).map(Some)
let minor: u8;
let patch: u16;
text_io::try_scan!(p.bytes() => "{}.{}.{}", major, minor, patch);
Ok(Some(Self::new(major, minor, patch)))
} }
fn get_kernel_version() -> Result<Self, CurrentKernelVersionError> { fn get_kernel_version() -> Result<Self, CurrentKernelVersionError> {
@ -130,17 +125,23 @@ impl KernelVersion {
// //
// The length of the arrays in a struct utsname is unspecified (see NOTES); the fields are // The length of the arrays in a struct utsname is unspecified (see NOTES); the fields are
// terminated by a null byte ('\0'). // terminated by a null byte ('\0').
let p = unsafe { CStr::from_ptr(info.release.as_ptr()) }; let s = unsafe { CStr::from_ptr(info.release.as_ptr()) };
let p = p.to_str()?; let s = s.to_str()?;
// Unlike sscanf, text_io::try_scan! does not stop at the first non-matching character. Self::parse_kernel_version_string(s)
let p = match p.split_once(|c: char| c != '.' && !c.is_ascii_digit()) { }
Some((prefix, _suffix)) => prefix,
None => p, fn parse_kernel_version_string(s: &str) -> Result<Self, CurrentKernelVersionError> {
}; fn parse<T: FromStr<Err = std::num::ParseIntError>>(s: Option<&str>) -> Option<T> {
let major: u8; match s.map(str::parse).transpose() {
let minor: u8; Ok(option) => option,
let patch: u16; Err(std::num::ParseIntError { .. }) => None,
text_io::try_scan!(p.bytes() => "{}.{}.{}", major, minor, patch); }
}
let error = || CurrentKernelVersionError::ParseError(s.to_string());
let mut parts = s.split(|c: char| c == '.' || !c.is_ascii_digit());
let major = parse(parts.next()).ok_or_else(error)?;
let minor = parse(parts.next()).ok_or_else(error)?;
let patch = parse(parts.next()).ok_or_else(error)?;
Ok(Self::new(major, minor, patch)) Ok(Self::new(major, minor, patch))
} }
} }
@ -331,6 +332,19 @@ pub(crate) fn bytes_of_slice<T: Pod>(val: &[T]) -> &[u8] {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use assert_matches::assert_matches;
#[test]
fn test_parse_kernel_version_string() {
// WSL.
assert_matches!(KernelVersion::parse_kernel_version_string("5.15.90.1-microsoft-standard-WSL2"), Ok(kernel_version) => {
assert_eq!(kernel_version, KernelVersion::new(5, 15, 90))
});
// uname -r on Fedora.
assert_matches!(KernelVersion::parse_kernel_version_string("6.3.11-200.fc38.x86_64"), Ok(kernel_version) => {
assert_eq!(kernel_version, KernelVersion::new(6, 3, 11))
});
}
#[test] #[test]
fn test_parse_online_cpus() { fn test_parse_online_cpus() {

@ -6,12 +6,12 @@ publish = false
[dependencies] [dependencies]
anyhow = "1" anyhow = "1"
assert_matches = "1.5.0"
aya = { path = "../../aya" } aya = { path = "../../aya" }
aya-log = { path = "../../aya-log" } aya-log = { path = "../../aya-log" }
aya-obj = { path = "../../aya-obj" } aya-obj = { path = "../../aya-obj" }
libc = { version = "0.2.105" } libc = { version = "0.2.105" }
log = "0.4" log = "0.4"
matches = "0.1.8"
object = { version = "0.31", default-features = false, features = [ object = { version = "0.31", default-features = false, features = [
"elf", "elf",
"read_core", "read_core",

@ -8,7 +8,7 @@ fn run_with_rbpf() {
let object = Object::parse(crate::PASS).unwrap(); let object = Object::parse(crate::PASS).unwrap();
assert_eq!(object.programs.len(), 1); assert_eq!(object.programs.len(), 1);
matches::assert_matches!(object.programs["pass"].section, ProgramSection::Xdp { .. }); assert_matches::assert_matches!(object.programs["pass"].section, ProgramSection::Xdp { .. });
assert_eq!(object.programs["pass"].section.name(), "pass"); assert_eq!(object.programs["pass"].section.name(), "pass");
let instructions = &object let instructions = &object
@ -35,7 +35,7 @@ fn use_map_with_rbpf() {
let mut object = Object::parse(crate::MULTIMAP_BTF).unwrap(); let mut object = Object::parse(crate::MULTIMAP_BTF).unwrap();
assert_eq!(object.programs.len(), 1); assert_eq!(object.programs.len(), 1);
matches::assert_matches!( assert_matches::assert_matches!(
object.programs["tracepoint"].section, object.programs["tracepoint"].section,
ProgramSection::TracePoint { .. } ProgramSection::TracePoint { .. }
); );

Loading…
Cancel
Save