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}