【问题标题】:Arm64 Linux Page Table WalkArm64 Linux 页表遍历
【发布时间】:2018-11-14 03:43:02
【问题描述】:

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

我使用一些典型的页表遍历代码,如下所示:

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)时,它总是返回一个空的pmd

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

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

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

欢迎提出任何建议!谢谢。

西蒙

【问题讨论】:

  • 编写一个使用TTBCR 并根据目标地址返回TTBR0TTBR1 的例程。这比current->mm 好,但您处理的是物理 ARM PTE 值,而不是 Linux 变体。 TTBR1 用于内核空间(在较新的 Linux 版本 ~3.xx+ 中),因为它在用户空间上下文切换时永远不会改变。注意:Linux armv8 对 TTBR0 使用 EL0,对 TTBR1 使用 EL1。还有物理地址的CP15查询unsigned int pa; asm("\t mcr p15, 0, %0, c7, c8, 2\n" "\t isb\n" "\t mrc p15, 0, %0, c7, c4, 0\n" : "=r" (pa) : "0" (0xffff0000));
  • 如果地址在内核空间,可以使用pgd_offset_k(address)

标签: linux linux-kernel arm arm64 page-tables


【解决方案1】:

我终于解决了这个问题。

其实我的代码是正确的。我唯一错过的部分是页表条目检查。

根据page table design of ARMv8,ARM 使用 4 级页表来处理 4kb 粒度的情况。每个级别(链接中定义的级别 0-3)在 Linux 代码中实现为pgd, pud, pmd, and ptep

在 ARM 架构中,每个级别可以是块条目或表条目(请参阅链接中的 AArch64 描述符格式部分)。

如果内存地址属于一个 4kb 的表项,则需要向下追溯直到第 3 级项(ptep)。但是,对于属于更大块的地址,对应的表项可能保存在pgd, pud, or pmd级别。

通过检查每个级别的条目的最后 2 位,您知道它是否是块条目,您只需要继续追踪块条目。

这里是如何改进我上面的代码:

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

如果描述符是块条目 (0x01),那么您需要提取较低级别的条目,如我的代码所示。 如果您已经获得了任何级别的表条目 (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;
}

【讨论】:

  • 您介意更深入地了解解决方案吗?直接属于 pdg 或 pmd 是什么意思?或者您介意发布修改后的代码吗?
  • @Archmede 嗨,我刚刚更新了我的答案,详细解释了 ARMv8-A 架构中的页表概念。请注意,如果您使用的是 x86 或其他架构,那么情况可能会有所不同。很抱歉,我无法共享代码,因为它与多个任务结合在一起,因此我的代码的当前版本无法帮助您理解 ARM 的页表遍历。请先阅读我在答案中输入的链接,然后再阅读我的答案。希望对您有所帮助。
  • 所写的代码将不再编译。它应该有更多的页表层检查块:现在,在 pgd 和 pud 之间,有 p4d。正式需要此代码,即使真实页表没有 p4d 级别。在实践中,内核会发挥它的魔力让它消失。所以顺序应该是:pgd->p4d->pud->pmd->pte
【解决方案2】:

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

pgd = pgd_offset(&init_mm, address);

我认为其余的应该没问题,但我还没有测试过。也可以在arch/arm64/mm/dump.c文件中查看内核中是如何完成的

【讨论】:

  • 嗨 Jay,实际上从内核的角度来看 current->mm 与我认为的 init_mm 相同。无论如何,谢谢你的帮助。
猜你喜欢
  • 2018-03-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-10-28
  • 1970-01-01
相关资源
最近更新 更多