0

I'm trying to emulate the function lookup_address(http://lxr.free-electrons.com/source/arch/x86/mm/pageattr.c#L373) for arm platform (and just for kernel pagetables).

The point is I'm getting the address of swapper_pg_dir from TTBR1, and so far that's working. I checked it with gdb:

(gdb) file vmlinux
Reading symbols from vmlinux...done.
(gdb) p init_mm.pgd
$1 = (pgd_t *) 0xc0004000
(gdb)

and the code from my module:

 static pgd_t *get_global_pgd (void)

{
        pgd_t *pgd;
        unsigned int ttb_reg;

        asm volatile (
        "       mrc     p15, 0, %0, c2, c0, 1"
        : "=r" (ttb_reg));

        ttb_reg &= TTBR_MASK;
        pgd = __va (ttb_reg);
        pr_info ("get_global_pgd: %p\n", pgd);

        return pgd;
}

and the output:

bananapi kernel: [ 5665.358139] mod: get_global_pgd: c0004000

So far, this is matching. Now I'm computing the addr of the right pgd, doing:

pgd = get_global_pgd() + pgd_index (addr);

And since (addr >> 21) is 0x600, I get 0xc0007000. Then I continue with:

pud = pud_offset (pgd, addr);
pr_info ("pud: 0x%0x - %p\n",pud_val (*pud), pud);
pmd = pmd_offset (pud, addr);
pr_info ("pmd: 0x%0x - %p\n", pmd_val (*pmd), pmd);
if (pmd == NULL || pmd_none (*pmd)) {
        return NULL;
}
return pte_offset_kernel (pmd, addr);

output:

bananapi kernel: [ 5665.390391] mod: pud: 0x4001140e - c0007000
bananapi kernel: [ 5665.401603] mod: pmd: 0x4001140e - c0007000
bananapi kernel: [ 5665.423838] mod: pte: 0xe59f119c - c0011020

The problem is that the pte I get seems not to be fine, because the attributes of the pte don't match. Let's take an address from /proc/kallsyms:

c0008054 t __create_page_tables

I can read it with gdb:

(gdb) x/2x 0xc0008054
0xc0008054 <__create_page_tables>:  0xe2884901  0xe1a00004
(gdb)

But the pte I get from this address, doesn't have the present flag:

I'm checking it with (this pte is the one I got from my lookup_address):

ret = pte_present (*pte);
pr_info ("pte_present: %d\n", ret);

The pte_present is 0 (which checks L_PTE_PRESENT flag defined include/asm/pgtable-2level.h), but shoudln't be 0 as long as I can read from it in GDB.

I've tested with some other addresses, for instance: 0xc0035618:

c0035618 T __put_task_struct

And for this one the L_PTE_PRESENT big is set.

I'm pretty sure I'm missing something, or I got it wrong.

Thanks in advance!

artless noise
  • 21,212
  • 6
  • 68
  • 105
leberus
  • 51
  • 1
  • 6
  • The `L_*` attributes are Linux-specific ones, not necessarily hardware ones. There are plenty of questions on here already about how ARM Linux [maintains a second set of shadow pagetables for those](http://lxr.free-electrons.com/source/arch/arm/include/asm/pgtable-2level.h). – Notlikethat Oct 06 '16 at 11:07
  • yes, I know that. Actually I think ARM doesn't provide those bits, so linux had to workaround it by adding these bits. But Linux is checking these bits when a page fault is being triggered, so I think they are the right ones to check. Thanks – leberus Oct 06 '16 at 12:26
  • Possible duplicate of [Page table entry (PTE) descriptor in Linux kernel for ARM](http://stackoverflow.com/questions/16909101/page-table-entry-pte-descriptor-in-linux-kernel-for-arm) – artless noise Oct 06 '16 at 16:32
  • Search: [SO on ARM+MMU+Linux-kernel](http://stackoverflow.com/questions/tagged/arm+linux-kernel+mmu?sort=votes&pageSize=50). There are many relevant question and answers for you. You can not do a **hardware lookup** and expect to find the **Linux** table! You need to subtract/add 2k to get from the hardware to linux and vice-versa. The info is in [this answer](http://stackoverflow.com/questions/32943129/how-does-arm-linux-emulate-the-dirty-accessed-and-file-bits-of-a-pte) as well as *Notlikethat* referenced Linux header. – artless noise Oct 06 '16 at 16:34
  • Hi @artlessnoise, thanks to point me there. I've read all your explanation, but I still have some questions. It looks like that by walking the page, I get the HW-pte, so I should sub 2048 (PTE_HWTABLE_OFF value) in order to get the Linux-pte, so then I could check the bits L_PTE_*. Am I right? thanks – leberus Oct 10 '16 at 12:41

1 Answers1

0

I've read all the pointed links, but I'm afraig I still don't get the whole picture. I'll try to explain what I understood so far:

From include/asm/pgtable-2level.h , it looks like a page stores:

  • 0 - pte1_linux
  • 1024 - pte2_linux
  • 2048 - pte1_hw
  • 3072 - pte2_hw

Actually I also saw this in early_pte_alloc function, which allocates 4096bytes for the pte:

     static pte_t * __init early_pte_alloc(pmd_t *pmd, unsigned long addr, unsigned long prot)

     {
        if (pmd_none(*pmd)) {
                pte_t *pte = early_alloc(PTE_HWTABLE_OFF +  PTE_HWTABLE_SIZE);
                __pmd_populate(pmd, __pa(pte), prot);
        }
        BUG_ON(pmd_bad(*pmd));
        return pte_offset_kernel(pmd, addr);
}

Then, in __pmd_populate we take the phys address of the memory previously allocated, we add 2048 (for hw pte), and we OR it with the protection flag (which in case to be from a kernel_domain should be PMD_TYPE_TABLE.

static inline void __pmd_populate(pmd_t *pmdp, phys_addr_t pte,
                                  pmdval_t prot)
{
        pmdval_t pmdval = (pte + PTE_HWTABLE_OFF) | prot;
        pmdp[0] = __pmd(pmdval);
#ifndef CONFIG_ARM_LPAE
        pmdp[1] = __pmd(pmdval + 256 * sizeof(pte_t));
#endif
        flush_pmd_entry(pmdp);
}

So far is clear. Given this information, a walking page should be something like:

  • pmd_offset_k (addr)
  • pud_offset (pgd, addr)
  • pmd_offset (pud, addr)
  • pte_offset_kernel (pmd, addr)

pte_offset_kernel gives the virtual address of the pmd_val stored in pmd. (it also ANDs the value with PHYS_MASK and PAGE_MASK), and adds the pte_index (addr). At this point I should have the value of the virtual addres of the linux_pte_0 (because the previous ANDs with PAGE_MASK brought me to the top of the page).

So I think at this point I should be able to check the L_PTE_* flags.

Am I wrong?

Thanks in advance

leberus
  • 51
  • 1
  • 6