1use crate::{memory, sync::SpinLock};
18use core::{
19 alloc::{GlobalAlloc, Layout},
20 ptr,
21 sync::atomic::{AtomicUsize, Ordering as AtomicOrdering},
22};
23use x86_64::PhysAddr;
24
25const SLAB_SIZES: [usize; 26] = [
43 8, 16, 24, 32, 48, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, 448, 512, 640, 768, 896,
44 1024, 1280, 1536, 1792, 2048,
45];
46const NUM_SLABS: usize = SLAB_SIZES.len();
47const MAX_SLAB_SIZE: usize = 2048;
49
50#[derive(Clone, Copy, Debug, Eq, PartialEq)]
51pub enum KernelHeapBackend {
52 Slab,
53 Vmalloc,
54}
55
56#[derive(Clone, Copy, Debug, Eq, PartialEq)]
57pub enum KernelHeapAllocError {
58 InvalidLayout,
59 AlignmentExceedsKernelPage {
61 align: usize,
62 },
63 SlabRefillFailed {
64 effective: usize,
65 class_size: usize,
66 },
67 Vmalloc(memory::vmalloc::VmallocError),
68}
69
70#[derive(Clone, Copy, Debug, Eq, PartialEq)]
71pub struct KernelHeapFailureSnapshot {
72 pub backend: KernelHeapBackend,
73 pub requested_size: usize,
74 pub align: usize,
75 pub effective_size: usize,
76 pub error: KernelHeapAllocError,
77}
78
79#[derive(Clone, Copy, Debug, Eq, PartialEq)]
80pub struct SlabDiagSnapshot {
81 pub pages_allocated: usize,
82 pub pages_reclaimed: usize,
83 pub pages_live: usize,
84}
85
86#[inline]
87pub(crate) fn classify_kernel_heap_backend(layout: Layout) -> KernelHeapBackend {
88 let effective = layout.size().max(layout.align());
89 if effective <= MAX_SLAB_SIZE {
90 KernelHeapBackend::Slab
91 } else {
92 KernelHeapBackend::Vmalloc
93 }
94}
95
96const HEAP_POISON_ENABLED: bool = true;
115const POISON_BYTE: u8 = 0xDE;
117const SLAB_CANARY: u32 = 0xDEAD_BEEF;
119
120#[repr(C)]
138struct SlabPageHeader {
139 next_partial: *mut SlabPageHeader,
141 free_head: *mut u8,
143 free_count: u32,
145 total_blocks: u32,
147}
148
149unsafe impl Send for SlabPageHeader {}
151unsafe impl Sync for SlabPageHeader {}
152
153const SLAB_HEADER_SIZE: usize = core::mem::size_of::<SlabPageHeader>();
155
156const _: () = assert!(
158 SLAB_HEADER_SIZE == 24,
159 "SlabPageHeader size changed : update docs"
160);
161const _: () = assert!(
162 (4096 - SLAB_HEADER_SIZE) / SLAB_SIZES[NUM_SLABS - 1] >= 1,
163 "SlabPageHeader too large: largest slab class gets 0 blocks per page"
164);
165
166struct SlabState {
178 partial_pages: [*mut SlabPageHeader; NUM_SLABS],
179}
180
181unsafe impl Send for SlabState {}
183unsafe impl Sync for SlabState {}
184
185impl SlabState {
186 const fn new() -> Self {
187 SlabState {
188 partial_pages: [ptr::null_mut(); NUM_SLABS],
189 }
190 }
191
192 #[inline]
197 fn class_index_for_layout(layout: Layout) -> usize {
198 for (i, &s) in SLAB_SIZES.iter().enumerate() {
199 if layout.size() <= s && layout.align() <= slab_class_alignment(i) {
200 return i;
201 }
202 }
203 unreachable!("class_index_for_layout called for unsupported slab layout")
204 }
205
206 unsafe fn refill(&mut self, ci: usize, token: &crate::sync::IrqDisabledToken) {
209 debug_assert!(
210 !crate::arch::x86_64::interrupts_enabled(),
211 "refill: IRQs must be disabled (IrqDisabledToken contract)"
212 );
213 let slab_size = SLAB_SIZES[ci];
214 let slab_align = slab_class_alignment(ci);
215 let blocks_offset = (SLAB_HEADER_SIZE + slab_align - 1) & !(slab_align - 1);
216 let num_blocks = (4096 - blocks_offset) / slab_size;
217 debug_assert!(
218 num_blocks >= 1,
219 "refill: slab_size {} yields 0 blocks",
220 slab_size
221 );
222
223 let frame = match memory::allocate_frame(token) {
224 Ok(f) => f,
225 Err(_) => return, };
227 SLAB_PAGES_ALLOCATED.fetch_add(1, AtomicOrdering::Relaxed);
228
229 let page_virt = super::phys_to_virt(frame.start_address.as_u64()) as *mut u8;
230
231 let header = page_virt as *mut SlabPageHeader;
233 (*header).next_partial = ptr::null_mut();
234 (*header).free_head = ptr::null_mut();
235 (*header).free_count = 0;
236 (*header).total_blocks = num_blocks as u32;
237
238 let blocks_start = page_virt.add(blocks_offset);
241 for i in (0..num_blocks).rev() {
242 let block = blocks_start.add(i * slab_size);
243 debug_assert_eq!(
244 (block as usize) & (slab_align - 1),
245 0,
246 "slab block alignment invariant broken for class {}",
247 slab_size
248 );
249 *(block as *mut *mut u8) = (*header).free_head;
250 if HEAP_POISON_ENABLED {
251 let end = slab_size.saturating_sub(4);
252 for off in 8..end {
253 *block.add(off) = POISON_BYTE;
254 }
255 if slab_size >= 12 {
256 let cp = block.add(slab_size - 4) as *mut u32;
257 *cp = SLAB_CANARY;
258 }
259 }
260 (*header).free_head = block;
261 (*header).free_count += 1;
262 }
263
264 (*header).next_partial = self.partial_pages[ci];
266 self.partial_pages[ci] = header;
267 }
268
269 unsafe fn alloc_block(&mut self, ci: usize, token: &crate::sync::IrqDisabledToken) -> *mut u8 {
272 if self.partial_pages[ci].is_null() {
273 self.refill(ci, token);
274 }
275 let header = self.partial_pages[ci];
276 if header.is_null() {
277 return ptr::null_mut();
278 }
279
280 let block = (*header).free_head;
281 debug_assert!(
282 !block.is_null(),
283 "alloc_block: partial page has null free_head"
284 );
285
286 (*header).free_head = *(block as *const *mut u8);
287 (*header).free_count -= 1;
288
289 if (*header).free_count == 0 {
291 self.partial_pages[ci] = (*header).next_partial;
292 (*header).next_partial = ptr::null_mut();
293 }
294
295 if HEAP_POISON_ENABLED {
296 let slab_size = SLAB_SIZES[ci];
297 let end = slab_size.saturating_sub(4);
298 let mut bad_off: Option<usize> = None;
299 for off in 8..end {
300 if *block.add(off) != POISON_BYTE {
301 bad_off = Some(off);
302 break;
303 }
304 }
305 if let Some(off) = bad_off {
306 let b0 = *block.add(off);
307 let b1 = if off + 1 < slab_size {
308 *block.add(off + 1)
309 } else {
310 0
311 };
312 let b2 = if off + 2 < slab_size {
313 *block.add(off + 2)
314 } else {
315 0
316 };
317 let b3 = if off + 3 < slab_size {
318 *block.add(off + 3)
319 } else {
320 0
321 };
322 crate::serial_println!(
323 "\x1b[1;31m[HEAP] USE-AFTER-FREE: slab[{}] block={:#x} off={} bytes=[{:02x} {:02x} {:02x} {:02x}]\x1b[0m",
324 slab_size,
325 block as u64,
326 off,
327 b0,
328 b1,
329 b2,
330 b3
331 );
332 }
333 if slab_size >= 12 {
334 let canary = *(block.add(slab_size - 4) as *const u32);
335 if canary != SLAB_CANARY {
336 crate::serial_println!(
337 "\x1b[1;31m[HEAP] CANARY OVERFLOW: slab[{}] block={:#x} expected={:#x} got={:#x}\x1b[0m",
338 slab_size,
339 block as u64,
340 SLAB_CANARY,
341 canary
342 );
343 }
344 }
345 }
346
347 block
348 }
349
350 unsafe fn dealloc_block(
353 &mut self,
354 ptr: *mut u8,
355 ci: usize,
356 token: &crate::sync::IrqDisabledToken,
357 ) {
358 let slab_size = SLAB_SIZES[ci];
359
360 if HEAP_POISON_ENABLED {
361 if slab_size >= 12 {
362 let cp = ptr.add(slab_size - 4) as *mut u32;
363 *cp = SLAB_CANARY;
364 }
365 let end = slab_size.saturating_sub(4);
366 for off in 8..end {
367 *ptr.add(off) = POISON_BYTE;
368 }
369 }
370
371 let page_base = (ptr as usize) & !0xFFF;
373 let header = page_base as *mut SlabPageHeader;
374
375 let was_full = (*header).free_count == 0;
376
377 *(ptr as *mut *mut u8) = (*header).free_head;
379 (*header).free_head = ptr;
380 (*header).free_count += 1;
381
382 if was_full {
383 (*header).next_partial = self.partial_pages[ci];
385 self.partial_pages[ci] = header;
386 }
387
388 if (*header).free_count == (*header).total_blocks {
390 self.remove_from_partial(header, ci);
391 let phys = super::virt_to_phys(page_base as u64);
392 core::ptr::write_bytes(header as *mut u8, 0, SLAB_HEADER_SIZE);
394 let frame = memory::frame::PhysFrame {
395 start_address: PhysAddr::new(phys),
396 };
397 memory::free_frame(token, frame);
398 SLAB_PAGES_RECLAIMED.fetch_add(1, AtomicOrdering::Relaxed);
399 }
400 }
401
402 unsafe fn remove_from_partial(&mut self, page: *mut SlabPageHeader, ci: usize) {
404 if self.partial_pages[ci] == page {
405 self.partial_pages[ci] = (*page).next_partial;
406 (*page).next_partial = ptr::null_mut();
407 return;
408 }
409 let mut cur = self.partial_pages[ci];
410 while !cur.is_null() {
411 let next = (*cur).next_partial;
412 if next == page {
413 (*cur).next_partial = (*page).next_partial;
414 (*page).next_partial = ptr::null_mut();
415 return;
416 }
417 cur = next;
418 }
419 debug_assert!(
420 false,
421 "remove_from_partial: page {:p} not found in class {} list",
422 page, ci
423 );
424 }
425}
426
427static SLAB_ALLOC: SpinLock<SlabState> = SpinLock::new(SlabState::new());
428static LAST_HEAP_FAILURE: SpinLock<Option<KernelHeapFailureSnapshot>> = SpinLock::new(None);
429
430static SLAB_PAGES_ALLOCATED: AtomicUsize = AtomicUsize::new(0);
432static SLAB_PAGES_RECLAIMED: AtomicUsize = AtomicUsize::new(0);
434
435pub fn debug_slab_lock_addr() -> usize {
437 &SLAB_ALLOC as *const _ as usize
438}
439
440pub fn debug_register_slab_trace() {
442 crate::sync::debug_set_trace_slab_addr(debug_slab_lock_addr());
443}
444
445pub struct LockedHeap;
450
451unsafe impl GlobalAlloc for LockedHeap {
452 unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
454 try_alloc_kernel_heap(layout).unwrap_or(ptr::null_mut())
455 }
456
457 unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
459 let effective = layout.size().max(layout.align());
460
461 match classify_kernel_heap_backend(layout) {
462 KernelHeapBackend::Slab => {
463 let ci = SlabState::class_index_for_layout(layout);
465 let cpu = crate::arch::x86_64::percpu::current_cpu_index();
466 let irq_enabled = crate::arch::x86_64::interrupts_enabled();
467 #[cfg(debug_assertions)]
468 if !irq_enabled {
469 use core::sync::atomic::{AtomicUsize, Ordering};
470 static HEAP_D_COUNT: AtomicUsize = AtomicUsize::new(0);
471 let n = HEAP_D_COUNT.fetch_add(1, Ordering::Relaxed);
472 if n % 100 == 0 {
473 crate::e9_println!(
474 "HEAP-D cpu={} irq=0 size={} ci={} n={}",
475 cpu,
476 effective,
477 ci,
478 n
479 );
480 }
481 }
482 #[cfg(debug_assertions)]
487 {
488 let addr = ptr as u64;
489 if addr >= crate::memory::vmalloc::VMALLOC_VIRT_START
490 && addr < crate::memory::vmalloc::VMALLOC_VIRT_END
491 {
492 crate::serial_println!(
493 "[heap][bug] slab dealloc: ptr {:#x} is in vmalloc range : layout mismatch",
494 addr
495 );
496 debug_assert!(
497 false,
498 "slab dealloc with vmalloc pointer : alloc/dealloc layout mismatch"
499 );
500 }
501 }
502 let mut slab = SLAB_ALLOC.lock();
503 slab.with_mut_and_token(|s, token| s.dealloc_block(ptr, ci, token));
504 }
505 KernelHeapBackend::Vmalloc => {
506 let addr = ptr as u64;
508 if addr >= crate::memory::vmalloc::VMALLOC_VIRT_START
509 && addr < crate::memory::vmalloc::VMALLOC_VIRT_END
510 {
511 let ok = crate::sync::with_irqs_disabled(|token| {
512 crate::memory::free_kernel_virtual(ptr, token)
513 });
514 if !ok {
515 crate::serial_println!(
516 "[heap][leak] vmalloc free: no live mapping at {:#x} (wrong base or double-free?)",
517 addr
518 );
519 }
520 } else {
521 crate::serial_println!(
524 "[heap][leak] vmalloc dealloc: ptr {:#x} outside vmalloc arena [{:#x}..{:#x}]",
525 addr,
526 crate::memory::vmalloc::VMALLOC_VIRT_START,
527 crate::memory::vmalloc::VMALLOC_VIRT_END,
528 );
529 #[cfg(debug_assertions)]
530 debug_assert!(
531 false,
532 "vmalloc dealloc with out-of-range pointer : memory leaked"
533 );
534 }
535 }
536 }
537 }
538}
539
540fn record_heap_failure(
541 layout: Layout,
542 effective: usize,
543 backend: KernelHeapBackend,
544 error: KernelHeapAllocError,
545) -> KernelHeapAllocError {
546 *LAST_HEAP_FAILURE.lock() = Some(KernelHeapFailureSnapshot {
547 backend,
548 requested_size: layout.size(),
549 align: layout.align(),
550 effective_size: effective,
551 error,
552 });
553 error
554}
555
556pub fn last_heap_failure_snapshot() -> Option<KernelHeapFailureSnapshot> {
557 *LAST_HEAP_FAILURE.lock()
558}
559
560pub fn slab_diag_snapshot() -> SlabDiagSnapshot {
561 let allocated = SLAB_PAGES_ALLOCATED.load(AtomicOrdering::Relaxed);
562 let reclaimed = SLAB_PAGES_RECLAIMED.load(AtomicOrdering::Relaxed);
563 SlabDiagSnapshot {
564 pages_allocated: allocated,
565 pages_reclaimed: reclaimed,
566 pages_live: allocated.saturating_sub(reclaimed),
567 }
568}
569
570pub const SLAB_NUM_CLASSES: usize = NUM_SLABS;
572
573#[inline]
577pub fn slab_class_size(ci: usize) -> usize {
578 SLAB_SIZES[ci]
579}
580
581#[inline]
585pub fn slab_class_alignment(ci: usize) -> usize {
586 1usize << SLAB_SIZES[ci].trailing_zeros()
587}
588
589#[inline]
593pub fn slab_blocks_per_page(ci: usize) -> usize {
594 let align = slab_class_alignment(ci);
595 let blocks_offset = (SLAB_HEADER_SIZE + align - 1) & !(align - 1);
596 (4096 - blocks_offset) / SLAB_SIZES[ci]
597}
598
599pub fn slab_page_in_partial_list(ci: usize, page_base: u64) -> Option<bool> {
605 let mut guard = SLAB_ALLOC.try_lock()?;
606 Some(guard.with_mut_and_token(|s, _| unsafe {
607 let mut cur = s.partial_pages[ci];
608 while !cur.is_null() {
609 if cur as u64 == page_base {
610 return true;
611 }
612 cur = (*cur).next_partial;
613 }
614 false
615 }))
616}
617
618#[inline]
624pub unsafe fn try_alloc_kernel_heap(layout: Layout) -> Result<*mut u8, KernelHeapAllocError> {
625 let effective = layout.size().max(layout.align());
627 if !layout.align().is_power_of_two() {
630 return Err(record_heap_failure(
631 layout,
632 effective,
633 classify_kernel_heap_backend(layout),
634 KernelHeapAllocError::InvalidLayout,
635 ));
636 }
637 if effective > MAX_SLAB_SIZE && layout.align() > 4096 {
639 return Err(record_heap_failure(
640 layout,
641 effective,
642 KernelHeapBackend::Vmalloc,
643 KernelHeapAllocError::AlignmentExceedsKernelPage {
644 align: layout.align(),
645 },
646 ));
647 }
648 let boot_reg = crate::silo::debug_boot_reg_active();
649 if boot_reg {
650 crate::serial_println!(
651 "[trace][heap] alloc enter effective={} size={} align={}",
652 effective,
653 layout.size(),
654 layout.align()
655 );
656 }
657
658 let result = match classify_kernel_heap_backend(layout) {
659 KernelHeapBackend::Slab => {
660 let ci = SlabState::class_index_for_layout(layout);
662 let cpu = crate::arch::x86_64::percpu::current_cpu_index();
664 let irq_enabled = crate::arch::x86_64::interrupts_enabled();
665 #[cfg(debug_assertions)]
666 if !irq_enabled {
667 use core::sync::atomic::{AtomicUsize, Ordering};
668 static HEAP_A_COUNT: AtomicUsize = AtomicUsize::new(0);
669 let n = HEAP_A_COUNT.fetch_add(1, Ordering::Relaxed);
670 if n % 100 == 0 {
671 crate::e9_println!(
672 "HEAP-A cpu={} irq=0 size={} ci={} n={}",
673 cpu,
674 effective,
675 ci,
676 n
677 );
678 }
679 }
680 if boot_reg {
681 crate::serial_println!(
682 "[trace][heap] alloc slab ci={} slab_size={} lock={:#x}",
683 ci,
684 SLAB_SIZES[ci],
685 &SLAB_ALLOC as *const _ as usize
686 );
687 }
688 let mut slab = SLAB_ALLOC.lock();
689 if boot_reg {
690 crate::serial_println!("[trace][heap] alloc slab lock acquired");
691 }
692 let ptr = slab.with_mut_and_token(|s, token| s.alloc_block(ci, token));
693 if ptr.is_null() {
694 return Err(record_heap_failure(
695 layout,
696 effective,
697 KernelHeapBackend::Slab,
698 KernelHeapAllocError::SlabRefillFailed {
699 effective,
700 class_size: SLAB_SIZES[ci],
701 },
702 ));
703 }
704 ptr
705 }
706 KernelHeapBackend::Vmalloc => {
707 if boot_reg {
709 crate::serial_println!("[trace][heap] alloc vmalloc size={}", effective);
710 }
711
712 crate::sync::with_irqs_disabled(|token| {
713 crate::memory::allocate_kernel_virtual(effective, token).map_err(|error| {
714 record_heap_failure(
715 layout,
716 effective,
717 KernelHeapBackend::Vmalloc,
718 KernelHeapAllocError::Vmalloc(error),
719 )
720 })
721 })?
722 }
723 };
724
725 Ok(result)
726}
727
728#[global_allocator]
729static HEAP_ALLOCATOR: LockedHeap = LockedHeap;
730
731#[inline]
739pub unsafe fn alloc_kernel_heap(layout: Layout) -> *mut u8 {
740 try_alloc_kernel_heap(layout).unwrap_or(ptr::null_mut())
741}
742
743#[inline]
745pub unsafe fn dealloc_kernel_heap(ptr: *mut u8, layout: Layout) {
746 HEAP_ALLOCATOR.dealloc(ptr, layout);
747}
748
749fn log_common_oom_header(layout: Layout, effective: usize) {
750 let cpu = crate::arch::x86_64::percpu::current_cpu_index();
751 let irq_enabled = crate::arch::x86_64::interrupts_enabled();
752 let tid = crate::process::current_task_id()
753 .map(|t| t.as_u64())
754 .unwrap_or(0);
755 let task_name = crate::process::current_task_clone()
756 .map(|t| t.name)
757 .unwrap_or("<none>");
758
759 crate::serial_println!(
760 "[heap][oom] cpu={} irq={} tid={} task={} size={} align={} effective={}",
761 cpu,
762 irq_enabled,
763 tid,
764 task_name,
765 layout.size(),
766 layout.align(),
767 effective
768 );
769}
770
771fn log_buddy_snapshot() -> Option<(usize, usize, usize)> {
772 if let Some(guard) = crate::memory::buddy::get_allocator().try_lock() {
773 if let Some(alloc) = guard.as_ref() {
774 let (total_pages, allocated_pages) = alloc.page_totals();
775 let free_pages = total_pages.saturating_sub(allocated_pages);
776 let fail_counts = crate::memory::buddy::buddy_alloc_fail_counts_snapshot();
777
778 crate::serial_println!(
779 "[heap][oom] buddy: total={} alloc={} free={}",
780 total_pages,
781 allocated_pages,
782 free_pages
783 );
784
785 let mut fail_line = alloc::string::String::from("[heap][oom] buddy_fail_by_order:");
786 for (i, &count) in fail_counts.iter().enumerate() {
787 use core::fmt::Write;
788 let _ = write!(fail_line, " o{}={} ", i, count);
789 }
790 crate::serial_println!("{}", fail_line);
791 return Some((total_pages, allocated_pages, free_pages));
792 }
793 crate::serial_println!("[heap][oom] buddy: allocator uninitialized");
794 return None;
795 }
796
797 crate::serial_println!("[heap][oom] buddy: allocator locked");
798 None
799}
800
801fn log_heap_failure_policy(layout: Layout) {
802 match last_heap_failure_snapshot() {
803 Some(snapshot) => {
804 crate::serial_println!(
805 "[heap][oom] last_failure backend={:?} requested={} align={} effective={} error={:?}",
806 snapshot.backend,
807 snapshot.requested_size,
808 snapshot.align,
809 snapshot.effective_size,
810 snapshot.error
811 );
812 if snapshot.requested_size != layout.size() || snapshot.align != layout.align() {
813 crate::serial_println!(
814 "[heap][oom] note=last_heap_failure does not exactly match current layout; using best-effort context"
815 );
816 }
817 }
818 None => crate::serial_println!("[heap][oom] last_heap_failure unavailable"),
819 }
820}
821
822#[alloc_error_handler]
824fn alloc_error_handler(layout: Layout) -> ! {
825 let effective = layout.size().max(layout.align());
826 let pages_needed = (effective.saturating_add(4095)) / 4096;
827 let order = if pages_needed == 0 {
828 0
829 } else {
830 pages_needed.next_power_of_two().trailing_zeros() as u8
831 };
832 log_common_oom_header(layout, effective);
833
834 if effective <= MAX_SLAB_SIZE {
835 crate::serial_println!(
836 "[heap][oom] backend=slab effective={} class_max={} refill_order=0",
837 effective,
838 MAX_SLAB_SIZE
839 );
840 log_heap_failure_policy(layout);
841 if let Some((total_pages, _, free_pages)) = log_buddy_snapshot() {
842 crate::serial_println!(
843 "[heap][oom] slab-refill pages={} buddy_order={}",
844 pages_needed,
845 order
846 );
847 if free_pages > (total_pages / 4) {
848 crate::serial_println!(
849 "[heap][oom] diagnosis=slab order-0 refill failed despite remaining free pages \
850 ({} free pages): allocator pressure, zone exhaustion, or transient allocator state",
851 free_pages,
852 );
853 }
854 }
855 } else {
856 crate::serial_println!(
857 "[heap][oom] backend=vmalloc request_pages={} legacy_buddy_order_hint={}",
858 pages_needed,
859 order
860 );
861 log_heap_failure_policy(layout);
862 if let Some(snap) = last_heap_failure_snapshot() {
863 if let KernelHeapAllocError::AlignmentExceedsKernelPage { align } = snap.error {
864 crate::serial_println!(
865 "[heap][oom] diagnosis=layout alignment {} B exceeds 4 KiB page alignment guaranteed by vmalloc heap path",
866 align
867 );
868 }
869 }
870 match crate::memory::vmalloc::last_failure_snapshot() {
871 Some(snapshot) => {
872 crate::serial_println!(
873 "[heap][oom] vmalloc_last_failure size={} pages={} error={:?}",
874 snapshot.size,
875 snapshot.pages,
876 snapshot.error
877 );
878 match snapshot.error {
879 crate::memory::vmalloc::VmallocError::SizeExceedsPolicy {
880 requested,
881 max_allowed,
882 } => {
883 crate::serial_println!(
884 "[heap][oom] diagnosis=vmalloc policy limit exceeded requested={} max_allowed={}",
885 requested,
886 max_allowed
887 );
888 }
889 crate::memory::vmalloc::VmallocError::VirtualRangeExhausted => {
890 crate::serial_println!(
891 "[heap][oom] diagnosis=kernel virtual allocation arena exhausted or fragmented"
892 );
893 }
894 crate::memory::vmalloc::VmallocError::PhysicalMemoryExhausted => {
895 crate::serial_println!(
896 "[heap][oom] diagnosis=vmalloc could not acquire enough physical pages"
897 );
898 }
899 crate::memory::vmalloc::VmallocError::MetadataAllocationFailed => {
900 crate::serial_println!(
901 "[heap][oom] diagnosis=vmalloc metadata allocation failed"
902 );
903 }
904 crate::memory::vmalloc::VmallocError::KernelMapFailed => {
905 crate::serial_println!(
906 "[heap][oom] diagnosis=kernel page-table mapping failed during vmalloc"
907 );
908 }
909 crate::memory::vmalloc::VmallocError::ZeroSize => {
910 crate::serial_println!("[heap][oom] diagnosis=zero-sized vmalloc request");
911 }
912 }
913 }
914 None => {
915 crate::serial_println!("[heap][oom] vmalloc_last_failure unavailable");
916 }
917 }
918 let _ = log_buddy_snapshot();
919 }
920 crate::serial_println!(
921 "[heap][oom] policy=fatal_global_alloc_path use try_alloc_kernel_heap()/allocate_kernel_virtual() on recoverable paths"
922 );
923 panic!("fatal kernel heap allocation failure: {:?}", layout)
924}
925
926pub fn dump_diagnostics() {
934 crate::serial_println!("[heap][diag] === Heap Diagnostics ===");
935
936 if let Some(guard) = crate::memory::buddy::get_allocator().try_lock() {
938 if let Some(alloc) = guard.as_ref() {
939 let (total_pages, allocated_pages) = alloc.page_totals();
940 let mut zones =
941 [crate::memory::buddy::ZoneStats::empty(); crate::memory::zone::ZoneType::COUNT];
942 let zone_count = alloc.zone_snapshot(&mut zones);
943 crate::serial_println!(
944 "[heap][diag] buddy: total={} pages, allocated={} pages, free={} pages",
945 total_pages,
946 allocated_pages,
947 total_pages.saturating_sub(allocated_pages)
948 );
949
950 for info in zones.iter().take(zone_count) {
951 crate::serial_println!(
952 "[heap][diag] zone={:?} state={:?} managed={} present={} reserved={} free={} cached={} cu/cm={}/{} avail={} segments={}/{} pageblocks=u{}/m{} u_free={} m_free={} watermarks={}/{}/{} reserve={} largest_order={:?}",
953 info.zone_type,
954 info.pressure(),
955 info.managed_pages,
956 info.present_pages,
957 info.reserved_pages,
958 info.free_pages,
959 info.cached_pages,
960 info.cached_unmovable_pages,
961 info.cached_movable_pages,
962 info.available_after_reserve_pages(),
963 info.segment_count,
964 info.segment_capacity,
965 info.unmovable_pageblocks,
966 info.movable_pageblocks,
967 info.unmovable_free_pages,
968 info.movable_free_pages,
969 info.watermark_min,
970 info.watermark_low,
971 info.watermark_high,
972 info.lowmem_reserve_pages,
973 info.largest_free_order
974 );
975 }
976
977 for zi in 0..zone_count {
979 let zone = alloc.get_zone(zi);
980 let info = zones[zi];
981 let mut line = alloc::string::String::from("[heap][diag] ");
982 use core::fmt::Write;
983 let _ = write!(line, "zone={:?} free_heads:", zone.zone_type);
984 for order in 0..=crate::memory::zone::MAX_ORDER {
985 let unmovable = zone.free_list_count_for(
986 order as u8,
987 crate::memory::zone::Migratetype::Unmovable,
988 );
989 let movable = zone.free_list_count_for(
990 order as u8,
991 crate::memory::zone::Migratetype::Movable,
992 );
993 if unmovable > 0 || movable > 0 {
994 let _ = write!(line, " o{}=u{}/m{} ", order, unmovable, movable);
995 }
996 }
997 crate::serial_println!("{}", line);
998
999 let mut frag = alloc::string::String::from("[heap][diag] ");
1000 let _ = write!(frag, "zone={:?} frag:", zone.zone_type);
1001 for order in 1..=crate::memory::zone::MAX_ORDER {
1002 let score = zone.fragmentation_score(order as u8, info.cached_pages);
1003 let _ = write!(frag, " o{}={}%", order, score);
1004 }
1005 crate::serial_println!("{}", frag);
1006 }
1007 }
1008 } else {
1009 crate::serial_println!("[heap][diag] buddy: allocator locked (retry later)");
1010 }
1011
1012 let fail_counts = crate::memory::buddy::buddy_alloc_fail_counts_snapshot();
1014 let mut has_fails = false;
1015 for (i, &count) in fail_counts.iter().enumerate() {
1016 if count > 0 {
1017 has_fails = true;
1018 }
1019 crate::serial_println!("[heap][diag] buddy_fail[{}]: {}", i, count);
1020 }
1021 if has_fails {
1022 crate::serial_println!(
1023 "[heap][diag] => non-zero buddy_fail counts indicate fragmentation pressure"
1024 );
1025 }
1026
1027 {
1029 let alloc = SLAB_PAGES_ALLOCATED.load(AtomicOrdering::Relaxed);
1030 let reclaim = SLAB_PAGES_RECLAIMED.load(AtomicOrdering::Relaxed);
1031 crate::serial_println!(
1032 "[heap][diag] slab: pages_allocated={} pages_reclaimed={} pages_live={}",
1033 alloc,
1034 reclaim,
1035 alloc.saturating_sub(reclaim)
1036 );
1037 }
1038 if let Some(mut guard) = SLAB_ALLOC.try_lock() {
1039 guard.with_mut_and_token(|s, _| unsafe {
1041 for ci in 0..NUM_SLABS {
1042 let mut head = s.partial_pages[ci];
1043 if head.is_null() {
1044 continue;
1045 }
1046 let mut page_count = 0usize;
1047 let mut free_blocks = 0u32;
1048 while !head.is_null() {
1049 page_count += 1;
1050 free_blocks = free_blocks.saturating_add((*head).free_count);
1051 head = (*head).next_partial;
1052 }
1053 crate::serial_println!(
1054 "[heap][diag] slab[{}]: partial_pages={} free_blocks={}",
1055 SLAB_SIZES[ci],
1056 page_count,
1057 free_blocks
1058 );
1059 }
1060 });
1061 } else {
1062 crate::serial_println!("[heap][diag] slab: locked (retry later)");
1063 }
1064
1065 {
1067 let d = crate::memory::phys_contiguous_diag();
1068 crate::serial_println!(
1069 "[heap][diag] phys_contiguous: pages_allocated={} pages_freed={} pages_live={} alloc_failures={}",
1070 d.pages_allocated,
1071 d.pages_freed,
1072 d.pages_live,
1073 d.alloc_fail_count
1074 );
1075 }
1076
1077 if let Some(snapshot) = last_heap_failure_snapshot() {
1078 crate::serial_println!(
1079 "[heap][diag] last_heap_failure: backend={:?} requested={} align={} effective={} error={:?}",
1080 snapshot.backend,
1081 snapshot.requested_size,
1082 snapshot.align,
1083 snapshot.effective_size,
1084 snapshot.error
1085 );
1086 }
1087
1088 crate::memory::vmalloc::dump_diagnostics();
1089
1090 crate::serial_println!("[heap][diag] === End Diagnostics ===");
1091}