From ac771aae52302ef32124bd24c89d96cad60aa8af Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Mon, 6 Jul 2020 17:03:56 +0200 Subject: [PATCH 1/2] Add a `idt::set_default_handler!` proc macro --- Cargo.toml | 6 +- src/structures/idt.rs | 11 +++ x86_64-idt-default-handler/Cargo.toml | 14 ++++ x86_64-idt-default-handler/src/lib.rs | 102 ++++++++++++++++++++++++++ 4 files changed, 132 insertions(+), 1 deletion(-) create mode 100644 x86_64-idt-default-handler/Cargo.toml create mode 100644 x86_64-idt-default-handler/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index 533ec6df1..86adeb08a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,15 +25,18 @@ repository = "https://github.com/rust-osdev/x86_64" version = "0.12.2" edition = "2018" +[workspace] + [dependencies] bit_field = "0.9.0" bitflags = "1.0.4" +x86_64-idt-default-handler = { path = "x86_64-idt-default-handler", optional = true} [build-dependencies] cc = { version = "1.0.37", optional = true } [features] -default = [ "nightly", "instructions" ] +default = [ "nightly", "instructions", "proc_macros" ] instructions = [] deny-warnings = [] external_asm = [ "cc" ] @@ -41,6 +44,7 @@ nightly = [ "inline_asm", "const_fn", "abi_x86_interrupt" ] inline_asm = [] abi_x86_interrupt = [] const_fn = [] +proc_macros = [ "x86_64-idt-default-handler" ] [package.metadata.release] no-dev-version = true diff --git a/src/structures/idt.rs b/src/structures/idt.rs index d20264a50..4effc6b2b 100644 --- a/src/structures/idt.rs +++ b/src/structures/idt.rs @@ -16,6 +16,8 @@ use core::fmt; use core::marker::PhantomData; use core::ops::Bound::{Excluded, Included, Unbounded}; use core::ops::{Deref, Index, IndexMut, RangeBounds}; +#[cfg(feature = "proc_macros")] +pub use x86_64_idt_default_handler::set_default_handler; /// An Interrupt Descriptor Table with 256 entries. /// @@ -829,4 +831,13 @@ mod test { assert_eq!(size_of::>(), 16); assert_eq!(size_of::(), 256 * 16); } + + #[cfg(feature = "proc_macros")] + #[test] + fn default_handlers() { + fn default_handler(_stack_frame: &mut InterruptStackFrame, _index: u8) {} + + let mut idt = InterruptDescriptorTable::new(); + set_default_handler!(&mut idt, default_handler, 32..64); + } } diff --git a/x86_64-idt-default-handler/Cargo.toml b/x86_64-idt-default-handler/Cargo.toml new file mode 100644 index 000000000..d0b43f182 --- /dev/null +++ b/x86_64-idt-default-handler/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "x86_64-idt-default-handler" +version = "0.1.0" +authors = ["Philipp Oppermann "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +proc-macro = true + +[dependencies] +syn = { version = "1.0.33", features = ["full"] } +quote = "1.0.7" diff --git a/x86_64-idt-default-handler/src/lib.rs b/x86_64-idt-default-handler/src/lib.rs new file mode 100644 index 000000000..d6dbea975 --- /dev/null +++ b/x86_64-idt-default-handler/src/lib.rs @@ -0,0 +1,102 @@ +use quote::quote; +use syn::{ + parse::{Parse, ParseStream}, + parse_macro_input, Expr, ExprLit, ExprRange, ExprReference, Ident, Lit, RangeLimits, Token, +}; + +extern crate proc_macro; + +use proc_macro::TokenStream; + +struct DefaultHandler { + idt: ExprReference, + function: Ident, + range: ExprRange, +} + +impl Parse for DefaultHandler { + fn parse(input: ParseStream) -> syn::Result { + let idt = input + .parse() + .map_err(|err| syn::Error::new(err.span(), "expected a `&mut` reference to an IDT"))?; + input.parse::()?; + let function = input.parse().map_err(|err| { + syn::Error::new(err.span(), "expected the name of a handler function") + })?; + input.parse::()?; + let range = input.parse()?; + Ok(DefaultHandler { + idt, + function, + range, + }) + } +} + +#[proc_macro] +pub fn set_default_handler(item: TokenStream) -> TokenStream { + let input = parse_macro_input!(item as DefaultHandler); + + set_default_handler_impl(&input).unwrap_or_else(|err| err.to_compile_error().into()) +} + +fn set_default_handler_impl(input: &DefaultHandler) -> syn::Result { + let DefaultHandler { + idt, + function, + range, + } = input; + + if idt.mutability.is_none() { + return Err(syn::Error::new_spanned( + idt, + "Must be a `&mut` reference to an IDT", + )); + } + + let range_start: u8 = match range.from.as_deref() { + Some(&Expr::Lit(ExprLit { + lit: Lit::Int(ref int), + .. + })) => int.base10_parse()?, + Some(other) => return Err(syn::Error::new_spanned(other, "Invalid range start")), + None => 0, + }; + let mut range_end: u8 = match range.to.as_deref() { + Some(&Expr::Lit(ExprLit { + lit: Lit::Int(ref int), + .. + })) => int.base10_parse()?, + Some(other) => return Err(syn::Error::new_spanned(other, "Invalid range end")), + None => 255, + }; + if let ExprRange { + limits: RangeLimits::HalfOpen(_), + to: Some(_), + .. + } = range + { + range_end = range_end + .checked_sub(1) + .ok_or_else(|| syn::Error::new_spanned(&range.to, "Invalid range"))?; + }; + + let mut handlers = Vec::new(); + for index in range_start..=range_end { + let handler = quote! { + { + extern "x86-interrupt" fn handler(stack_frame: &mut InterruptStackFrame) { + #function(stack_frame, #index.into()); + } + (#idt)[#index.into()].set_handler_fn(handler); + } + }; + handlers.push(handler); + } + + let ret = quote! { + #(#handlers)* + }; + + Ok(ret.into()) +} From 10c4d0957786635386f72ff73dda9166ad7108ed Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Tue, 7 Jul 2020 11:18:18 +0200 Subject: [PATCH 2/2] Rename and rewrite macro to make it more flexible It is now aware of x86_64-specific exceptions and correctly handles reserved exceptions and exceptions without error code. The third argument is now optional and can be an index or a range. If it is omitted, all non-reserved entries of the IDT are set. --- Cargo.toml | 4 +- .../Cargo.toml | 3 +- idt-general-handler/src/lib.rs | 186 ++++++++++++++++++ src/structures/idt.rs | 65 +++++- x86_64-idt-default-handler/src/lib.rs | 102 ---------- 5 files changed, 251 insertions(+), 109 deletions(-) rename {x86_64-idt-default-handler => idt-general-handler}/Cargo.toml (83%) create mode 100644 idt-general-handler/src/lib.rs delete mode 100644 x86_64-idt-default-handler/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index 86adeb08a..618be3dea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,7 +30,7 @@ edition = "2018" [dependencies] bit_field = "0.9.0" bitflags = "1.0.4" -x86_64-idt-default-handler = { path = "x86_64-idt-default-handler", optional = true} +x86_64-idt-general-handler = { path = "idt-general-handler", optional = true} [build-dependencies] cc = { version = "1.0.37", optional = true } @@ -44,7 +44,7 @@ nightly = [ "inline_asm", "const_fn", "abi_x86_interrupt" ] inline_asm = [] abi_x86_interrupt = [] const_fn = [] -proc_macros = [ "x86_64-idt-default-handler" ] +proc_macros = [ "x86_64-idt-general-handler" ] [package.metadata.release] no-dev-version = true diff --git a/x86_64-idt-default-handler/Cargo.toml b/idt-general-handler/Cargo.toml similarity index 83% rename from x86_64-idt-default-handler/Cargo.toml rename to idt-general-handler/Cargo.toml index d0b43f182..96fd623ff 100644 --- a/x86_64-idt-default-handler/Cargo.toml +++ b/idt-general-handler/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "x86_64-idt-default-handler" +name = "x86_64-idt-general-handler" version = "0.1.0" authors = ["Philipp Oppermann "] edition = "2018" @@ -12,3 +12,4 @@ proc-macro = true [dependencies] syn = { version = "1.0.33", features = ["full"] } quote = "1.0.7" +proc-macro2 = "1.0.18" diff --git a/idt-general-handler/src/lib.rs b/idt-general-handler/src/lib.rs new file mode 100644 index 000000000..affe96997 --- /dev/null +++ b/idt-general-handler/src/lib.rs @@ -0,0 +1,186 @@ +use proc_macro::TokenStream; +use quote::{quote, quote_spanned}; +use std::{fmt::Display, ops::RangeInclusive}; +use syn::{ + parse::Parser, punctuated::Punctuated, spanned::Spanned, token::Comma, Expr, ExprLit, ExprPath, + ExprRange, ExprReference, Lit, RangeLimits, +}; + +extern crate proc_macro; + +const RESERVED_HANDLERS: &[u8] = &[15, 21, 22, 23, 24, 25, 26, 27, 28, 29, 31]; +const HANDLERS_WITH_ERR_CODE: &[u8] = &[8, 10, 11, 12, 13, 14, 17, 30]; + +#[proc_macro] +pub fn set_general_handler(item: TokenStream) -> TokenStream { + set_general_handler_impl(item).unwrap_or_else(|err| err.to_compile_error().into()) +} + +fn set_general_handler_impl(item: TokenStream) -> syn::Result { + let input = Punctuated::::parse_separated_nonempty.parse(item)?; + let (idt, function, range) = parse_input(&input)?; + + let mut handlers = Vec::new(); + for index in range { + if RESERVED_HANDLERS.contains(&index) { + continue; // skip reserved handlers + } + let function_call = if HANDLERS_WITH_ERR_CODE.contains(&index) { + quote_spanned!(function.span() => { + #function(stack_frame, #index.into(), Some(error_code)); + }) + } else { + quote_spanned!(function.span() => { + #function(stack_frame, #index.into(), None); + }) + }; + + let stack_frame_arg = + quote! { stack_frame: &mut x86_64::structures::idt::InterruptStackFrame }; + + let handler_with_err_code = quote! { + extern "x86-interrupt" fn handler(#stack_frame_arg, error_code: u64) { + #function_call + } + }; + + let set_handler_fn = match index { + 8 => quote! { + extern "x86-interrupt" fn handler(#stack_frame_arg, error_code: u64) -> ! { + #function_call + panic!("General handler returned on double fault"); + } + (#idt).double_fault.set_handler_fn(handler); + }, + 10 => quote! { + #handler_with_err_code + (#idt).invalid_tss.set_handler_fn(handler); + }, + 11 => quote! { + #handler_with_err_code + (#idt).segment_not_present.set_handler_fn(handler); + }, + 12 => quote! { + #handler_with_err_code + (#idt).stack_segment_fault.set_handler_fn(handler); + }, + 13 => quote! { + #handler_with_err_code + (#idt).general_protection_fault.set_handler_fn(handler); + }, + 14 => quote! { + extern "x86-interrupt" fn handler(#stack_frame_arg, error_code: x86_64::structures::idt::PageFaultErrorCode) { + let error_code = error_code.bits(); + #function_call + } + (#idt).page_fault.set_handler_fn(handler); + }, + 17 => quote! { + #handler_with_err_code + (#idt).alignment_check.set_handler_fn(handler); + }, + 18 => quote! { + extern "x86-interrupt" fn handler(#stack_frame_arg) -> ! { + #function_call + panic!("General handler returned on machine check exception"); + } + (#idt).machine_check.set_handler_fn(handler); + }, + 30 => quote! { + #handler_with_err_code + (#idt).security_exception.set_handler_fn(handler); + }, + index => quote! { + extern "x86-interrupt" fn handler(#stack_frame_arg) { + #function_call + } + (#idt)[#index.into()].set_handler_fn(handler); + }, + }; + + // double `{{` to create new scope + handlers.push(quote! {{ + #set_handler_fn + }}); + } + + let ret = quote! { + #(#handlers)* + }; + + // uncomment to print generated code: + // println!("{}", ret); + + Ok(ret.into()) +} + +fn parse_input( + input: &Punctuated, +) -> syn::Result<(&ExprReference, &ExprPath, RangeInclusive)> { + if input.len() < 2 || input.len() > 3 { + return Err(err(input, "expected 2 or 3 arguments")); + } + + let idt = match &input[0] { + Expr::Reference(r) if r.mutability.is_some() => r, + other => return Err(err(other, "expected a `&mut` reference to an IDT")), + }; + + let function = match &input[1] { + Expr::Path(path) => path, + other => return Err(err(other, "expected the name of a handler function")), + }; + + let range = if input.len() == 3 { + match &input[2] { + Expr::Lit(lit) => match &lit.lit { + Lit::Int(int) => { + let index: u8 = int.base10_parse()?; + index..=index + } + other => return Err(err(other, "expected index or range")), + }, + Expr::Range(range) => parse_range(&range)?, + other => return Err(err(other, "expected index or range")), + } + } else { + 0..=255 + }; + + Ok((idt, function, range)) +} + +fn parse_range(range: &ExprRange) -> syn::Result> { + let range_start: u8 = match range.from.as_deref() { + Some(&Expr::Lit(ExprLit { + lit: Lit::Int(ref int), + .. + })) => int.base10_parse()?, + Some(other) => return Err(err(other, "Invalid range start")), + None => 0, + }; + let mut range_end: u8 = match range.to.as_deref() { + Some(&Expr::Lit(ExprLit { + lit: Lit::Int(ref int), + .. + })) => int.base10_parse()?, + Some(other) => return Err(err(other, "Invalid range end")), + None => 255, + }; + if let ExprRange { + limits: RangeLimits::HalfOpen(_), + to: Some(_), + .. + } = range + { + range_end = range_end + .checked_sub(1) + .ok_or_else(|| err(&range.to, "Invalid range"))?; + }; + + Ok(range_start..=range_end) +} + +fn err(tokens: impl quote::ToTokens, message: impl Display) -> syn::Error { + syn::Error::new_spanned(tokens, message) +} diff --git a/src/structures/idt.rs b/src/structures/idt.rs index 4effc6b2b..273559f85 100644 --- a/src/structures/idt.rs +++ b/src/structures/idt.rs @@ -17,7 +17,7 @@ use core::marker::PhantomData; use core::ops::Bound::{Excluded, Included, Unbounded}; use core::ops::{Deref, Index, IndexMut, RangeBounds}; #[cfg(feature = "proc_macros")] -pub use x86_64_idt_default_handler::set_default_handler; +pub use x86_64_idt_general_handler::set_general_handler; /// An Interrupt Descriptor Table with 256 entries. /// @@ -824,6 +824,26 @@ bitflags! { #[cfg(test)] mod test { use super::*; + use crate as x86_64; + + fn entry_present(idt: &InterruptDescriptorTable, index: usize) -> bool { + let options = match index { + 8 => &idt.double_fault.options, + 10 => &idt.invalid_tss.options, + 11 => &idt.segment_not_present.options, + 12 => &idt.stack_segment_fault.options, + 13 => &idt.general_protection_fault.options, + 14 => &idt.page_fault.options, + 15 => &idt.reserved_1.options, + 17 => &idt.alignment_check.options, + 18 => &idt.machine_check.options, + i @ 21..=29 => &idt.reserved_2[i - 21].options, + 30 => &idt.security_exception.options, + 31 => &idt.reserved_3.options, + other => &idt[other].options, + }; + options.0.get_bit(15) + } #[test] fn size_test() { @@ -834,10 +854,47 @@ mod test { #[cfg(feature = "proc_macros")] #[test] - fn default_handlers() { - fn default_handler(_stack_frame: &mut InterruptStackFrame, _index: u8) {} + fn general_handlers() { + fn general_handler( + _stack_frame: &mut InterruptStackFrame, + _index: u8, + _error_code: Option, + ) { + } let mut idt = InterruptDescriptorTable::new(); - set_default_handler!(&mut idt, default_handler, 32..64); + set_general_handler!(&mut idt, general_handler, 0); + for i in 0..256 { + if i == 0 { + assert!(entry_present(&idt, i)); + } else { + assert!(!entry_present(&idt, i)); + } + } + set_general_handler!(&mut idt, general_handler, 14); + for i in 0..256 { + if i == 0 || i == 14 { + assert!(entry_present(&idt, i)); + } else { + assert!(!entry_present(&idt, i)); + } + } + set_general_handler!(&mut idt, general_handler, 32..64); + for i in 1..256 { + if i == 0 || i == 14 || (i >= 32 && i < 64) { + assert!(entry_present(&idt, i)); + } else { + assert!(!entry_present(&idt, i)); + } + } + set_general_handler!(&mut idt, general_handler); + for i in 0..256 { + if i == 15 || i == 31 || (i >= 21 && i <= 29) { + // reserved entries should not be set + assert!(!entry_present(&idt, i)); + } else { + assert!(entry_present(&idt, i)); + } + } } } diff --git a/x86_64-idt-default-handler/src/lib.rs b/x86_64-idt-default-handler/src/lib.rs deleted file mode 100644 index d6dbea975..000000000 --- a/x86_64-idt-default-handler/src/lib.rs +++ /dev/null @@ -1,102 +0,0 @@ -use quote::quote; -use syn::{ - parse::{Parse, ParseStream}, - parse_macro_input, Expr, ExprLit, ExprRange, ExprReference, Ident, Lit, RangeLimits, Token, -}; - -extern crate proc_macro; - -use proc_macro::TokenStream; - -struct DefaultHandler { - idt: ExprReference, - function: Ident, - range: ExprRange, -} - -impl Parse for DefaultHandler { - fn parse(input: ParseStream) -> syn::Result { - let idt = input - .parse() - .map_err(|err| syn::Error::new(err.span(), "expected a `&mut` reference to an IDT"))?; - input.parse::()?; - let function = input.parse().map_err(|err| { - syn::Error::new(err.span(), "expected the name of a handler function") - })?; - input.parse::()?; - let range = input.parse()?; - Ok(DefaultHandler { - idt, - function, - range, - }) - } -} - -#[proc_macro] -pub fn set_default_handler(item: TokenStream) -> TokenStream { - let input = parse_macro_input!(item as DefaultHandler); - - set_default_handler_impl(&input).unwrap_or_else(|err| err.to_compile_error().into()) -} - -fn set_default_handler_impl(input: &DefaultHandler) -> syn::Result { - let DefaultHandler { - idt, - function, - range, - } = input; - - if idt.mutability.is_none() { - return Err(syn::Error::new_spanned( - idt, - "Must be a `&mut` reference to an IDT", - )); - } - - let range_start: u8 = match range.from.as_deref() { - Some(&Expr::Lit(ExprLit { - lit: Lit::Int(ref int), - .. - })) => int.base10_parse()?, - Some(other) => return Err(syn::Error::new_spanned(other, "Invalid range start")), - None => 0, - }; - let mut range_end: u8 = match range.to.as_deref() { - Some(&Expr::Lit(ExprLit { - lit: Lit::Int(ref int), - .. - })) => int.base10_parse()?, - Some(other) => return Err(syn::Error::new_spanned(other, "Invalid range end")), - None => 255, - }; - if let ExprRange { - limits: RangeLimits::HalfOpen(_), - to: Some(_), - .. - } = range - { - range_end = range_end - .checked_sub(1) - .ok_or_else(|| syn::Error::new_spanned(&range.to, "Invalid range"))?; - }; - - let mut handlers = Vec::new(); - for index in range_start..=range_end { - let handler = quote! { - { - extern "x86-interrupt" fn handler(stack_frame: &mut InterruptStackFrame) { - #function(stack_frame, #index.into()); - } - (#idt)[#index.into()].set_handler_fn(handler); - } - }; - handlers.push(handler); - } - - let ret = quote! { - #(#handlers)* - }; - - Ok(ret.into()) -}