简体   繁体   English

Arm64 Linux页面表遍历

[英]Arm64 Linux Page Table Walk

Currently I'm developing some research-related programs and I need to find the pte of some specific addresses. 目前,我正在开发一些与研究相关的程序,我需要找到一些特定地址的pte My development environment is Juno r1 board (CPUs are A53 and A57 ) and it's running arm64 Linux kernel. 我的开发环境是Juno r1板(CPU是A53和A57),它正在运行arm64 Linux内核。

I use some typical page table walk codes like this: 我使用一些典型的页表遍历代码,如下所示:

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;
}

However, when the program checks the pte for the address(0xffffffc0008b2000), it always returns an empty pmd . 但是,当程序检查pte地址(0xffffffc0008b2000)时,它总是返回一个空的pmd

My guess is that I got the wrong pgd in the first step. 我的猜测是第一步我得到了错误的pgd I saw Tims Notes said that using current->mm only could get the pgd of TTBR0 (user space pgd ) while the address I checked is a kernel space address so I should try to get the pgd of TTBR1 . 我看到Tims Notes表示,仅使用current->mm可以获取pgd of TTBR0 (用户空间pgd ),而我检查的地址是内核空间地址,因此我应该尝试获取pgd of TTBR1

So my question is: If I want to get the pte of a kernel space address, can I use current->mm to get the pgd ? 所以我的问题是:如果我想获取内核空间地址的pte ,可以使用current->mm来获取pgd吗?

If I can't, is there anything else I could try instead? 如果我做不到,还有什么我可以尝试的吗?

Any suggestion is welcome! 任何建议都欢迎! Thank you. 谢谢。

Simon 西蒙

I think the problem you are having is that you are passing the struct mm_struct * pointer of the current process. 我认为您遇到的问题是您正在传递当前进程的struct mm_struct *指针。 But the address you are passing if from the kernel virtual address space. 但是您要传递的地址(如果来自内核虚拟地址空间)。 You need to pass the mm pointer to the init process ( &init_mm ): 您需要将mm指针传递给init进程( &init_mm ):

pgd = pgd_offset(&init_mm, address);

I think the rest should be fine, but I haven't tested it. 我认为其余的应该没问题,但我尚未对其进行测试。 You can also look at how it is done in the kernel in the file arch/arm64/mm/dump.c 您也可以在arch/arm64/mm/dump.c文件中查看如何在内核中完成此操作。

I finally solved the problem. 我终于解决了问题。

Actually, my code is correct. 实际上,我的代码是正确的。 The only part I missed is a page table entry check. 我唯一错过的部分是页表项检查。

According to the page table design of ARMv8 , ARM uses 4 levels page table for 4kb granule case. 根据ARMv8页表设计 ,ARM对4kb颗粒大小写使用4级页表。 Each level (level 0-3 defined in the link) is implemented as pgd, pud, pmd, and ptep in Linux code. 每个级别(在链接中定义的0-3级)在Linux代码中分别实现为pgd, pud, pmd, and ptep

In the ARM architecture, each level can be either block entry or the table entry (see the AArch64 Descriptor Format Section in the link). 在ARM体系结构中,每个级别都可以是块条目或表条目(请参阅链接中的AArch64描述符格式部分 )。

If the memory address belongs to a 4kb table entry, then it needs to be traced down till level 3 entry ( ptep ). 如果内存地址属于一个4kb的表条目,则需要向下追溯直到第3级条目( ptep )。 However, for the address belongs to a larger chunk, the corresponding table entry may save in the pgd, pud, or pmd level. 但是,由于该地址属于更大的块,因此相应的表条目可能保存在pgd, pud, or pmd级别中。

By checking the last 2 bits of the entry in each level, you know it's block entry or not and you only keep tracing down for the block entry. 通过检查每个级别中条目的最后2位,您可以知道它是否是块条目,并且仅继续跟踪该块条目。

Here is how to improve my code above: 这是上面的代码的改进方法:

Retrieving the descriptor based on the page table pointer desc = *pgd and then checking the last 2 bits of the descriptor. 根据页表指针desc = *pgd检索描述符,然后检查描述符的最后2位。

If the descriptor is a block entry (0x01) then you need to extract the lower level entry as my code shows above. 如果描述符是一个块条目(0x01),则需要提取较低级别的条目,如上面的代码所示。 If you already get the table entry (0x11) at any level, then you can stop there and translate the VA to PA based on the descriptor desc you just get. 如果您已经获得任何级别的表条目(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;
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM