4

目前我正在开发一些与研究相关的程序,我需要找到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说 using current->mmonly 可以获取pgd of TTBR0(用户空间pgd),而我检查的地址是内核空间地址,所以我应该尝试获取pgd of TTBR1.

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

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

欢迎任何建议!谢谢你。

西蒙

4

2 回答 2

4

我终于解决了这个问题。

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

根据ARMv8 的页表设计,ARM 使用 4 级页表来处理 4kb 的粒度情况。每个级别(链接中定义的级别 0-3)都按照pgd, pud, pmd, and ptepLinux 代码实现。

在 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;
}
于 2017-07-05T21:13:59.243 回答
1

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

pgd = pgd_offset(&init_mm, address);

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

于 2017-06-23T23:16:47.803 回答