Page 1 of 1
changing code origin of a function
Posted: Thu Jul 02, 2009 9:24 pm
by yemista
Ok, heres a tough one. Im writing a function that disables paging to access all of memory, just like in JamesM tutorial, so it can copy contents of one frame into another.
Code: Select all
copy_frame:
push ebx
pushf ; push flags in case interrups were enabled
cli
mov ebx, [esp+12] ; source
mov ecx, [esp+16] ; dest
mov edx, cr0
and edx, 0x7fffffff
mov cr0, edx ; disable paging.
mov edx, 1024
.loop:
mov eax, [ebx]
mov [ecx], eax
add ebx, 4
add ecx, 4
dec edx
jnz .loop
mov edx, cr0
or edx, 0x80000000
mov cr0, edx ; enable paging
popf
pop ebx
ret
Its pretty much the same thing, but heres the catch. My kernel is mapped to 0xc00100000, and this function is located within the compiled kernel, and it has to be in order to be called within the kernel, but it crashes everything. Ive realized its because after it disables paging, it thinks that the next instruction is at 0xc00100000 + offset, so it cant read it because theres not that much RAM in the system, and it fails. I tried an org directive right after paging is disabled, but nasm wont compile it because it has to look like [org 0x00100000 + label], and thats not a valid format for the directive. Any ideas on how to solve this? I thought of copying the frames without disabling paging, and thats all well and good, but if I get a frame address higher than what the kernel can see, which is 0x00400000, it will page fault. I dont want to make the kernel tables map all of memory, so I need to disable paging in case it tries to copy to a frame high than what is mapped in the kernels directory.
Re: changing code origin of a function
Posted: Fri Jul 03, 2009 1:50 am
by Combuster
So, where did the required identity mapping go? The intel manuals are clear that you should only enable/disable paging from an identity mapped location or the result is undefined.
Bottom line: you can't disable paging when EIP > 0xc0000000. Independent of whatever origin the code is linked to.
Re: changing code origin of a function
Posted: Fri Jul 03, 2009 6:10 am
by yemista
Well I do have identity mapping, but the kernel is linked to run at 0xc0100000 because part of the boot process ensures paging is enabled and the mapping is setup right. So are you saying I would have to link in that one function into the boot code that is mapped at 0x00100000? I think that would fix it, but I was wondering if there was a way to do it with the org directive, even if its a hack, so I could avoid having a special case for a function in the linker script. This is the only function I can foresee needing such special treatment.
Re: changing code origin of a function
Posted: Fri Jul 03, 2009 10:02 am
by Firestryke31
If you're running in regular Protected Mode there's always the segment trick: create a code (and maybe data) segment that starts at (physical - 0xC0100000) then make a identity mapped page, then do a long jump to the segment and page, then disable paging.
Warning: I just woke up so some of that might technically be wrong, mainly the segment start calculation.
Re: changing code origin of a function
Posted: Fri Jul 03, 2009 10:09 am
by NickJohnson
You can identity map the kernel to 0x100000 and then use an assembly function to jump to the address a function should be in lower memory. When the function returns, it goes back to higher memory. Here's the spot in my kernel where this happens (it disables paging temporarily to disable the 4mb page bit):
Code: Select all
global redo_paging
redo_paging :
mov eax, [esp+4]
lea ecx, [.lower-0xF8000000]
jmp ecx ; Jump to lower memory copy of kernel
.lower:
mov ecx, cr0
and ecx, 0x7FFFFFFF ; Disable paging
mov cr0, ecx
mov ecx, cr4
and ecx, 0xFFFFFFEF ; Disable 4 MB pages
mov cr4, ecx
mov cr3, eax ; Load new page directory
mov ecx, cr0
or ecx, 0x80000000 ; Re-enable paging
mov cr0, ecx
ret
Edit:
Alternatively, you can change the virtual memory position of an ELF segment to be around 0x100000. You would need to set the linker script to try and load a new segment (let's call it ".ltext") to virtual and physical location 0x100000. Functions in that segment could only be called if paging is off or the kernel is mapped to lower memory. To declare a C function in that segment, use the following syntax:
Code: Select all
__attribute__ ((section(".ltext")))
int lower_memory_function();
Re: changing code origin of a function
Posted: Fri Jul 03, 2009 10:37 am
by yemista
the segment trick will work, but i want to avoid so much overhead for a small function. Nick, your idea looks promising, and Ill give it a try when I get home, but are you sure this will work? The reason I think it might not is that my code disabled paging halfway through, so the function entered correctly, but failed right at the next instruction, and it failed because it loaded eip with the instruction of the next address, which was (0xc0100000 + some_offset),
so I dont know if lea will change things because I dont really know too well how instructions are assembled. Worse comes to worse ill have that one function linked in differently, which I know will work, but its just an ugly way to do it, and I can forsee some problems down the line with it.
Re: changing code origin of a function
Posted: Fri Jul 03, 2009 10:51 am
by cyr1x
To clear the CR4 flag you don't need to disable Paging.
Re: changing code origin of a function
Posted: Fri Jul 03, 2009 3:24 pm
by Owen
Why disable paging to copy the memory? It's going to be slower than just mapping the pages in, copying them, unmapping them, and doing a pair of invlpgs. It adds the complication and mess of identity mapping. But worse, it cause the processor to dump the TLB's contents, which will slow down all memory accesses when you re-enable paging.
It's also not portable to Long Mode.
Re: changing code origin of a function
Posted: Fri Jul 03, 2009 3:50 pm
by NickJohnson
cyr1x wrote:To clear the CR4 flag you don't need to disable Paging.
I know that now. I just pulled that piece of code out of my git repo's history just as an example - I don't use it anymore, but it worked then.
Owen wrote:Why disable paging to copy the memory? It's going to be slower than just mapping the pages in, copying them, unmapping them, and doing a pair of invlpgs. It adds the complication and mess of identity mapping. But worse, it cause the processor to dump the TLB's contents, which will slow down all memory accesses when you re-enable paging.
It's also not portable to Long Mode.
That's also definitely true. I'm guessing the OP is at least loosely following Jamesm's tutorial, which uses that weird method. Here's an example of a more "proper" technique for copying the contents of frames:
Code: Select all
for (i = 0; i < 976; i++) if (src->virt[i]) {
for (j = 0; j < 1024; j++) if (src->virt[i][j] & PF_PRES) {
p_alloc(dest, ((i << 10) + j) << 12, src->virt[i][j] & PF_MASK);
page_set(&kmap, 0xFFFF0000, page_fmt( src->virt[i][j], (PF_PRES | PF_RW)));
page_set(&kmap, 0xFFFF1000, page_fmt(dest->virt[i][j], (PF_PRES | PF_RW)));
asm volatile ("invlpg 0xFFFF0000");
asm volatile ("invlpg 0xFFFF1000");
memcpy( (void*) 0xFFFF1000, (void*) 0xFFFF0000, 0x1000);
}
}
A lot of the stuff in there is kernel-dependent, but the design (at the point this code was written; this is also from my history) is similar to the one in JamesM's tutorial. The two frames are mapped at 0xFFFF0000 and 0xFFFF1000, and the two "invlpg" instructions make sure the TLB is synced for those two addresses (you need to do that), and the data is just copied, while paging is still enabled. It is much faster and cache friendly than the other method, as well as simpler and more portable (or as portable as you can get with inline asm
).
Re: changing code origin of a function
Posted: Fri Jul 03, 2009 7:01 pm
by yemista
The reason I like JamesM method of frame copying is that it lets you copy to frames that you arnt necessarily able to see. For example, fork will always be called from kernel mode, now im not at the point of system calls yet, but either two things will happen, you will switch to the kernels directory, or keep the current directory with kernel pages mapped in, either way fork will clone the directory. Now when you clone the directory, you setup tables for the new process, and within those tables you setup valid frame addresses for the pages. Depending on how you are allocating frames, you probably dont know or care which frames you are using. So how are you to copy the contents of one frame into another, if the frame you are copying to, happens to be a physical address not mapped anywhere in the working directory? You cant, and thats why JamesM method is useful. Of course disabling paging has left me with a predicament at the moment, that being running the function with paging disabled, but this does seem to be the proper course to take.
[edit] Also, I realize this is a non-issue if you identity map the whole address range, but Im trying to avoid that. This may be a hobby kernel, but its still going to be as efficient as possible.
[/edit]