Walk the GLOBAL inode hashtable. This is the only reliable way to find every cached inode on modern (≥ ~5.x) kernels — `super_block.s_inodes` is frequently sparse because most inodes spend their cached life on the per-sb LRU and aren't kept on s_inodes. inode_hashtable is a `struct hlist_head *` array of `1 << i_hash_shift` entries (8 bytes each — single `first` pointer per bucket). Each cached i
| 458 | // entries (8 bytes each — single `first` pointer per bucket). Each cached |
| 459 | // inode is linked into one bucket via `inode.i_hash` (hlist_node @ 0xd0). |
| 460 | std::vector<VAddr> walk_inode_hashtable(const Engine& eng, const Offsets& o) |
| 461 | { |
| 462 | std::vector<VAddr> inodes; |
| 463 | const auto& isf = eng.isf(); |
| 464 | if (o.in_i_hash == 0) { |
| 465 | log::warn("inode_hashtable: ISF has no inode.i_hash field"); |
| 466 | return inodes; |
| 467 | } |
| 468 | |
| 469 | auto* tbl_sym = isf.find_symbol("inode_hashtable"); |
| 470 | auto* shift_sym = isf.find_symbol("i_hash_shift"); |
| 471 | if (!tbl_sym || !shift_sym) { |
| 472 | log::warn("inode_hashtable: missing inode_hashtable / i_hash_shift " |
| 473 | "symbols"); |
| 474 | return inodes; |
| 475 | } |
| 476 | |
| 477 | VAddr table_va = 0; |
| 478 | if (!kva_read_pod(eng, tbl_sym->address, table_va) || table_va == 0) { |
| 479 | log::warn("inode_hashtable: cannot read table pointer"); |
| 480 | return inodes; |
| 481 | } |
| 482 | u32 hash_shift = 0; |
| 483 | if (!kva_read_pod(eng, shift_sym->address, hash_shift) || |
| 484 | hash_shift == 0 || hash_shift > 30) |
| 485 | { |
| 486 | log::warn("inode_hashtable: bad hash_shift = {}", hash_shift); |
| 487 | return inodes; |
| 488 | } |
| 489 | const u64 buckets = 1ULL << hash_shift; |
| 490 | log::debug("inode_hashtable @ {:#x} ({} buckets)", table_va, buckets); |
| 491 | |
| 492 | // Read the array in big chunks to amortise PGD walks. |
| 493 | constexpr u64 kChunk = 0x10000; // 64 KiB at a time (8192 ptrs) |
| 494 | std::vector<VAddr> chunk(kChunk / sizeof(VAddr), 0); |
| 495 | constexpr std::size_t kInodeCap = 500'000; |
| 496 | std::size_t empty_buckets = 0; |
| 497 | |
| 498 | for (u64 start = 0; start < buckets; start += chunk.size()) { |
| 499 | u64 want = std::min<u64>(chunk.size(), buckets - start); |
| 500 | std::size_t bytes = want * sizeof(VAddr); |
| 501 | std::memset(chunk.data(), 0, bytes); |
| 502 | kva_read(eng, table_va + start * sizeof(VAddr), chunk.data(), bytes); |
| 503 | |
| 504 | for (u64 i = 0; i < want; ++i) { |
| 505 | VAddr node = chunk[i]; |
| 506 | if (node == 0) { ++empty_buckets; continue; } |
| 507 | // Walk the hlist chain in this bucket. |
| 508 | int chain_guard = 0; |
| 509 | while (node != 0 && chain_guard++ < 1'000'000 && |
| 510 | inodes.size() < kInodeCap) |
| 511 | { |
| 512 | VAddr inode_va = node - o.in_i_hash; |
| 513 | inodes.push_back(inode_va); |
| 514 | |
| 515 | // Next link is at hlist_node offset 0. |
| 516 | VAddr next = 0; |
| 517 | if (!kva_read_pod(eng, node, next) || next == node) break; |
no test coverage detected