Skip to main content

PageTableMonitor

Struct PageTableMonitor 

Source
pub struct PageTableMonitor<Driver, Tag = &'static str>
where Driver: VmiRead + VmiSetProtection, <Driver as VmiDriver>::Architecture: ArchAdapter<Driver, Tag>, Tag: TagType,
{ /* private fields */ }
Available on crate features ptm and utils only.
Expand description

Architecture-independent page table monitor.

Thin wrapper that delegates to an architecture-specific implementation selected via ArchAdapter.

Implementations§

Source§

impl<Driver, Tag> PageTableMonitor<Driver, Tag>
where Driver: VmiRead + VmiSetProtection, <Driver as VmiDriver>::Architecture: ArchAdapter<Driver, Tag>, Tag: TagType,

Source

pub fn new() -> PageTableMonitor<Driver, Tag>

Creates a new page table monitor.

Examples found in repository?
examples/windows-breakpoint-manager.rs (line 182)
67    pub fn new(
68        session: &VmiSession<WindowsOs<Driver>>,
69        profile: &Profile,
70        terminate_flag: Arc<AtomicBool>,
71    ) -> Result<Self, VmiError> {
72        // Capture the current state of the vCPU and get the base address of
73        // the kernel.
74        //
75        // This base address is essential to correctly offset monitored
76        // functions.
77        //
78        // NOTE: `kernel_image_base` tries to find the kernel in the memory
79        //       with the help of the CPU registers. On AMD64 architecture,
80        //       the kernel image base is usually found using the `MSR_LSTAR`
81        //       register, which contains the address of the system call
82        //       handler. This register is set by the operating system during
83        //       boot and is left unchanged (unless some rootkits are involved).
84        //
85        //       Therefore, we can take an arbitrary registers at any point
86        //       in time (as long as the OS has booted and the page tables are
87        //       set up) and use them to find the kernel image base.
88        let registers = session.registers(VcpuId(0))?;
89        let vmi = session.with_registers(&registers);
90
91        let kernel_image_base = vmi.os().kernel_image_base()?;
92        tracing::info!(%kernel_image_base);
93
94        // Get the system process.
95        //
96        // The system process is the first process created by the kernel.
97        // In Windows, it is referenced by the kernel symbol `PsInitialSystemProcess`.
98        // To monitor page table entries, we need to locate the translation root
99        // of this process.
100        let system_process = vmi.os().system_process()?;
101        tracing::info!(system_process = %system_process.object()?);
102
103        // Get the translation root of the system process.
104        // This is effectively "the CR3 of the kernel".
105        //
106        // The translation root is the root of the page table hierarchy (also
107        // known as the Directory Table Base or PML4).
108        let root = system_process.translation_root()?;
109        tracing::info!(%root);
110
111        // Load the symbols from the profile.
112        let symbols = Symbols::new(profile)?;
113
114        // Enable monitoring of the INT3 and singlestep events.
115        //
116        // INT3 is used to monitor the execution of specific functions.
117        // Singlestep is used to monitor the modifications of page table
118        // entries.
119        vmi.monitor_enable(EventMonitor::Interrupt(ExceptionVector::Breakpoint))?;
120        vmi.monitor_enable(EventMonitor::Singlestep)?;
121
122        // Create a new view for the monitor.
123        // This view is used for monitoring function calls and memory accesses.
124        let view = vmi.create_view(MemoryAccess::RWX)?;
125        vmi.switch_to_view(view)?;
126
127        // Create a new breakpoint controller.
128        //
129        // The breakpoint controller is used to insert breakpoints for specific
130        // functions.
131        //
132        // From the guest's perspective, these breakpoints are "hidden", since
133        // the breakpoint controller will unset the read/write access to the
134        // physical memory page where the breakpoint is inserted, while keeping
135        // the execute access.
136        //
137        // This way, the guest will be able to execute the code, but attempts to
138        // read or write the memory will trigger the `memory_access` callback.
139        //
140        // When a vCPU tries to execute the breakpoint instruction:
141        // - an `interrupt` callback will be triggered
142        // - the breakpoint will be handled (e.g., log the function call)
143        // - a fast-singlestep[1] will be performed over the INT3 instruction
144        //
145        // When a vCPU tries to read from this page (e.g., a PatchGuard check):
146        // - `memory_access` callback will be triggered (with the `MemoryAccess::R`
147        //   access type)
148        // - fast-singlestep[1] will be performed over the instruction that tried to
149        //   read the memory
150        //
151        // This way, the instruction will read the original memory content.
152        //
153        // [1] Fast-singlestep is a VMI feature that allows to switch the vCPU
154        //     to a different view, execute a single instruction, and then
155        //     switch back to the original view. In this case, the view is
156        //     switched to the `default_view` (which is unmodified).
157        let mut bpm = BreakpointManager::new();
158
159        // Create a new page table monitor.
160        //
161        // The page table monitor is used to monitor the page table entries of
162        // the hooked functions.
163        //
164        // More specifically, it is used to monitor the pages that the breakpoint
165        // was inserted into. This is necessary to handle the case when the
166        // page containing the breakpoint is paged out (and then paged in
167        // again).
168        //
169        // `PageTableMonitor` works by unsetting the write access to the page
170        // tables of the hooked functions. When the page is paged out, the
171        // `PRESENT` bit in the page table entry is unset and, conversely, when
172        // the page is paged in, the `PRESENT` bit is set again.
173        //
174        // When that happens:
175        // - the `memory_access` callback will be triggered (with the `MemoryAccess::R`
176        //   access type)
177        // - the callback will mark the page as dirty in the page table monitor
178        // - a singlestep will be performed over the instruction that tried to modify
179        //   the memory containing the page table entry
180        // - the `singlestep` handler will process the dirty page table entries and
181        //   inform the breakpoint controller to handle the changes
182        let mut ptm = PageTableMonitor::new();
183
184        // Pause the VM to avoid race conditions between inserting breakpoints
185        // and monitoring page table entries. The VM resumes when the pause
186        // guard is dropped.
187        let _pause_guard = vmi.pause_guard()?;
188
189        // Insert breakpoint for the `NtCreateFile` function.
190        let va_NtCreateFile = kernel_image_base + symbols.NtCreateFile;
191        let cx_NtCreateFile = (va_NtCreateFile, root);
192        let bp_NtCreateFile = Breakpoint::new(cx_NtCreateFile, view)
193            .global()
194            .with_tag("NtCreateFile");
195        bpm.insert(&vmi, bp_NtCreateFile)?;
196        ptm.monitor(&vmi, cx_NtCreateFile, view, "NtCreateFile")?;
197        tracing::info!(%va_NtCreateFile);
198
199        // Insert breakpoint for the `NtWriteFile` function.
200        let va_NtWriteFile = kernel_image_base + symbols.NtWriteFile;
201        let cx_NtWriteFile = (va_NtWriteFile, root);
202        let bp_NtWriteFile = Breakpoint::new(cx_NtWriteFile, view)
203            .global()
204            .with_tag("NtWriteFile");
205        bpm.insert(&vmi, bp_NtWriteFile)?;
206        ptm.monitor(&vmi, cx_NtWriteFile, view, "NtWriteFile")?;
207        tracing::info!(%va_NtWriteFile);
208
209        // Insert breakpoint for the `PspInsertProcess` function.
210        let va_PspInsertProcess = kernel_image_base + symbols.PspInsertProcess;
211        let cx_PspInsertProcess = (va_PspInsertProcess, root);
212        let bp_PspInsertProcess = Breakpoint::new(cx_PspInsertProcess, view)
213            .global()
214            .with_tag("PspInsertProcess");
215        bpm.insert(&vmi, bp_PspInsertProcess)?;
216        ptm.monitor(&vmi, cx_PspInsertProcess, view, "PspInsertProcess")?;
217
218        // Insert breakpoint for the `MmCleanProcessAddressSpace` function.
219        let va_MmCleanProcessAddressSpace = kernel_image_base + symbols.MmCleanProcessAddressSpace;
220        let cx_MmCleanProcessAddressSpace = (va_MmCleanProcessAddressSpace, root);
221        let bp_MmCleanProcessAddressSpace = Breakpoint::new(cx_MmCleanProcessAddressSpace, view)
222            .global()
223            .with_tag("MmCleanProcessAddressSpace");
224        bpm.insert(&vmi, bp_MmCleanProcessAddressSpace)?;
225        ptm.monitor(
226            &vmi,
227            cx_MmCleanProcessAddressSpace,
228            view,
229            "MmCleanProcessAddressSpace",
230        )?;
231
232        Ok(Self {
233            terminate_flag,
234            view,
235            bpm,
236            ptm,
237        })
238    }
Source

