isr_macros/
offsets.rs

1use crate::Error;
2
3/// A field within a structure.
4///
5/// `Field` encapsulates the offset and size of a field, enabling type-safe
6/// access to structure members. It's primarily used with the [`offsets!`] macro
7/// for defining structure layouts and accessing their fields.
8///
9/// [`offsets!`]: crate::offsets
10#[derive(Debug, Clone, Copy)]
11pub struct Field {
12    /// The offset of the field from the beginning of the structure, in bytes.
13    pub(crate) offset: u64,
14
15    /// The size of the field, in bytes.
16    pub(crate) size: u64,
17}
18
19impl Field {
20    /// Creates a new field descriptor.
21    pub fn new(offset: u64, size: u64) -> Self {
22        Self { offset, size }
23    }
24
25    /// Returns the offset of the field from the beginning of the structure,
26    /// in bytes.
27    pub fn offset(&self) -> u64 {
28        self.offset
29    }
30
31    /// Returns the size of the field, in bytes.
32    pub fn size(&self) -> u64 {
33        self.size
34    }
35}
36
37/// A bitfield within a structure.
38///
39/// `Bitfield` provides information about the offset, size, bit position, and
40/// bit length of a bitfield member. It extends the functionality of [`Field`]
41/// by allowing access to individual bits within a field.
42#[derive(Debug, Clone, Copy)]
43pub struct Bitfield {
44    pub(crate) field: Field,
45
46    /// The starting bit position of the bitfield within the underlying field.
47    pub(crate) bit_position: u64,
48
49    /// The length of the bitfield, in bits.
50    pub(crate) bit_length: u64,
51}
52
53impl std::ops::Deref for Bitfield {
54    type Target = Field;
55
56    fn deref(&self) -> &Self::Target {
57        &self.field
58    }
59}
60
61impl Bitfield {
62    /// Creates a new bitfield descriptor.
63    pub fn new(offset: u64, size: u64, bit_position: u64, bit_length: u64) -> Self {
64        Self {
65            field: Field::new(offset, size),
66            bit_position,
67            bit_length,
68        }
69    }
70
71    /// Returns the starting bit position of the bitfield within the underlying field.
72    pub fn bit_position(&self) -> u64 {
73        self.bit_position
74    }
75
76    /// Returns the length of the bitfield, in bits.
77    pub fn bit_length(&self) -> u64 {
78        self.bit_length
79    }
80
81    /// This method performs bitwise operations to isolate and return the
82    /// value represented by the bitfield within the provided integer.
83    pub fn extract(&self, value: u64) -> u64 {
84        let result = value >> self.bit_position;
85        let result = result & ((1 << self.bit_length) - 1);
86
87        #[expect(clippy::let_and_return)]
88        result
89    }
90}
91
92/// A field descriptor.
93///
94/// This descriptor can be either a [`Field`] or a [`Bitfield`].
95#[derive(Debug, Clone)]
96pub enum FieldDescriptor {
97    /// Represents a regular field.
98    Field(Field),
99
100    /// Represents a bitfield.
101    Bitfield(Bitfield),
102}
103
104impl FieldDescriptor {
105    /// Returns the offset of the field or bitfield, in bytes.
106    pub fn offset(&self) -> u64 {
107        match self {
108            FieldDescriptor::Field(field) => field.offset,
109            FieldDescriptor::Bitfield(bitfield) => bitfield.offset,
110        }
111    }
112
113    /// Returns the size of the field or bitfield, in bytes.
114    pub fn size(&self) -> u64 {
115        match self {
116            FieldDescriptor::Field(field) => field.size,
117            FieldDescriptor::Bitfield(bitfield) => bitfield.size,
118        }
119    }
120}
121
122impl TryFrom<FieldDescriptor> for u64 {
123    type Error = Error;
124
125    fn try_from(value: FieldDescriptor) -> Result<Self, Self::Error> {
126        match value {
127            FieldDescriptor::Field(field) => Ok(field.offset),
128            FieldDescriptor::Bitfield(bitfield) => Ok(bitfield.offset),
129        }
130    }
131}
132
133impl TryFrom<FieldDescriptor> for Field {
134    type Error = Error;
135
136    fn try_from(value: FieldDescriptor) -> Result<Self, Self::Error> {
137        match value {
138            FieldDescriptor::Field(field) => Ok(field),
139            FieldDescriptor::Bitfield(_) => {
140                Err(Error::Conversion("expected field, found bitfield"))
141            }
142        }
143    }
144}
145
146impl TryFrom<FieldDescriptor> for Bitfield {
147    type Error = Error;
148
149    fn try_from(value: FieldDescriptor) -> Result<Self, Self::Error> {
150        match value {
151            FieldDescriptor::Field(_) => Err(Error::Conversion("expected bitfield, found field")),
152            FieldDescriptor::Bitfield(bitfield) => Ok(bitfield),
153        }
154    }
155}
156
157//
158//
159//
160
161pub trait IntoField<T> {
162    type Error;
163
164    fn into_field(self) -> Result<T, Error>;
165}
166
167impl IntoField<u64> for Result<FieldDescriptor, Error> {
168    type Error = Error;
169
170    fn into_field(self) -> Result<u64, Error> {
171        self?.try_into()
172    }
173}
174
175impl IntoField<Field> for Result<FieldDescriptor, Error> {
176    type Error = Error;
177
178    fn into_field(self) -> Result<Field, Error> {
179        self?.try_into()
180    }
181}
182
183impl IntoField<Bitfield> for Result<FieldDescriptor, Error> {
184    type Error = Error;
185
186    fn into_field(self) -> Result<Bitfield, Error> {
187        self?.try_into()
188    }
189}
190
191impl IntoField<Option<u64>> for Result<FieldDescriptor, Error> {
192    type Error = Error;
193
194    fn into_field(self) -> Result<Option<u64>, Error> {
195        match self {
196            Ok(descriptor) => Ok(Some(descriptor.try_into()?)),
197            Err(_) => Ok(None),
198        }
199    }
200}
201
202impl IntoField<Option<Field>> for Result<FieldDescriptor, Error> {
203    type Error = Error;
204
205    fn into_field(self) -> Result<Option<Field>, Error> {
206        match self {
207            Ok(descriptor) => Ok(Some(descriptor.try_into()?)),
208            Err(_) => Ok(None),
209        }
210    }
211}
212
213impl IntoField<Option<Bitfield>> for Result<FieldDescriptor, Error> {
214    type Error = Error;
215
216    fn into_field(self) -> Result<Option<Bitfield>, Error> {
217        match self {
218            Ok(descriptor) => Ok(Some(descriptor.try_into()?)),
219            Err(_) => Ok(None),
220        }
221    }
222}
223
224/// Defines offsets for members within a structure.
225///
226/// This macro facilitates type-safe access to structure members in the ISR
227/// framework, automatically calculating field offsets and sizes based on
228/// provided profile data.
229///
230/// # Usage
231///
232/// ```rust
233/// # use isr::{
234/// #     cache::{Codec as _, JsonCodec},
235/// #     macros::{offsets, Bitfield, Field},
236/// # };
237/// #
238/// offsets! {
239///     // Defined attributes are applied to each substucture.
240///     #[derive(Debug)]
241///     pub struct Offsets {
242///         struct _EX_FAST_REF {
243///             RefCnt: Bitfield,
244///             Value: Field,
245///         }
246///
247///         struct _EPROCESS {
248///             UniqueProcessId: Field,
249///
250///             // Define an alternative name for a field.
251///             #[isr(alias = "Wow64Process")]
252///             WoW64Process: Field,
253///
254///             // We can even define field names that are present
255///             // in the nested structures.
256///             Affinity: Field,  // Defined in _KPROCESS
257///         }
258///
259///         // Define an alternative name for a structure.
260///         #[isr(alias = "_KLDR_DATA_TABLE_ENTRY")]
261///         struct _LDR_DATA_TABLE_ENTRY {
262///             InLoadOrderLinks: Field,
263///             DllBase: Field,
264///             FullDllName: Field,
265///         }
266///     }
267/// }
268///
269/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
270/// // Use the profile of a Windows 10.0.18362.356 kernel.
271/// # let profile = JsonCodec::decode(include_bytes!(
272/// #   concat!(
273/// #     "../../../",
274/// #     "tests/data/cache/",
275/// #     "windows/ntkrnlmp.pdb/ce7ffb00c20b87500211456b3e905c471/profile.json"
276/// #   )
277/// # ))?;
278/// let offsets = Offsets::new(&profile)?;
279///
280/// let refcnt = offsets._EX_FAST_REF.RefCnt.value_from(0x1234567890abcdef);
281/// assert_eq!(offsets._EX_FAST_REF.RefCnt.bit_position, 0);
282/// assert_eq!(offsets._EX_FAST_REF.RefCnt.bit_length, 4);
283/// assert_eq!(refcnt, 0xf);
284///
285/// assert!(!offsets._EPROCESS.is_empty());
286/// assert_eq!(offsets._EPROCESS.len(), 2176);
287///
288/// // The field with the largest offset + size in the `Offset` struct
289/// // is `WoW64Process` (offset 1064, size 8), so the effective length
290/// // of the structure is 1072 bytes.
291/// assert_eq!(offsets._EPROCESS.effective_len(), 1072);
292///
293/// assert_eq!(offsets._EPROCESS.UniqueProcessId.offset, 744);
294/// assert_eq!(offsets._EPROCESS.UniqueProcessId.size, 8);
295///
296/// assert_eq!(offsets._EPROCESS.WoW64Process.offset, 1064);
297/// assert_eq!(offsets._EPROCESS.WoW64Process.size, 8);
298///
299/// assert_eq!(offsets._EPROCESS.Affinity.offset, 80);
300/// assert_eq!(offsets._EPROCESS.Affinity.size, 168);
301/// # Ok(())
302/// # }
303/// ```
304///
305/// # Attributes
306///
307/// - `#[isr(alias = <alias>)]`: Specifies an alternative name for a field or
308///   structure. This is useful if a field might have a different name across
309///   OS builds or kernel versions.
310///
311///   `<alias>` can be a single literal or an array of literals, e.g.:
312///   - `#[isr(alias = "alternative_name")]`
313///   - `#[isr(alias = ["name1", "name2", ...])]`
314///
315/// The generated struct provides a `new` method that takes a reference to
316/// a [`Profile`] and returns a [`Result`] containing the populated struct or
317/// an error if any fields or structures are not found.
318///
319/// Each inner struct also implements the following convenience methods:
320/// - `is_empty()`: Returns `true` if the structure has zero size.
321/// - `len()`: Returns the size of the structure in bytes.
322/// - `effective_len()`: Returns the offset of the last defined field plus its size.
323///
324/// [`Profile`]: isr_core::Profile
325#[macro_export]
326macro_rules! offsets {
327    (
328        $(#[$meta:meta])*
329        $vis:vis struct $name:ident {
330            $($rest:tt)*
331        }
332    ) => {
333        $crate::offsets!(@outer
334            $vis,
335            [ $(#[$meta])* ],
336            struct $name {
337                $($rest)*
338            }
339        );
340
341        $crate::offsets!(@inner
342            $vis,
343            [ $(#[$meta])* ],
344            $($rest)*
345        );
346    };
347
348    (@outer
349        $vis:vis,
350        [$($meta:tt)*],
351        struct $name:ident {
352            $(
353                $(#[isr($($iattr:tt)*)])?
354                struct $iname:ident {
355                    $(
356                        $(#[isr($($fattr:tt)*)])?
357                        $fname:ident: $ftype:ty
358                    ),* $(,)?
359                }
360            )+
361        }
362    ) => {
363        #[allow(non_camel_case_types, non_snake_case, missing_docs)]
364        $($meta)*
365        $vis struct $name {
366            $(
367                $vis $iname: $iname,
368            )*
369        }
370
371        impl $name {
372            /// Creates a new offsets instance.
373            $vis fn new(profile: &$crate::__private::Profile) -> Result<Self, $crate::Error> {
374                Ok(Self {
375                    $(
376                        $iname: $iname::new(profile)?,
377                    )+
378                })
379            }
380        }
381    };
382
383    (@inner
384        $vis:vis,
385        [$($meta:tt)*],
386        $(#[isr($($iattr:tt)*)])?
387        struct $iname:ident {
388            $(
389                $(#[isr($($fattr:tt)*)])?
390                $fname:ident: $ftype:ty
391            ),* $(,)?
392        }
393
394        $($rest:tt)*
395    ) => {
396        #[allow(non_camel_case_types, non_snake_case, missing_docs)]
397        $($meta)*
398        $vis struct $iname {
399            $(
400                pub $fname: $ftype,
401            )*
402            __len: usize,
403            __effective_len: usize,
404        }
405
406        impl $iname {
407            #[doc = concat!("Creates a new `", stringify!($iname), "` instance.")]
408            $vis fn new(profile: &$crate::__private::Profile) -> Result<Self, $crate::Error> {
409                use $crate::__private::IntoField as _;
410
411                let name = $crate::offsets!(@find
412                    profile,
413                    $iname,
414                    [$($($iattr)*)?]
415                ).ok_or($crate::Error::type_not_found(stringify!($iname)))?;
416
417                let len = profile
418                    .struct_size(name)
419                    .ok_or($crate::Error::type_not_found(name))?;
420                let mut effective_len: u64 = 0;
421
422                $(
423                    effective_len = u64::max(
424                        effective_len,
425                        match $crate::offsets!(@assign
426                            profile,
427                            name,
428                            $fname,
429                            [$($($fattr)*)?]
430                        ) {
431                            Ok(descriptor) => descriptor.size() + descriptor.offset(),
432                            Err(_) => 0,
433                        }
434                    );
435                )*
436
437                Ok(Self {
438                    $(
439                        $fname: $crate::offsets!(@assign
440                            profile,
441                            name,
442                            $fname,
443                            [$($($fattr)*)?]
444                        ).into_field()?,
445                    )*
446                    __len: len as usize,
447                    __effective_len: effective_len as usize,
448                })
449            }
450
451            /// Returns `true` if the structure does not contain any fields.
452            $vis fn is_empty(&self) -> bool {
453                self.__len == 0
454            }
455
456            /// Returns the size of the structure in bytes.
457            $vis fn len(&self) -> usize {
458                self.__len
459            }
460
461            /// Returns the effective size of the structure in bytes.
462            ///
463            /// The effective size is the offset of the last defined field plus its size.
464            $vis fn effective_len(&self) -> usize {
465                self.__effective_len
466            }
467        }
468
469        $crate::offsets!(@inner
470            $vis,
471            [$($meta)*],
472            $($rest)*
473        );
474    };
475
476    (@inner
477        $vis:vis,
478        [$($meta:tt)*],
479    ) => {};
480
481    //
482    // @find
483    //
484
485    (@find
486        $profile:ident,
487        $iname:ident,
488        []
489    ) => {{
490        $profile
491            .find_struct(stringify!($iname))
492            .map(|_| stringify!($iname))
493    }};
494
495    (@find
496        $profile:ident,
497        $iname:ident,
498        [alias = $alias:literal]
499    ) => {{
500        $profile
501            .find_struct(stringify!($iname))
502            .map(|_| stringify!($iname))
503            .or_else(|| $profile
504                .find_struct($alias)
505                .map(|_| $alias)
506            )
507    }};
508
509    (@find
510        $profile:ident,
511        $iname:ident,
512        [alias = [$($alias:literal),+ $(,)?]]
513    ) => {{
514        $profile
515            .find_struct(stringify!($iname))
516            .map(|_| stringify!($iname))
517            $(
518                .or_else(|| $profile
519                    .find_struct($alias)
520                    .map(|_| $alias)
521                )
522            )+
523    }};
524
525    //
526    // @assign
527    //
528
529    (@assign
530        $profile:ident,
531        $iname:ident,
532        $fname:ident,
533        []
534    ) => {{
535        use $crate::__private::ProfileExt as _;
536
537        $profile
538            .find_field_descriptor($iname, stringify!($fname))
539    }};
540
541    (@assign
542        $profile:ident,
543        $iname:ident,
544        $fname:ident,
545        [alias = $alias:literal]
546    ) => {{
547        use $crate::__private::ProfileExt as _;
548
549        $profile
550            .find_field_descriptor($iname, stringify!($fname))
551            .or_else(|_| $profile
552                .find_field_descriptor($iname, $alias)
553            )
554    }};
555
556    (@assign
557        $profile:ident,
558        $iname:ident,
559        $fname:ident,
560        [alias = [$($alias:literal),+ $(,)?]]
561    ) => {{
562        use $crate::__private::ProfileExt as _;
563
564        $profile
565            .find_field_descriptor($iname, stringify!($fname))
566            $(
567                .or_else(|_| $profile
568                    .find_field_descriptor($iname, $alias)
569                )
570            )+
571    }};
572}