Arm64 Linux 页面 Table 步行

Arm64 Linux Page Table Walk

目前我正在开发一些研究相关的程序,需要找到一些特定地址的pte。我的开发环境是 Juno r1 板(CPU 是 A53 和 A57 )并且它是 运行 arm64 Linux 内核。

我使用一些典型的页面 table 步行代码如下:

int find_physical_pte(void *addr)
{
    pgd_t *pgd;
    pud_t *pud;
    pmd_t *pmd;
    pte_t *ptep;
    unsigned long long address;

    address = (unsigned long long)addr;

    pgd = pgd_offset(current->mm, address);
    printk(KERN_INFO "\npgd is: %p\n", (void *)pgd);
    printk(KERN_INFO "pgd value: %llx\n", *pgd);
    if (pgd_none(*pgd) || pgd_bad(*pgd)) 
        return -1;

    pud = pud_offset(pgd, address);
    printk(KERN_INFO "\npud is: %p\n", (void *)pud);
    printk(KERN_INFO "pud value: %llx\n", (*pud).pgd);
    if (pud_none(*pud) || pud_bad(*pud))
        return -2;

    pmd = pmd_offset(pud, address);
    printk(KERN_INFO "\npmd is: %p\n", (void *)pmd);
    printk(KERN_INFO "pmd value: %llx\n",*pmd);
    if (pmd_none(*pmd) || pmd_bad(*pmd))
        return -3;

    ptep = pte_offset_kernel(pmd, address);
    printk(KERN_INFO "\npte is: %p\n", (void *)ptep);
    printk(KERN_INFO "pte value: %llx\n",*ptep);
    if (!ptep)
        return -4;

    return 1;
}

但是,当程序检查 pte 的地址 (0xffffffc0008b2000) 时,它总是 returns 一个空的 pmd

我的猜测是我第一步走错了pgd。我看到Tims Notes说用current->mm只能得到pgd of TTBR0(用户spacepgd),而我查的地址是内核space地址,所以我应该尝试获取 pgd of TTBR1

所以我的问题是:如果我想获取内核space地址的pte,我可以使用current->mm获取pgd吗?

如果我不能,还有什么我可以尝试的吗?

欢迎任何建议!谢谢。

西蒙

我认为您遇到的问题是您正在传递当前进程的 struct mm_struct * 指针。但是你传递的地址如果来自内核虚拟地址 space。您需要将 mm 指针传递给 init 进程 (&init_mm):

pgd = pgd_offset(&init_mm, address);

我觉得其余的应该没问题,但我没有测试过。您还可以在文件 arch/arm64/mm/dump.c

中查看它在内核中是如何完成的

我终于解决了问题

实际上,我的代码是正确的。我唯一错过的部分是页面 table 条目检查。

根据 page table design of ARMv8,ARM 使用 4 级页面 table 用于 4kb 颗粒情况。每个级别(link 中定义的级别 0-3)在 Linux 代码中实现为 pgd, pud, pmd, and ptep

在 ARM 架构中,每一层都可以是块入口或 table 入口(参见 AArch64 描述符格式部分 在 link 中)。

如果内存地址属于4kb table entry,则需要向下追溯至level 3 entry (ptep)。但是,对于属于更大chunk的地址,对应的table条目可能保存在pgd, pud, or pmd层。

通过检查每个级别中条目的最后 2 位,您可以知道它是否是块条目,您只需继续向下跟踪块条目。

下面是改进我上面的代码的方法:

根据页面table指针desc = *pgd检索描述符,然后检查描述符的最后2位。

如果描述符是块条目 (0x01),那么您需要提取较低级别的条目,如我上面的代码所示。 如果您已经在任何级别获得 table 条目 (0x11),那么您可以停在那里并根据您刚刚获得的描述符 desc 将 VA 转换为 PA。

int find_physical_pte(void *addr)
{
    pgd_t *pgd;
    pud_t *pud;
    pmd_t *pmd;
    pte_t *ptep;
    unsigned long long address;

    address = (unsigned long long)addr;

    pgd = pgd_offset(current->mm, address);
    printk(KERN_INFO "\npgd is: %p\n", (void *)pgd);
    printk(KERN_INFO "pgd value: %llx\n", *pgd);
    if (pgd_none(*pgd) || pgd_bad(*pgd)) 
        return -1;
    //check if (*pgd) is a table entry. Exit here if you get the table entry.

    pud = pud_offset(pgd, address);
    printk(KERN_INFO "\npud is: %p\n", (void *)pud);
    printk(KERN_INFO "pud value: %llx\n", (*pud).pgd);
    if (pud_none(*pud) || pud_bad(*pud))
        return -2;
    //check if (*pud) is a table entry. Exit here if you get the table entry.   

    pmd = pmd_offset(pud, address);
    printk(KERN_INFO "\npmd is: %p\n", (void *)pmd);
    printk(KERN_INFO "pmd value: %llx\n",*pmd);
    if (pmd_none(*pmd) || pmd_bad(*pmd))
        return -3;
    //check if (*pmd) is a table entry. Exit here if you get the table entry.

    ptep = pte_offset_kernel(pmd, address);
    printk(KERN_INFO "\npte is: %p\n", (void *)ptep);
    printk(KERN_INFO "pte value: %llx\n",*ptep);
    if (!ptep)
        return -4;

    return 1;
}