pub fn monitored_tables(&self) -> usize

Returns the number of write-protected page table pages.

Source

pub fn monitored_entries(&self) -> usize

Returns the number of monitored entries.

Source

pub fn paged_in_entries(&self) -> usize

Returns the number of monitored addresses currently backed by physical memory.

Source

pub fn dump(&self)

Logs the monitor state for debugging.

Source

pub fn monitor( &mut self, vmi: &VmiCore<Driver>, ctx: impl Into<AddressContext>, view: View, tag: Tag, ) -> Result<(), VmiError>

Begins monitoring a virtual address for page table changes.

Walks the page table hierarchy and write-protects the relevant pages.

Examples found in repository?
examples/windows-breakpoint-manager.rs (line 196)
67    pub fn new(
68        session: &VmiSession<WindowsOs<Driver>>,
69        profile: &Profile,
70        terminate_flag: Arc<AtomicBool>,
71    ) -> Result<Self, VmiError> {
72        // Capture the current state of the vCPU and get the base address of
73        // the kernel.
74        //
75        // This base address is essential to correctly offset monitored
76        // functions.
77        //
78        // NOTE: `kernel_image_base` tries to find the kernel in the memory
79        //       with the help of the CPU registers. On AMD64 architecture,
80        //       the kernel image base is usually found using the `MSR_LSTAR`
81        //       register, which contains the address of the system call
82        //       handler. This register is set by the operating system during
83        //       boot and is left unchanged (unless some rootkits are involved).
84        //
85        //       Therefore, we can take an arbitrary registers at any point
86        //       in time (as long as the OS has booted and the page tables are
87        //       set up) and use them to find the kernel image base.
88        let registers = session.registers(VcpuId(0))?;
89        let vmi = session.with_registers(&registers);
90
91        let kernel_image_base = vmi.os().kernel_image_base()?;
92        tracing::info!(%kernel_image_base);
93
94        // Get the system process.
95        //
96        // The system process is the first process created by the kernel.
97        // In Windows, it is referenced by the kernel symbol `PsInitialSystemProcess`.
98        // To monitor page table entries, we need to locate the translation root
99        // of this process.
100        let system_process = vmi.os().system_process()?;
101        tracing::info!(system_process = %system_process.object()?);
102
103        // Get the translation root of the system process.
104        // This is effectively "the CR3 of the kernel".
105        //
106        // The translation root is the root of the page table hierarchy (also
107        // known as the Directory Table Base or PML4).
108        let root = system_process.translation_root()?;
109        tracing::info!(%root);
110
111        // Load the symbols from the profile.
112        let symbols = Symbols::new(profile)?;
113
114        // Enable monitoring of the INT3 and singlestep events.
115        //
116        // INT3 is used to monitor the execution of specific functions.
117        // Singlestep is used to monitor the modifications of page table
118        // entries.
119        vmi.monitor_enable(EventMonitor::Interrupt(ExceptionVector::Breakpoint))?;
120        vmi.monitor_enable(EventMonitor::Singlestep)?;
121
122        // Create a new view for the monitor.
123        // This view is used for monitoring function calls and memory accesses.
124        let view = vmi.create_view(MemoryAccess::RWX)?;
125        vmi.switch_to_view(view)?;
126
127        // Create a new breakpoint controller.
128        //
129        // The breakpoint controller is used to insert breakpoints for specific
130        // functions.
131        //
132        // From the guest's perspective, these breakpoints are "hidden", since
133        // the breakpoint controller will unset the read/write access to the
134        // physical memory page where the breakpoint is inserted, while keeping
135        // the execute access.
136        //
137        // This way, the guest will be able to execute the code, but attempts to
138        // read or write the memory will trigger the `memory_access` callback.
139        //
140        // When a vCPU tries to execute the breakpoint instruction:
141        // - an `interrupt` callback will be triggered
142        // - the breakpoint will be handled (e.g., log the function call)
143        // - a fast-singlestep[1] will be performed over the INT3 instruction
144        //
145        // When a vCPU tries to read from this page (e.g., a PatchGuard check):
146        // - `memory_access` callback will be triggered (with the `MemoryAccess::R`
147        //   access type)
148        // - fast-singlestep[1] will be performed over the instruction that tried to
149        //   read the memory
150        //
151        // This way, the instruction will read the original memory content.
152        //
153        // [1] Fast-singlestep is a VMI feature that allows to switch the vCPU
154        //     to a different view, execute a single instruction, and then
155        //     switch back to the original view. In this case, the view is
156        //     switched to the `default_view` (which is unmodified).
157        let mut bpm = BreakpointManager::new();
158
159        // Create a new page table monitor.
160        //
161        // The page table monitor is used to monitor the page table entries of
162        // the hooked functions.
163        //
164        // More specifically, it is used to monitor the pages that the breakpoint
165        // was inserted into. This is necessary to handle the case when the
166        // page containing the breakpoint is paged out (and then paged in
167        // again).
168        //
169        // `PageTableMonitor` works by unsetting the write access to the page
170        // tables of the hooked functions. When the page is paged out, the
171        // `PRESENT` bit in the page table entry is unset and, conversely, when
172        // the page is paged in, the `PRESENT` bit is set again.
173        //
174        // When that happens:
175        // - the `memory_access` callback will be triggered (with the `MemoryAccess::R`
176        //   access type)
177        // - the callback will mark the page as dirty in the page table monitor
178        // - a singlestep will be performed over the instruction that tried to modify
179        //   the memory containing the page table entry
180        // - the `singlestep` handler will process the dirty page table entries and
181        //   inform the breakpoint controller to handle the changes
182        let mut ptm = PageTableMonitor::new();
183
184        // Pause the VM to avoid race conditions between inserting breakpoints
185        // and monitoring page table entries. The VM resumes when the pause
186        // guard is dropped.
187        let _pause_guard = vmi.pause_guard()?;
188
189        // Insert breakpoint for the `NtCreateFile` function.
190        let va_NtCreateFile = kernel_image_base + symbols.NtCreateFile;
191        let cx_NtCreateFile = (va_NtCreateFile, root);
192        let bp_NtCreateFile = Breakpoint::new(cx_NtCreateFile, view)
193            .global()
194            .with_tag("NtCreateFile");
195        bpm.insert(&vmi, bp_NtCreateFile)?;
196        ptm.monitor(&vmi, cx_NtCreateFile, view, "NtCreateFile")?;
197        tracing::info!(%va_NtCreateFile);
198
199        // Insert breakpoint for the `NtWriteFile` function.
200        let va_NtWriteFile = kernel_image_base + symbols.NtWriteFile;
201        let cx_NtWriteFile = (va_NtWriteFile, root);
202        let bp_NtWriteFile = Breakpoint::new(cx_NtWriteFile, view)
203            .global()
204            .with_tag("NtWriteFile");
205        bpm.insert(&vmi, bp_NtWriteFile)?;
206        ptm.monitor(&vmi, cx_NtWriteFile, view, "NtWriteFile")?;
207        tracing::info!(%va_NtWriteFile);
208
209        // Insert breakpoint for the `PspInsertProcess` function.
210        let va_PspInsertProcess = kernel_image_base + symbols.PspInsertProcess;
211        let cx_PspInsertProcess = (va_PspInsertProcess, root);
212        let bp_PspInsertProcess = Breakpoint::new(cx_PspInsertProcess, view)
213            .global()
214            .with_tag("PspInsertProcess");
215        bpm.insert(&vmi, bp_PspInsertProcess)?;
216        ptm.monitor(&vmi, cx_PspInsertProcess, view, "PspInsertProcess")?;
217
218        // Insert breakpoint for the `MmCleanProcessAddressSpace` function.
219        let va_MmCleanProcessAddressSpace = kernel_image_base + symbols.MmCleanProcessAddressSpace;
220        let cx_MmCleanProcessAddressSpace = (va_MmCleanProcessAddressSpace, root);
221        let bp_MmCleanProcessAddressSpace = Breakpoint::new(cx_MmCleanProcessAddressSpace, view)
222            .global()
223            .with_tag("MmCleanProcessAddressSpace");
224        bpm.insert(&vmi, bp_MmCleanProcessAddressSpace)?;
225        ptm.monitor(
226            &vmi,
227            cx_MmCleanProcessAddressSpace,
228            view,
229            "MmCleanProcessAddressSpace",
230        )?;
231
232        Ok(Self {
233            terminate_flag,
234            view,
235            bpm,
236            ptm,
237        })
238    }
Source

pub fn unmonitor( &mut self, vmi: &VmiCore<Driver>, ctx: impl Into<AddressContext>, view: View, ) -> Result<(), VmiError>

Stops monitoring a virtual address.

Removes write protection from page table pages that no longer have any monitored entries.

Source

pub fn unmonitor_all(&mut self, vmi: &VmiCore<Driver>)

Stops monitoring all virtual addresses and restores memory access.

Source

pub fn unmonitor_view(&mut self, vmi: &VmiCore<Driver>, view: View)

Stops monitoring all virtual addresses associated with a view.

Source

pub fn mark_dirty_entry( &mut self, entry_pa: Pa, view: View, vcpu_id: VcpuId, ) -> bool

Marks a page table entry as dirty after a write to a monitored page.

Returns true if the entry is being monitored (and was marked), false otherwise.

Examples found in repository?
examples/windows-breakpoint-manager.rs (line 260)
241    fn memory_access(
242        &mut self,
243        vmi: &VmiContext<WindowsOs<Driver>>,
244    ) -> Result<VmiEventResponse<Amd64>, VmiError> {
245        let memory_access = vmi.event().reason().as_memory_access();
246
247        tracing::trace!(
248            pa = %memory_access.pa,
249            va = %memory_access.va,
250            access = %memory_access.access,
251        );
252
253        if memory_access.access.contains(MemoryAccess::W) {
254            // It is assumed that a write memory access event is caused by a
255            // page table modification.
256            //
257            // The page table entry is marked as dirty in the page table monitor
258            // and a singlestep is performed to process the dirty entries.
259            self.ptm
260                .mark_dirty_entry(memory_access.pa, self.view, vmi.event().vcpu_id());
261
262            Ok(VmiEventResponse::singlestep().with_view(vmi.default_view()))
263        }
264        else if memory_access.access.contains(MemoryAccess::R) {
265            // When the guest tries to read from the memory, a fast-singlestep
266            // is performed over the instruction that tried to read the memory.
267            // This is done to allow the instruction to read the original memory
268            // content.
269            Ok(VmiEventResponse::fast_singlestep(vmi.default_view()))
270        }
271        else {
272            panic!("Unhandled memory access: {memory_access:?}");
273        }
274    }
Source

