1use super::*;
7use crate::mem::DebugMemorySettings;
8
9const MAGIC: u32 = 0x7F84_E666;
10
11pub(super) fn eligible(flags: vk::MemoryPropertyFlags) -> bool {
12 let required =
13 vk::MemoryPropertyFlagBits::HOST_VISIBLE_BIT | vk::MemoryPropertyFlagBits::HOST_COHERENT_BIT;
14 flags.as_raw() & required.as_raw() == required.as_raw()
15}
16
17pub fn set_debug_memory_settings(
21 allocator: &mut Allocator<'_>,
22 settings: DebugMemorySettings,
23) -> Result<(), AllocError> {
24 allocator.ensure_global()?;
25 allocator.ensure_open()?;
26 if allocator.native_count != 0 || !allocator.live.is_empty() {
27 return Err(AllocError::LiveAllocations);
28 }
29 if !allocator.pools.is_empty() {
30 return Err(AllocError::LivePools);
31 }
32 if !settings.margin.is_multiple_of(4) {
33 return Err(AllocError::InvalidRequest(
34 "debug guard margin must be a multiple of four",
35 ));
36 }
37 if settings.margin != 0 || settings.initialize_allocations || settings.poison_frees {
38 require(allocator.device.fp().map_memory.is_some(), "vkMapMemory")?;
39 require(
40 allocator.device.fp().unmap_memory.is_some(),
41 "vkUnmapMemory",
42 )?;
43 if settings.initialize_allocations || settings.poison_frees {
44 require(
45 allocator.device.fp().flush_mapped_memory_ranges.is_some(),
46 "vkFlushMappedMemoryRanges",
47 )?;
48 }
49 }
50 allocator.debug_settings = settings;
51 Ok(())
52}
53
54unsafe fn fill(
55 allocator: &mut Allocator<'_>,
56 allocation: &Allocation,
57 byte: u8,
58) -> Result<(), AllocError> {
59 let length = usize::try_from(allocation.size).map_err(|_| AllocError::InvalidRange)?;
60 let pointer = unsafe { map(allocator, allocation) }?;
62 unsafe { pointer.cast::<u8>().write_bytes(byte, length) };
64 let flushed = unsafe { flush(allocator, allocation, 0, vk::WHOLE_SIZE) };
66 let cleanup = unsafe { unmap(allocator, allocation) };
68 flushed.and(cleanup)
69}
70
71pub(super) unsafe fn initialize(
72 allocator: &mut Allocator<'_>,
73 allocation: &Allocation,
74) -> Result<(), AllocError> {
75 let settings = allocator.debug_settings;
76 if settings.margin == 0 && !settings.initialize_allocations && !settings.poison_frees {
77 return Ok(());
78 }
79 let live = allocator.validate(allocation)?;
80 if live.imported {
81 return Ok(());
82 }
83 let guard_size = live.guard_size;
84 let guard_offset = usize::try_from(live.guard_offset).map_err(|_| AllocError::InvalidRange)?;
85 if allocator.debug_settings.initialize_allocations && allocation.is_host_visible() {
86 unsafe { fill(allocator, allocation, 0xDC) }?;
88 }
89 if guard_size != 0 {
90 let length = usize::try_from(guard_size).map_err(|_| AllocError::InvalidRange)?;
91 let pointer = unsafe { map(allocator, allocation) }?;
93 for offset in (0..length).step_by(4) {
94 unsafe {
96 pointer
97 .cast::<u8>()
98 .add(guard_offset + offset)
99 .cast::<u32>()
100 .write_unaligned(MAGIC)
101 };
102 }
103 unsafe { unmap(allocator, allocation) }?;
105 }
106 allocator
107 .live
108 .get_mut(&allocation.serial)
109 .expect("live initialized allocation")
110 .debug_ready = true;
111 Ok(())
112}
113
114pub(super) unsafe fn check_one(
115 allocator: &mut Allocator<'_>,
116 allocation: &Allocation,
117) -> Result<(), AllocError> {
118 let live = allocator.validate(allocation)?;
119 if !live.debug_ready || live.guard_size == 0 {
120 return Ok(());
121 }
122 let guard_offset = usize::try_from(live.guard_offset).map_err(|_| AllocError::InvalidRange)?;
123 let length = usize::try_from(live.guard_size).map_err(|_| AllocError::InvalidRange)?;
124 let pointer = unsafe { map(allocator, allocation) }?;
126 let mut failure = None;
127 for offset in (0..length).step_by(4) {
128 let value = unsafe {
130 pointer
131 .cast::<u8>()
132 .add(guard_offset + offset)
133 .cast::<u32>()
134 .read_unaligned()
135 };
136 if value != MAGIC {
137 failure = Some(AllocError::CorruptionDetected {
138 allocation: allocation.serial,
139 offset: u64::try_from(guard_offset + offset).expect("mapped native host extent"),
140 });
141 break;
142 }
143 }
144 let cleanup = unsafe { unmap(allocator, allocation) };
146 if let Some(error) = failure {
147 Err(error)
148 } else {
149 cleanup
150 }
151}
152
153pub(super) unsafe fn before_free(
154 allocator: &mut Allocator<'_>,
155 allocation: &Allocation,
156) -> Result<(), AllocError> {
157 if allocator.debug_settings.margin == 0 && !allocator.debug_settings.poison_frees {
158 return Ok(());
159 }
160 if !allocator.validate(allocation)?.debug_ready {
161 return Ok(());
162 }
163 unsafe { check_one(allocator, allocation) }?;
165 if allocator.debug_settings.poison_frees && allocation.is_host_visible() {
166 unsafe { fill(allocator, allocation, 0xEF) }?;
168 }
169 Ok(())
170}
171
172unsafe fn check(
173 allocator: &mut Allocator<'_>,
174 bits: u32,
175 pool: Option<u64>,
176) -> Result<(), AllocError> {
177 allocator.ensure_open()?;
178 if allocator.debug_settings.margin == 0
179 || !(0..allocator.selector.properties.memory_type_count)
180 .any(|index| bits & (1 << index) != 0 && eligible(memory_flags(allocator, index)))
181 {
182 return Err(AllocError::UnsupportedCapability(
183 "debug corruption detection is not enabled for these memory types",
184 ));
185 }
186 let mut selected = HostVec::new(allocator.host);
187 selected
188 .try_reserve_exact(allocator.live.len())
189 .map_err(|_| AllocError::HostAllocationFailed)?;
190 for (&serial, live) in &allocator.live {
191 if bits & (1 << live.memory_type_index) != 0
192 && live.guard_size != 0
193 && (pool.is_none() || live.pool == pool)
194 {
195 selected.push_reserved(serial);
196 }
197 }
198 selected.sort_unstable();
199 for &serial in &selected {
200 let allocation = allocator.token(serial)?;
201 unsafe { check_one(allocator, &allocation) }?;
203 }
204 Ok(())
205}
206
207pub unsafe fn check_corruption(
214 allocator: &mut Allocator<'_>,
215 memory_type_bits: u32,
216) -> Result<(), AllocError> {
217 unsafe { check(allocator, memory_type_bits, None) }
219}
220
221pub unsafe fn check_pool_corruption(
225 allocator: &mut Allocator<'_>,
226 pool: &Pool,
227) -> Result<(), AllocError> {
228 let index = pool::validate(allocator, pool)?.info.memory_type_index;
229 unsafe { check(allocator, 1 << index, Some(pool.serial)) }
231}
232
233impl Allocator<'_> {
234 pub fn set_debug_memory_settings(
236 &mut self,
237 settings: DebugMemorySettings,
238 ) -> Result<(), AllocError> {
239 set_debug_memory_settings(self, settings)
240 }
241
242 pub unsafe fn check_corruption(&mut self, bits: u32) -> Result<(), AllocError> {
246 unsafe { check_corruption(self, bits) }
248 }
249
250 pub unsafe fn check_pool_corruption(&mut self, pool: &Pool) -> Result<(), AllocError> {
254 unsafe { check_pool_corruption(self, pool) }
256 }
257}