Page 1 of 1

Can't clear page tables on exec()

Posted: Sun Apr 02, 2017 7:41 pm
by codyd51
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: Select all

	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) {}

Re: Can't clear page tables on exec()

Posted: Sun Apr 02, 2017 11:49 pm
by iansjack
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.

Re: Can't clear page tables on exec()

Posted: Mon Apr 03, 2017 12:20 am
by codyd51
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: Select all

		//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

Re: Can't clear page tables on exec()

Posted: Mon Apr 03, 2017 12:48 am
by iansjack
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).

Re: Can't clear page tables on exec()

Posted: Mon Apr 03, 2017 9:06 am
by codyd51
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?

Re: Can't clear page tables on exec()

Posted: Mon Apr 03, 2017 9:52 am
by mallard
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?