changing code origin of a function

Question about which tools to use, bugs, the best way to implement a function, etc should go here. Don't forget to see if your question is answered in the wiki first! When in doubt post here.
Post Reply
User avatar
yemista
Member
Member
Posts: 299
Joined: Fri Dec 26, 2008 12:31 pm
Location: Boston
Contact:

changing code origin of a function

Post 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.
User avatar
Combuster
Member
Member
Posts: 9301
Joined: Wed Oct 18, 2006 3:45 am
Libera.chat IRC: [com]buster
Location: On the balcony, where I can actually keep 1½m distance
Contact:

Re: changing code origin of a function

Post 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.
"Certainly avoid yourself. He is a newbie and might not realize it. You'll hate his code deeply a few years down the road." - Sortie
[ My OS ] [ VDisk/SFS ]
User avatar
yemista
Member
Member
Posts: 299
Joined: Fri Dec 26, 2008 12:31 pm
Location: Boston
Contact:

Re: changing code origin of a function

Post 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.
User avatar
Firestryke31
Member
Member
Posts: 550
Joined: Sat Nov 29, 2008 1:07 pm
Location: Throw a dart at central Texas
Contact:

Re: changing code origin of a function

Post 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.
Owner of Fawkes Software.
Wierd Al wrote: You think your Commodore 64 is really neato,
What kind of chip you got in there, a Dorito?
User avatar
NickJohnson
Member
Member
Posts: 1249
Joined: Tue Mar 24, 2009 8:11 pm
Location: Sunnyvale, California

Re: changing code origin of a function

Post 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();
User avatar
yemista
Member
Member
Posts: 299
Joined: Fri Dec 26, 2008 12:31 pm
Location: Boston
Contact:

Re: changing code origin of a function

Post 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.
cyr1x
Member
Member
Posts: 207
Joined: Tue Aug 21, 2007 1:41 am
Location: Germany

Re: changing code origin of a function

Post by cyr1x »

To clear the CR4 flag you don't need to disable Paging.
User avatar
Owen
Member
Member
Posts: 1700
Joined: Fri Jun 13, 2008 3:21 pm
Location: Cambridge, United Kingdom
Contact:

Re: changing code origin of a function

Post 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.
User avatar
NickJohnson
Member
Member
Posts: 1249
Joined: Tue Mar 24, 2009 8:11 pm
Location: Sunnyvale, California

Re: changing code origin of a function

Post 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 :lol: ).
User avatar
yemista
Member
Member
Posts: 299
Joined: Fri Dec 26, 2008 12:31 pm
Location: Boston
Contact:

Re: changing code origin of a function

Post 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]
Post Reply