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;
}
目前我正在开发一些研究相关的程序,需要找到一些特定地址的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;
}