Skip to content

Commit

Permalink
mm: add generic ptdump
Browse files Browse the repository at this point in the history
Add a generic version of page table dumping that architectures can opt-in
to.

Link: http://lkml.kernel.org/r/[email protected]
Signed-off-by: Steven Price <[email protected]>
Cc: Albert Ou <[email protected]>
Cc: Alexandre Ghiti <[email protected]>
Cc: Andy Lutomirski <[email protected]>
Cc: Ard Biesheuvel <[email protected]>
Cc: Arnd Bergmann <[email protected]>
Cc: Benjamin Herrenschmidt <[email protected]>
Cc: Borislav Petkov <[email protected]>
Cc: Catalin Marinas <[email protected]>
Cc: Christian Borntraeger <[email protected]>
Cc: Dave Hansen <[email protected]>
Cc: David S. Miller <[email protected]>
Cc: Heiko Carstens <[email protected]>
Cc: "H. Peter Anvin" <[email protected]>
Cc: Ingo Molnar <[email protected]>
Cc: James Hogan <[email protected]>
Cc: James Morse <[email protected]>
Cc: Jerome Glisse <[email protected]>
Cc: "Liang, Kan" <[email protected]>
Cc: Mark Rutland <[email protected]>
Cc: Michael Ellerman <[email protected]>
Cc: Paul Burton <[email protected]>
Cc: Paul Mackerras <[email protected]>
Cc: Paul Walmsley <[email protected]>
Cc: Peter Zijlstra <[email protected]>
Cc: Ralf Baechle <[email protected]>
Cc: Russell King <[email protected]>
Cc: Thomas Gleixner <[email protected]>
Cc: Vasily Gorbik <[email protected]>
Cc: Vineet Gupta <[email protected]>
Cc: Will Deacon <[email protected]>
Cc: Zong Li <[email protected]>
Signed-off-by: Andrew Morton <[email protected]>
Signed-off-by: Linus Torvalds <[email protected]>
  • Loading branch information
Steven Price authored and torvalds committed Feb 4, 2020
1 parent c5cfae1 commit 30d621f
Show file tree
Hide file tree
Showing 4 changed files with 182 additions and 0 deletions.
21 changes: 21 additions & 0 deletions include/linux/ptdump.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/* SPDX-License-Identifier: GPL-2.0 */

#ifndef _LINUX_PTDUMP_H
#define _LINUX_PTDUMP_H

#include <linux/mm_types.h>

struct ptdump_range {
unsigned long start;
unsigned long end;
};

struct ptdump_state {
void (*note_page)(struct ptdump_state *st, unsigned long addr,
int level, unsigned long val);
const struct ptdump_range *range;
};

void ptdump_walk_pgd(struct ptdump_state *st, struct mm_struct *mm);

#endif /* _LINUX_PTDUMP_H */
21 changes: 21 additions & 0 deletions mm/Kconfig.debug
Original file line number Diff line number Diff line change
Expand Up @@ -117,3 +117,24 @@ config DEBUG_RODATA_TEST
depends on STRICT_KERNEL_RWX
---help---
This option enables a testcase for the setting rodata read-only.

config GENERIC_PTDUMP
bool

config PTDUMP_CORE
bool

config PTDUMP_DEBUGFS
bool "Export kernel pagetable layout to userspace via debugfs"
depends on DEBUG_KERNEL
depends on DEBUG_FS
depends on GENERIC_PTDUMP
select PTDUMP_CORE
help
Say Y here if you want to show the kernel pagetable layout in a
debugfs file. This information is only useful for kernel developers
who are working in architecture specific areas of the kernel.
It is probably not a good idea to enable this feature in a production
kernel.

If in doubt, say N.
1 change: 1 addition & 0 deletions mm/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -109,3 +109,4 @@ obj-$(CONFIG_ZONE_DEVICE) += memremap.o
obj-$(CONFIG_HMM_MIRROR) += hmm.o
obj-$(CONFIG_MEMFD_CREATE) += memfd.o
obj-$(CONFIG_MAPPING_DIRTY_HELPERS) += mapping_dirty_helpers.o
obj-$(CONFIG_PTDUMP_CORE) += ptdump.o
139 changes: 139 additions & 0 deletions mm/ptdump.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
// SPDX-License-Identifier: GPL-2.0

#include <linux/pagewalk.h>
#include <linux/ptdump.h>
#include <linux/kasan.h>

#ifdef CONFIG_KASAN
/*
* This is an optimization for KASAN=y case. Since all kasan page tables
* eventually point to the kasan_early_shadow_page we could call note_page()
* right away without walking through lower level page tables. This saves
* us dozens of seconds (minutes for 5-level config) while checking for
* W+X mapping or reading kernel_page_tables debugfs file.
*/
static inline int note_kasan_page_table(struct mm_walk *walk,
unsigned long addr)
{
struct ptdump_state *st = walk->private;

st->note_page(st, addr, 5, pte_val(kasan_early_shadow_pte[0]));

walk->action = ACTION_CONTINUE;

return 0;
}
#endif

static int ptdump_pgd_entry(pgd_t *pgd, unsigned long addr,
unsigned long next, struct mm_walk *walk)
{
struct ptdump_state *st = walk->private;
pgd_t val = READ_ONCE(*pgd);

#if CONFIG_PGTABLE_LEVELS > 4 && defined(CONFIG_KASAN)
if (pgd_page(val) == virt_to_page(lm_alias(kasan_early_shadow_p4d)))
return note_kasan_page_table(walk, addr);
#endif

if (pgd_leaf(val))
st->note_page(st, addr, 1, pgd_val(val));

return 0;
}

static int ptdump_p4d_entry(p4d_t *p4d, unsigned long addr,
unsigned long next, struct mm_walk *walk)
{
struct ptdump_state *st = walk->private;
p4d_t val = READ_ONCE(*p4d);

#if CONFIG_PGTABLE_LEVELS > 3 && defined(CONFIG_KASAN)
if (p4d_page(val) == virt_to_page(lm_alias(kasan_early_shadow_pud)))
return note_kasan_page_table(walk, addr);
#endif

if (p4d_leaf(val))
st->note_page(st, addr, 2, p4d_val(val));

return 0;
}

static int ptdump_pud_entry(pud_t *pud, unsigned long addr,
unsigned long next, struct mm_walk *walk)
{
struct ptdump_state *st = walk->private;
pud_t val = READ_ONCE(*pud);

#if CONFIG_PGTABLE_LEVELS > 2 && defined(CONFIG_KASAN)
if (pud_page(val) == virt_to_page(lm_alias(kasan_early_shadow_pmd)))
return note_kasan_page_table(walk, addr);
#endif

if (pud_leaf(val))
st->note_page(st, addr, 3, pud_val(val));

return 0;
}

static int ptdump_pmd_entry(pmd_t *pmd, unsigned long addr,
unsigned long next, struct mm_walk *walk)
{
struct ptdump_state *st = walk->private;
pmd_t val = READ_ONCE(*pmd);

#if defined(CONFIG_KASAN)
if (pmd_page(val) == virt_to_page(lm_alias(kasan_early_shadow_pte)))
return note_kasan_page_table(walk, addr);
#endif

if (pmd_leaf(val))
st->note_page(st, addr, 4, pmd_val(val));

return 0;
}

static int ptdump_pte_entry(pte_t *pte, unsigned long addr,
unsigned long next, struct mm_walk *walk)
{
struct ptdump_state *st = walk->private;

st->note_page(st, addr, 5, pte_val(READ_ONCE(*pte)));

return 0;
}

static int ptdump_hole(unsigned long addr, unsigned long next,
int depth, struct mm_walk *walk)
{
struct ptdump_state *st = walk->private;

st->note_page(st, addr, depth + 1, 0);

return 0;
}

static const struct mm_walk_ops ptdump_ops = {
.pgd_entry = ptdump_pgd_entry,
.p4d_entry = ptdump_p4d_entry,
.pud_entry = ptdump_pud_entry,
.pmd_entry = ptdump_pmd_entry,
.pte_entry = ptdump_pte_entry,
.pte_hole = ptdump_hole,
};

void ptdump_walk_pgd(struct ptdump_state *st, struct mm_struct *mm)
{
const struct ptdump_range *range = st->range;

down_read(&mm->mmap_sem);
while (range->start != range->end) {
walk_page_range_novma(mm, range->start, range->end,
&ptdump_ops, st);
range++;
}
up_read(&mm->mmap_sem);

/* Flush out the last page */
st->note_page(st, 0, 0, 0);
}

0 comments on commit 30d621f

Please sign in to comment.