pub fn process_dirty_entries( &mut self, vmi: &VmiCore<Driver>, vcpu_id: VcpuId, ) -> Result<Vec<PageTableMonitorEvent>, VmiError>

Processes dirty entries for a vCPU, returning page-in/page-out events.

Should be called after singlestepping past the write that triggered the dirty marking, so the new PTE value is committed to memory.

Examples found in repository?
examples/windows-breakpoint-manager.rs (line 330)
324    fn singlestep(
325        &mut self,
326        vmi: &VmiContext<WindowsOs<Driver>>,
327    ) -> Result<VmiEventResponse<Amd64>, VmiError> {
328        // Get the page table modifications by processing the dirty page table
329        // entries.
330        let ptm_events = self.ptm.process_dirty_entries(vmi, vmi.event().vcpu_id())?;
331
332        // Let the breakpoint controller handle the page table modifications.
333        self.bpm.handle_ptm_events(vmi, ptm_events)?;
334
335        // Disable singlestep and switch back to our view.
336        Ok(VmiEventResponse::default().with_view(self.view))
337    }

Auto Trait Implementations§

§

impl<Driver, Tag> Freeze for PageTableMonitor<Driver, Tag>
where <<Driver as VmiDriver>::Architecture as ArchAdapter<Driver, Tag>>::Monitor: Freeze,

