OSDev.org

The Place to Start for Operating System Developers
It is currently Tue Apr 16, 2024 9:13 am

All times are UTC - 6 hours




Post new topic Reply to topic  [ 6 posts ] 
Author Message
 Post subject: Can't clear page tables on exec()
PostPosted: Sun Apr 02, 2017 7:41 pm 
Offline
Member
Member

Joined: Fri May 20, 2016 2:29 pm
Posts: 77
Location: Paris, France
Hello,

My paging implementation is largely based on James Molloy's tutorials, and I'm trying to figure out how to clear all page tables of a page directory created from clone_directory() upon exec().

clone_directory()'s implementation can be seen here: http://www.jamesmolloy.co.uk/tutorial_h ... aging.html

I've tried as follows. I'm immediately trying to wipe all page tables in the parent after a fork() for testing purposes; once this works I'll incorporate it into the exec() implementation. The problem I'm having is if I try to clear all page tables, I immediately triple fault. This is avoided if I don't clear any kernel tables, but that leaves me with the same result as clone_directory(), and defeats the purpose in the first place.

Code:
   int pid = sys_fork();
   //block child as we wipe its page directory
   //strictly for prototyping
   block_task(task_with_pid(pid), ZOMBIE);
   if (!pid) {
      //asm stub which places arg in ebx and calls int 0x80 (syscall vector)
      sys__exit(0xdeadbeef);
   }
   else {
      task_t* child = task_with_pid(pid);
      uint32_t eip_addr = child->eip;
      page_directory_t* old = child->page_dir;

      //for each page table in page directory
      for (int i = 0; i < 1024; i++) {
         page_table_t* tab = old->tables[i];

         //find page table containing this address
         uint32_t instruction_table_idx = (eip_addr / 0x1000) / 1024;
         if (i == instruction_table_idx) {
            //skip clearing page table containing code we want to execute when the child runs
            continue;
         }
         
         //for each page in page table
         for (int j = 0; j < 1024; j++) {
            //skip unassigned pages
            if (!tab->pages[j].frame) continue;

            //many crashes if this is removed
            //don't remove any pages which were linked directly from the kernel
            extern page_directory_t* kernel_directory;
            if (tab->pages[j].frame == kernel_directory->tables[i]->pages[j].frame) {
               continue;
            }
            free_frame(&(tab->pages[j]));
            tab->pages[j].present = 0;
         }
      }
      sleep(3000);
      unblock_task(child);
      int stat;
      waitpid(pid, &stat, NULL);
      printf("Child exited with status %x\n", stat);
      while (1) {}

_________________
www.github.com/codyd51/axle.git


Top
 Profile  
 
 Post subject: Re: Can't clear page tables on exec()
PostPosted: Sun Apr 02, 2017 11:49 pm 
Offline
Member
Member
User avatar

Joined: Sat Mar 31, 2012 3:07 am
Posts: 4594
Location: Chichester, UK
You obviously can't clear the entire page table in the child, else your running code no longer exists. You need to leave those pages that contain the kernel code and data untouched.


Top
 Profile  
 
 Post subject: Re: Can't clear page tables on exec()
PostPosted: Mon Apr 03, 2017 12:20 am 
Offline
Member
Member

Joined: Fri May 20, 2016 2:29 pm
Posts: 77
Location: Paris, France
I figured as much. That was the intention of the line with the comment "skip clearing page table containing code we want to execute when the child runs"

I've revised my code to create new page tables for any that were originally linked from the kernel. Then, the data from the frame containing the running code is physically copied from the kernel directory into the newly cleared directory.

Code:
      //go through each page table
      for (int i = 0; i < 1024; i++) {
         page_table_t* tab = old->tables[i];

         //if this page table is linked from kernel, make entirely new page table and replace
         if (tab == kernel_directory->tables[i]) {
            uint32_t new_tab_phys;
            page_table_t* new_tab = (page_table_t*)kmalloc_ap(sizeof(page_table_t), &new_tab_phys);
            memset((uint8_t*)new_tab, 0, sizeof(page_table_t));
            old->tables[i] = new_tab;
            old->tablesPhysical[i] = new_tab_phys | 0x07;
            continue;
         }

         printf("clearing non-kernel table @ idx %d\n", i);
         for (int j = 0; j < 1024; j++) {
            if (!tab->pages[j].frame) continue;

            uint32_t vmem_addr = vmem_from_frame(tab->pages[j].frame);
            printf("clearing %x [%x]\n", vmem_addr, tab->pages[j].frame);
            free_frame(&(tab->pages[j]));
            tab->pages[j].present = 0;
         }
      }

      page_t* eip_page = get_page(eip_addr, 1, old);
      printf("eip_page %x\n", eip_page);
      alloc_frame(eip_page, 1, 1);
      page_t* kernel_eip_page = get_page(eip_addr, 0, kernel_directory);

      printf("copying physical data from kernel frame %x to %x\n", kernel_eip_page->frame * 0x1000, eip_page->frame * 0x1000);

      extern void copy_page_physical(uint32_t page, uint32_t dest);
      copy_page_physical(kernel_eip_page->frame * 0x1000, eip_page->frame * 0x1000);


This, of course, still triple faults

_________________
www.github.com/codyd51/axle.git


Top
 Profile  
 
 Post subject: Re: Can't clear page tables on exec()
PostPosted: Mon Apr 03, 2017 12:48 am 
Offline
Member
Member
User avatar

Joined: Sat Mar 31, 2012 3:07 am
Posts: 4594
Location: Chichester, UK
You don't need to create new entries for the kernel, or copy kernel code, for the new page table. You just use the existing entries. So copy the page table then delete those parts of it that point to user code and data (or only copy the kernel entries in the first place). The kernel entries are the same for all tasks. Normally all you need to copy is the single top-level pointer of the table that points to kernel entries and anything pointing to devices (e.g. the display).


Top
 Profile  
 
 Post subject: Re: Can't clear page tables on exec()
PostPosted: Mon Apr 03, 2017 9:06 am 
Offline
Member
Member

Joined: Fri May 20, 2016 2:29 pm
Posts: 77
Location: Paris, France
iansjack wrote:
You don't need to create new entries for the kernel, or copy kernel code, for the new page table. You just use the existing entries. So copy the page table then delete those parts of it that point to user code and data (or only copy the kernel entries in the first place). The kernel entries are the same for all tasks. Normally all you need to copy is the single top-level pointer of the table that points to kernel entries and anything pointing to devices (e.g. the display).


Ah, I see. Why does every task need kernel code linked in? I was under the impression kernel data should never be accessed directly by other processes; even syscalls just use an interrupt.

I wrote a Mach-O loader, and the Mach-O expects to be loaded at 0x1000 by default. I use a lower-half kernel, so this memory is occupied by kernel space. This is why I was trying to purge all kernel tables from the new process. Is there another way to go about this? Am I expected to offset the Mach-O's load address to some arbitrary unused region?

_________________
www.github.com/codyd51/axle.git


Top
 Profile  
 
 Post subject: Re: Can't clear page tables on exec()
PostPosted: Mon Apr 03, 2017 9:52 am 
Offline
Member
Member

Joined: Tue May 13, 2014 3:02 am
Posts: 280
Location: Private, UK
codyd51 wrote:
Why does every task need kernel code linked in? I was under the impression kernel data should never be accessed directly by other processes; even syscalls just use an interrupt.


Technically, you don't need the entire kernel in every address space. However, your interrupt handlers must be available for interrupts to work. You could have each interrupt handled by a "task gate" with a different TSS and use that to switch to kernel address space, but this is not great for performance and makes passing parameters to syscalls harder (you can't just "use the registers") and you'll need some extra functionality in your kernel to (temporarily?) map data passed by address.

Even with this method, you'd still have to keep the GDT, IDT and all those TSS segments mapped into every address space.

For simplicity and speed, it's easier just to keep the kernel mapped into all address spaces. Bit 2 of both the page table entry and page directory entry is used to make pages inaccessible to userspace (ring 3) code.

codyd51 wrote:
I wrote a Mach-O loader, and the Mach-O expects to be loaded at 0x1000 by default. I use a lower-half kernel, so this memory is occupied by kernel space. This is why I was trying to purge all kernel tables from the new process. Is there another way to go about this? Am I expected to offset the Mach-O's load address to some arbitrary unused region?


I also have a lower-half kernel, but using ELF executables. As far as I can tell, Mach-O has no restrictions on module base address; you'd just set it in the linker script or with a compiler/linker parameter, although I'm not familiar with Mach-O tooling. Are you using GNU ld?

_________________
Image


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 6 posts ] 

All times are UTC - 6 hours


Who is online

Users browsing this forum: Google [Bot], MichaelPetch and 569 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group