flat assembler
Message board for the users of flat assembler.
![]() Goto page 1, 2 Next |
Author |
|
Tomasz Grysztar 20 Jan 2020, 10:57
You need to fill page tables with enough entries so that all memory that you need is mapped, including your own code.
The most basic, classic 80386 paging divides the 32-bit address into three parts: two 10-bit ones, and one 12-bit one. Let's say that you use linear address 0x000B8004. To find out what physical address corresponds to it, CPU first takes the uppermost 10 bits (in this case they are all 0) and this 10-bit number (with 10 bits you have range from 0 to 1023) is the number of entry in page directory. ( All entries in page directory or page table are 32-bit, so each one is 4 bytes and entire table has size 4*1024, which is nicely the same as the length of a single page. The physical address of page that contains this directory goes into CR3. ) So you take the entry in page directory that has number 0 (the very first one), and it gives you the address of the first page table. ( Every entry in page directories and tables gives an address of the page, and pages are 4096 bytes each, so their addresses always have the lowest 12 bits zeroed. Since this zeroing of the low bits is implied, the actual low 12 bits of each entry can be used for other purposes, so there are additional bits fields there. ) Now, CPU takes the second 10 bits of the original address, in this example they are 0x0B8. This is the number of one of the 1024 entries in the page table that was found by looking up the page directory. This entry defines the address of the actual page of memory that the original linear address points to. Now all that is left is to take the low 12 bits of the original address, 0x004, and add them to the address of page, as these bits are simply an offset within the page. Therefore, if you'd like to have linear address 0x000B8004 correspond to physical address 0x000B8004, you need to set up the first page table so that the entry number 0x0B8 points to page at 0x000B8000. As you see, this means that to get your sample working, you have to fill at least the first page table with correct entries. The simplest example of setting up paging that I have turns out to be my long mode example. So I took it and I removed all the long-mode-related stuff, to make this minimized demonstration. It fills the first 256 entries in the first page table with 1:1 linear-to-physical address mapping: Code: format binary as 'bin' ORG 7C00h USE16 cli lgdt [cs:GDTR] ; load GDT register mov eax,cr0 ; switch to protected mode or al,1 mov cr0,eax jmp CODE_SELECTOR:pm_start NULL_SELECTOR = 0 DATA_SELECTOR = 1 shl 3 ; flat data selector (ring 0) CODE_SELECTOR = 2 shl 3 ; 32-bit code selector (ring 0) GDTR: ; Global Descriptors Table Register dw 3*8-1 ; limit of GDT (size minus one) dq GDT ; linear address of GDT GDT rw 4 ; null desciptor dw 0FFFFh,0,9200h,08Fh ; flat data desciptor dw 0FFFFh,0,9A00h,0CFh ; 32-bit code desciptor USE32 pm_start: mov eax,DATA_SELECTOR ; load 4 GB data descriptor mov ds,ax ; to all data segment registers mov es,ax mov fs,ax mov gs,ax mov ss,ax mov edi,70000h mov ecx,1000h shr 2 xor eax,eax rep stosd ; clear the page directory mov dword [70000h],71000h + 111b ; first entry in page directory -> first page table mov edi,71000h ; address of first page table mov eax,0 + 111b mov ecx,256 ; number of pages to map (1 MB) make_page_entries: stosd add eax,1000h loop make_page_entries mov eax,70000h mov cr3,eax ; load page directory address mov eax,cr0 or eax,1 shl 31 mov cr0,eax ; enable paging jmp CODE_SELECTOR:paged_start paged_start: mov eax,'P G ' mov [0B8000h],eax jmp paged_start |
|||
![]() |
|
Fulgurance 21 Jan 2020, 10:14
If i understand good, paging is processor feature to translate linear address into physical address.
This feature split RAM and physical memory into many pages with fixed size, and extend all memory. This memory is for data and code i think ? But i don't understand your explanation about divides 32 bits address into three parts, what is the purpose if i would like to start with address 0x0, for example ? I just set all bits to 0 ? I don't understand very well. How can i put 32 bits code into page ? |
|||
![]() |
|
Tomasz Grysztar 21 Jan 2020, 10:52
Fulgurance wrote: But i don't understand your explanation about divides 32 bits address into three parts, what is the purpose if i would like to start with address 0x0, for example ? I just set all bits to 0 ? A 32-bit linear address has then 20 bits that are "the number of the page" and 12 bits (range 0-4095) that are the offset within the page. Linear address 0x00001234: page number 0x00001 (20-bit number), offset within page: 0x234 (12-bit number). In 32-bit addressing space we have 2^20 such pages. This is over a million. If we wanted to make a lookup table that for each page would tell us the actual physical address, it would be quite large - a few megabytes of memory. If you wanted to map some code in one part of memory, data in the other, and video/devices in yet another (perhaps above 0x80000000 address) you would end up with this huge table, but most of the space within it would be wasted for unused areas that do not need mapping anyway. And at the time 80386 was developed "few megabytes" was usually all the RAM PCs had. Therefore, instead of a single huge table we have a tree-like structure, with main directory that contains entries for sub-directories (or page tables) only for the addresses that we need. All other places in directory can be zeroed, which means that there is no sub-table needed there and all addresses within that range are considered invalid (not present). And thus, 20-bit number of page is split into two 10-bit numbers. A single page table has 1024 entries (numbered with 10 bits), and since each entry is 4 bytes long, entire table occupies a single 4096-byte page. It all works nicely together, the numbers have been chosen this way. So the area of memory mapped by a single page table is 1024*4096 = 4 megabytes. Therefore each entry in the page directory corresponds to 4 megabytes of mapped linear memory. The conversion the goes as such: we split the address into ( 10 bits : 10 bits: 12 bits ). The upper 10 bits tell us which 4 MB block of memory is it, and this is the number of entry in page directory and this entry points to the physical address of the page table. ( In my example I map just initial 1 MB of memory addresses, so I need only a single entry in page directory. All other entries are zeroed and therefore they do not have page tables. ) The page table maps 4 MB of memory and the middle 10 bits of the linear address tell us the number of page within that 4M block. The corresponding entry in page table give the physical address of the page. And then the final 12 bits are the address of a byte within that page. So, for example, let's say that we want to map video memory at 0x80001000 (so accessing [0x80001000] would end up accessing physical memory at 0xB8000). First, we calculate which of 4M areas is it, taking the upper 10 bits of 0x80001000: 0x200. So in page directory we need to set up the entry at 0x200*4 and put the physical address of page table there: Code: [page_directory_address + 0x200*4] = page_table_address + 111b ; access bits Then then middle 10 bits of 0x80001000 tell us the number of 4K page within that 4M block: 0x001. In page table we need the entry that points to the physical address that this page should correspond to: Code: [page_table_address + 0x001*4] = 0xB8000 + 111b ; access bits And now a working demonstration: Code: format binary as 'bin' ORG 7C00h USE16 cli lgdt [cs:GDTR] ; load GDT register mov eax,cr0 ; switch to protected mode or al,1 mov cr0,eax jmp CODE_SELECTOR:pm_start NULL_SELECTOR = 0 DATA_SELECTOR = 1 shl 3 ; flat data selector (ring 0) CODE_SELECTOR = 2 shl 3 ; 32-bit code selector (ring 0) GDTR: ; Global Descriptors Table Register dw 3*8-1 ; limit of GDT (size minus one) dq GDT ; linear address of GDT GDT rw 4 ; null desciptor dw 0FFFFh,0,9200h,08Fh ; flat data desciptor dw 0FFFFh,0,9A00h,0CFh ; 32-bit code desciptor USE32 pm_start: mov eax,DATA_SELECTOR ; load 4 GB data descriptor mov ds,ax ; to all data segment registers mov es,ax mov fs,ax mov gs,ax mov ss,ax mov edi,70000h mov ecx,1000h shr 2 xor eax,eax rep stosd ; clear the page directory mov dword [70000h],71000h + 111b ; first entry in page directory -> first page table mov edi,71000h ; address of first page table mov eax,0 + 111b mov ecx,256 ; number of pages to map (1 MB) make_page_entries: stosd add eax,1000h loop make_page_entries mov eax,70000h mov cr3,eax ; load page directory address mov eax,cr0 or eax,1 shl 31 mov cr0,eax ; enable paging ; Added example: map video memory at 80001000h page_directory_address = 70000h page_table_address = 72000h mov dword [page_directory_address + 0x200*4], page_table_address + 111b mov dword [page_table_address + 0x001*4], 0B8000h + 111b jmp CODE_SELECTOR:paged_start paged_start: mov [80001002h],dword '? ! ' jmp paged_start |
|||
![]() |
|
Fulgurance 21 Jan 2020, 16:57
Sorry, but i have problem with page directory structure. I have read page directory point to many table page, but when i look wiki or os dev, the example just show this structure point to just ONE page table.... I don't understand how i can make directory with MANY entry....
At this link: https://wiki.osdev.org/Paging In directory page section, you have just one address. But if this directory point to multiple address, how can i do this ? Do i need to update CR3 when i switch to other page ? |
|||
![]() |
|
Tomasz Grysztar 21 Jan 2020, 17:10
In my second example above you already have a page directory that contains two entries. One at 0, which points to the page table that maps first 4M of memory (but I mapped only 1M there, by filling only 256 entries out of 1024), and then - added in the second example - an entry at 0x200*4, which points to the page table that maps range of addresses from 0x80000000 to 0x803FFFFF (and filled a single entry in that page table to map a single page at 0x80001000 to physical address 0xB8000 for the purpose of demonstration). Other entries in page directory remain zeroed (they were cleared with "rep stosd" instruction), which means that these memory ranges are considered not present (accessing them raises the "page fault" exception).
For every additional 4M block you'd need, you'd fill another entry in the page directory, pointing to a page table mapping 1024 4K pages within that 4M block. Every page table itself is a single page (and you need to somehow allocate them). Every page is 4K bytes, every page table maps 1024 pages - 4M of memory. Page directory has 1024 entries for page tables, each one mapping 4M of memory, in total 4G - entire 32-bit addressing space. |
|||
![]() |
|
Fulgurance 21 Jan 2020, 18:53
Okay. Sorry,your code at many line is difficult for me, i don't know some instruction you use.
To explain simply, if i have 2 entry in my page directory, i have two 32 bits entries they following himself, is it that ? Can you just give me simple array page directory example, not with code plz. (same structure you use with your GDT it's more simple to read please) |
|||
![]() |
|
Tomasz Grysztar 21 Jan 2020, 19:31
Fulgurance wrote: Can you just give me simple array page directory example, not with code plz. (same structure you use with your GDT it's more simple to read please) I tested it from DOS using this loader (the same that comes with my long mode examples) to get the code at correct address and run it: Code: org 100h cli push 0 pop es mov di,7C00h mov si,basecode mov cx,basecode_length rep movsb jmp 0:7C00h basecode file 'EXAMPLE.BIN' basecode_length = $ - basecode Code: format binary as 'bin' ORG 7C00h USE16 cli lgdt [cs:GDTR] mov eax,cr0 ; switch to protected mode or al,1 mov cr0,eax jmp CODE_SELECTOR:pm_start NULL_SELECTOR = 0 DATA_SELECTOR = 1 shl 3 ; flat data selector (ring 0) CODE_SELECTOR = 2 shl 3 ; 32-bit code selector (ring 0) GDTR: ; Global Descriptors Table Register dw 3*8-1 ; limit of GDT (size minus one) dq GDT ; linear address of GDT GDT rw 4 ; null desciptor dw 0FFFFh,0,9200h,08Fh ; flat data desciptor dw 0FFFFh,0,9A00h,0CFh ; 32-bit code desciptor USE32 pm_start: mov eax,DATA_SELECTOR ; load 4 GB data descriptor mov ds,ax ; to all data segment registers mov es,ax mov fs,ax mov gs,ax mov ss,ax mov eax,PageDirectory mov cr3,eax ; load page directory address mov eax,cr0 or eax,1 shl 31 mov cr0,eax ; enable paging jmp CODE_SELECTOR:paged_start align 1000h PageDirectory: dd PageTable1 + 111b ; entry 0 (addresses 0x00000000 - 0x003FFFFF) dd 1FFh dup 0 ; 511 zeroed entries (area 0x00400000 - 0x7FFFFFFF not present) dd PageTable2 + 111b ; entry 0x200 (addresses 0x80000000 - 0x803FFFFF) dd 1FFh dup 0 ; 511 zeroed entries (area 0x80400000 - 0xFFFFFFFF not present) align 1000h ; not really needed if the above table constructed properly PageTable1: ; addresses 0x00000000 - 0x003FFFFF dd 0 + 111b ; map page at 0x0000000 to physical page 0x00000000 repeat 255 dd %*1000h + 111b ; similarly map 255 next pages (entire first 1M) to the same physical addresses end repeat dd 300h dup 0 ; remaining entries zeroed (memory not present) align 1000h ; not really needed if the above table constructed properly PageTable2: ; addresses 0x80000000 - 0x803FFFFF dd 0 ; page at 0x80000000 not present dd 0B8000h + 111b ; page at 0x80001000 mapped to 0x000B8000 (video memory) dd 3FEh dup 0 ; remaining entries zeroed (memory not present) paged_start: mov [80001002h],dword '? ! ' jmp paged_start |
|||
![]() |
|
Fulgurance 28 Jan 2020, 15:40
I ask you if i don't understand something else.
I have question, is it mandatory to use paging in protected mode and long mode ? Or is it better to use other system coded by the OS developer ? Why split into 4kb or 4Mb, why not whit other size for example ? Are there any OS use different memory usage without paging ? First question, at this line: Code: PageDirectory: dd PageTable1 + 111b ; entry 0 (addresses 0x00000000 - 0x003FFFFF) dd 1FFh dup 0 ; 511 zeroed entries (area 0x00400000 - 0x7FFFFFFF not present) dd PageTable2 + 111b ; entry 0x200 (addresses 0x80000000 - 0x803FFFFF) dd 1FFh dup 0 Is it 1024 4 byte entries of page directory ? If i understand good, you have just 2 entries without zero entries ? But why this two entries ? And what do you do when you write: Code: dd PageTable1 + 111b Is it your Page directory entry ? When you do this addition, do you write 111b at the lower byte of the 32 bits value ? (20 bits address + settings ?) What do exactly align 100h directive ? This directive write 4096 empty byte ? I don't understand good the purpose of align memory. And, it's possible i have wrong, but if i understand good, your page table don't size 4Kib, no ? |
|||
![]() |
|
Tomasz Grysztar 28 Jan 2020, 17:34
Fulgurance wrote: I have question, is it mandatory to use paging in protected mode and long mode ? Fulgurance wrote: Why split into 4kb or 4Mb, why not whit other size for example ? Fulgurance wrote: Are there any OS use different memory usage without paging ? Fulgurance wrote: Is it 1024 4 byte entries of page directory ? If i understand good, you have just 2 entries without zero entries ? But why this two entries ? Fulgurance wrote: And what do you do when you write: Fulgurance wrote: What do exactly align 100h directive ? This directive write 4096 empty byte ? I don't understand good the purpose of align memory. Fulgurance wrote: And, it's possible i have wrong, but if i understand good, your page table don't size 4Kib, no ? |
|||
![]() |
|
Fulgurance 28 Jan 2020, 21:24
If i understand, if i set page to 4MiB size, in protected mode, i can address maximum of 4 GiB?
(1024 x 1024 x 4MiB (max page size) ) You mention it's possible to rearange pages. But is it possible for program to allow access to many pages ? For example if code need more than 4MiB (it's so big yes) Now i have understand how to make all paging entries, my last question: How can i allocate one or many pages for a program ? After activate paging, where is kernel in memory ? |
|||
![]() |
|
Tomasz Grysztar 29 Jan 2020, 19:28
Fulgurance wrote: If i understand, if i set page to 4MiB size, in protected mode, i can address maximum of 4 GiB? Fulgurance wrote: You mention it's possible to rearange pages. But is it possible for program to allow access to many pages ? For example if code need more than 4MiB (it's so big yes) With standard 4K pages you need 1024 page tables to map entire memory. Note that it requires 4M of memory just for page mapping structures (plus 4K for page directory, which you always need). Fulgurance wrote: Now i have understand how to make all paging entries, my last question: Fulgurance wrote: After activate paging, where is kernel in memory ? |
|||
![]() |
|
Fulgurance 30 Jan 2020, 18:22
Oh okay, i see.
Is it better to use 4k pages or 4M ? Or just different ? If i understand good, pages are virtual ? It's just process for processor to translate address ? And extend RAM to disk ? Are there any space where i mustn't to write into memory when i activate paging, or i can write all of 4G memory ? Are there any zone where for example graphic card use memory ? (for example, into 0xB8000 ?) |
|||
![]() |
|
Tomasz Grysztar 30 Jan 2020, 20:16
Fulgurance wrote: Is it better to use 4k pages or 4M ? Or just different ? Fulgurance wrote: If i understand good, pages are virtual ? It's just process for processor to translate address ? And extend RAM to disk ? Fulgurance wrote: Are there any space where i mustn't to write into memory when i activate paging, or i can write all of 4G memory ? Are there any zone where for example graphic card use memory ? (for example, into 0xB8000 ?) In the linear addressing space ("virtual" addresses) you have a full control over everything. As in my example, where I map video memory to be accessible at address 80001000h. When you design your own OS, you can reserve any address ranges for any purposes you want. |
|||
![]() |
|
revolution 30 Jan 2020, 20:29
One fun thing to try with paging is to map all the addresses to a single page of RAM. It plays havoc with caching, of course, since the x86 uses VA for cache access. Write to one address and read the value back from a different address. And the really tricky part is to how to get all the page tables and the directory page, plus the program code all in the same physical 4kB RAM section.
|
|||
![]() |
|
Fulgurance 01 Feb 2020, 18:43
Okay, thanks
![]() And why you have right access for page table AND pages ? Rights access for pages are not sufficient ? |
|||
![]() |
|
revolution 01 Feb 2020, 19:01
Fulgurance wrote: And why you have right access for page table AND pages ? Rights access for pages are not sufficient ? Your OS might not need to be secure, but most general OSes need to be to be useful. |
|||
![]() |
|
Fulgurance 01 Feb 2020, 22:48
Oh okay, Thanks.
Question again ![]() About the P bit. I think it's to indicate if page is mapped into RAM or into internal memory ? (I'm not sure, but i suppose it's possible to use something similar to swap, no ?) How this feature work ? |
|||
![]() |
|
revolution 02 Feb 2020, 02:00
If a page isn't present then when it is accessed it causes an exception. When the OS is processing the exception it can swap another page to disc and map the accessed page to the newly freed section of RAM.
|
|||
![]() |
|
Fulgurance 05 Feb 2020, 21:56
I have tried to apply paging into my test code for test paging.
But when i launch it into virtualbox, my code don't work, virtual machine is stopped by virtualbox. I think something is bad, but what ? (code work without paging) Code: format binary as "img" use16 org 0x7C00 xor al,al xor dl,dl int 0x13 mov ax,0x100 mov es,ax mov ah,0x2 mov al,0x12 xor ch,ch mov cl,0x2 xor dh,dh xor dl,dl xor bx,bx int 0x13 jmp Kernel db 510-($-$$) dup 0x90 dw 0xAA55 use16 org 0x1000 Kernel: cli lgdt [GDTR] mov eax,PageDirectory mov cr3,eax mov eax,cr0 or eax,0x80000001 mov cr0,eax ;mov eax, cr4 ;or eax, 0x00000010 ;mov cr4, eax jmp 0x8:ProtectedMode use32 ProtectedMode: .Initialize: mov ax,0x10 mov ds,ax mov es,ax mov fs,ax mov gs,ax mov es,ax mov ss,ax .ShowText: mov byte [0x0000000],'*' mov byte [0x0000001],00000010b mov byte [0x0000002],' ' mov byte [0x0000003],00000111b mov byte [0x0000004],'M' mov byte [0x0000005],00000111b mov byte [0x0000006],'a' mov byte [0x0000007],00000111b mov byte [0x0000008],'i' mov byte [0x0000009],00000111b mov byte [0x000000A],'n' mov byte [0x000000B],00000111b .Main: hlt jmp .Main GDT: db 0, 0, 0, 0, 0, 0, 0, 0 GDTCS: db 0xFF, 0xFF, 0x0, 0x0, 0x0, 10011011b, 11011111b, 0x0 GDTDS: db 0xFF, 0xFF, 0x0, 0x0, 0x0, 10010011b, 11011111b, 0x0 GDTEND: GDTR: dw GDTEND-GDT-1 dd GDT PageDirectory: dd PageTable + 111b dd 1023 dup 0x0 PageTable: dd 0x0B8000 + 111b dd 1023 dup 0x0 db 347 dup 0x90 |
|||
![]() |
|
Goto page 1, 2 Next < Last Thread | Next Thread > |
Forum Rules:
|
Copyright © 1999-2025, Tomasz Grysztar. Also on GitHub, YouTube.
Website powered by rwasa.