§

impl<Driver, Tag> RefUnwindSafe for PageTableMonitor<Driver, Tag>
where <<Driver as VmiDriver>::Architecture as ArchAdapter<Driver, Tag>>::Monitor: RefUnwindSafe,

§

impl<Driver, Tag> Send for PageTableMonitor<Driver, Tag>
where <<Driver as VmiDriver>::Architecture as ArchAdapter<Driver, Tag>>::Monitor: Send,

§

impl<Driver, Tag> Sync for PageTableMonitor<Driver, Tag>
where <<Driver as VmiDriver>::Architecture as ArchAdapter<Driver, Tag>>::Monitor: Sync,

§

impl<Driver, Tag> Unpin for PageTableMonitor<Driver, Tag>
where <<Driver as VmiDriver>::Architecture as ArchAdapter<Driver, Tag>>::Monitor: Unpin,

§

impl<Driver, Tag> UnsafeUnpin for PageTableMonitor<Driver, Tag>
where <<Driver as VmiDriver>::Architecture as ArchAdapter<Driver, Tag>>::Monitor: UnsafeUnpin,

§

impl<Driver, Tag> UnwindSafe for PageTableMonitor<Driver, Tag>
where <<Driver as VmiDriver>::Architecture as ArchAdapter<Driver, Tag>>::Monitor: UnwindSafe,

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> ArchivePointee for T

Source§

type ArchivedMetadata = ()

The archived version of the pointer metadata for this type.
Source§

fn pointer_metadata( _: &<T as ArchivePointee>::ArchivedMetadata, ) -> <T as Pointee>::Metadata

Converts some archived metadata to the pointer metadata for itself.
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> LayoutRaw for T

Source§

fn layout_raw(_: <T as Pointee>::Metadata) -> Result<Layout, LayoutError>

Returns the layout of the type.
Source§

impl<T, N1, N2> Niching<NichedOption<T, N1>> for N2
where T: SharedNiching<N1, N2>, N1: Niching<T>, N2: Niching<T>,

Source§

unsafe fn is_niched(niched: *const NichedOption<T, N1>) -> bool

Returns whether the given value has been niched. Read more
Source§

fn resolve_niched(out: Place<NichedOption<T, N1>>)

Writes data to out indicating that a T is niched.
Source§

impl<T> Pointee for T

Source§

type Metadata = ()

The metadata type for pointers and references to this type.
Source§

impl<T> PolicyExt for T
where T: ?Sized,

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow only if self and other return Action::Follow. Read more
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow if either self or other returns Action::Follow. Read more
Source§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more