Page 1 of 1

Drawing in 800x600x32 BPP

Posted: Fri Jul 07, 2023 10:13 am
by m0NNESY
Hello! I am developing my OS on GRUB + Multiboot 2.
I can change my screen resolution and BPP through the framebuffer tag in the Multiboot 2 header. I can also get information about Multiboot 2, but that's not the point.

When I try to draw a pixel on the screen, it just doesn't draw.
I use this function to draw pixels:

Code: Select all

void set_pixel(uint8_t* framebuffer, int x, int y, uint32_t color, uint32_t pitch, uint32_t pixel_width) {
    unsigned ptr = (x * pixel_width) + (y * pitch);
    uint8_t alpha = (color >> 24) & 255;
    framebuffer[ptr] = color & 255;
    framebuffer[ptr + 1] = (color >> 8) & 255;
    framebuffer[ptr + 2] = (color >> 16) & 255;
    framebuffer[ptr + 3] = alpha;
}
This is how I'm trying to draw a pixel:

Code: Select all

set_pixel((uint8_t*)framebuffer, 10, 10, (uint32_t)0xFFFFFF, (uint32_t)video_info.pitch, (unsigned long)video_info.bpp/8);
video_info is a Multiboot 2 tag structure that I correctly receive.
My framebuffer = 0xFD000000.

Re: Drawing in 800x600x32 BPP

Posted: Wed Jul 12, 2023 6:13 pm
by Octocontrabass
The only thing I see that might be a problem with this code is the type. You didn't make the framebuffer volatile, so the compiler might see that you never read what you've written and optimize the writes away.

There could be a problem somewhere else in your code.

Re: Drawing in 800x600x32 BPP

Posted: Thu Jul 27, 2023 12:27 pm
by MichaelPetch
I think I asked you on another site some time back if you were using paging and whether you had mapped the framebuffer into virtual memory. I believe you said you were using paging. I recommend taking your entire project and putting it on Github including make files, build script, source (everything to reproduce your problem) and provide a link.

Note: Just learned on Stackoverflow that this is also 64-bit code running in long mode so paging must be enabled. The code on Stackoverflow for paging that you gave was:

Code: Select all

setup_page_tables:
    mov eax, page_table_l3
    or eax, 0b11 ; present, writable
    mov [page_table_l4], eax
    
    mov eax, page_table_l2
    or eax, 0b11 ; present, writable
    mov [page_table_l3], eax

    mov ecx, 0 ; counter
.loop:
    mov eax, 0x200000 ; 2MiB
    mul ecx
    or eax, 0b10000011 ; present, writable, huge page
    mov [page_table_l2 + ecx * 8], eax

    inc ecx ; increment counter
    cmp ecx, 512 ; checks if the whole table is mapped
    jne .loop ; if not, continue

    ret

enable_paging:
    ; pass page table location to cpu
    mov eax, page_table_l4
    mov cr3, eax

    ; enable PAE
    mov eax, cr4
    or eax, 1 << 5
    mov cr4, eax

    ; enable long mode
    mov ecx, 0xC0000080
    rdmsr
    or eax, 1 << 8
    wrmsr

    ; enable paging
    mov eax, cr0
    or eax, 1 << 31
    mov cr0, eax

    ret
setup_page_tables is only mapping the first 1GiB of memory (512 * 2MiB) into virtual address space (one to one mapping from virtual to physical). One serious problem is that the frame buffer is at 0xFD000000 which is well above 1GiB (but below 4GiB). Any access to 0xFD000000 memory region would almost certainly cause a page fault since that memory hasn't been mapped. To fix this you'd have to map the frame buffer into memory. It may be just easier to map the entire first 4GiB into virtual memory. I also assume from the way the page tables are set up that you are running your kernel in the lower half of memory (below 1GiB) and are not using a higher-half kernel.

From your other questions it appears you are using QEMU to test. I'd recommend for debugging purposes running QEMU with the -d int -no-reboot -no-shutdown options. '-d int' would allow you to see the exceptions and interrupts thrown and you'd likely find access to frame buffer memory throwing a page fault (v=0e).

I am taking a guess as to how things are in your code (I have to guess with the absence of any additional code), but to map the entire first 4 GiB (one to one virtual to physical) you can change the code to:

Code: Select all

setup_page_tables:
    mov dword [page_table_l4], page_table_l3 + 0b11                        ; present, writable
    mov dword [page_table_l3 + 0*8], (page_table_l2 + 0x1000*0) + 0b11     ; First GiB present, writable
    mov dword [page_table_l3 + 1*8], (page_table_l2 + 0x1000*1) + 0b11     ; Second GiB present, writable
    mov dword [page_table_l3 + 2*8], (page_table_l2 + 0x1000*2) + 0b11     ; Third GiB present, writable
    mov dword [page_table_l3 + 3*8], (page_table_l2 + 0x1000*3) + 0b11     ; Fourth GiB present, writable

    mov ecx, 0 ; counter
.loop:
    mov eax, 0x200000 ; 2MiB
    mul ecx
    or eax, 0b10000011 ; present, writable, huge page
    mov [page_table_l2 + ecx * 8], eax

    inc ecx ; increment counter
    cmp ecx, 512*4 ; checks if the first 4 page directories are mapped
    jne .loop ; if not, continue

    ret
In order for this code to work you will have change code I figure looks something like

Code: Select all

page_table_l2:
    resb 4096
to

Code: Select all

page_table_l2:
    resb 4096*4
. The idea is to create 4 level_2 page tables (page directories) and populate the first 4 entries of the level_3 page table (page directory pointer table). In the code the loop was extended to go to 512*4 instead of 512. The effect is we filled in 4 page directory tables with the one loop.

These changes should identity map (one to one mapping virtual to physical) the first 4GiB, and any framebuffer below 4GiB should be accessible including at 0xFD000000. There may be other issues with the code but I can't tell you because I don't have access to anything beyond what you show.

Side note: If your system supported 1GiB pages this could be simplified with just a Level 4 page table (PML4) and a Level 3 page table (PDPT) if you used the 1GiB paging feature.

Re: Drawing in 800x600x32 BPP

Posted: Thu Jul 27, 2023 7:36 pm
by Octocontrabass
MichaelPetch wrote:Side note: If your system supported 1GiB pages this could be simplified with just a Level 4 page table (PML4) and a Level 3 page table (PDPT) if you used the 1GiB paging feature.
In this case, you probably wouldn't want to use 1GiB pages. Large pages are not allowed to span two or more effective memory types, so you would most likely end up with either undefined behavior (from MTRRs that aren't aligned to 1GiB boundaries) or terrible performance (from avoiding undefined behavior by setting the pages to UC).

Re: Drawing in 800x600x32 BPP

Posted: Tue Aug 01, 2023 5:28 am
by m0NNESY
MichaelPetch wrote:In order for this code to work you will have change code I figure looks something like

Code: Select all

page_table_l2:
    resb 4096
to

Code: Select all

page_table_l2:
    resb 4096*4
.
My full bss section looks like this:

Code: Select all

section .bss
align 4096
page_table_l4:
    resb 4096
page_table_l3:
    resb 4096
page_table_l2:
    resb 4096*4
stack_bottom:
    resb 4096*4
stack_top:
Should I change only l2, or should I change l3 and/or l4?

Re: Drawing in 800x600x32 BPP

Posted: Tue Aug 01, 2023 10:51 am
by MichaelPetch
Just change L2. You need 4 L2 page table (page directories) for each of the 1GiB sections of memory. I could have written it like this:

Code: Select all

page_table_l2:
page_table_l2_0:
    resb 4096
page_table_l2_1:
    resb 4096
page_table_l2_2:
    resb 4096
page_table_l2_3:
    resb 4096
It really is just 4 L2 page tables (Page directories) one after the other in memory or the same as `resb 4096*4`

Re: Drawing in 800x600x32 BPP

Posted: Tue Aug 01, 2023 12:27 pm
by m0NNESY
MichaelPetch wrote:

Code: Select all

setup_page_tables:
    mov dword [page_table_l4], page_table_l3 + 0b11                        ; present, writable
    mov dword [page_table_l3 + 0*8], (page_table_l2 + 0x1000*0) + 0b11     ; First GiB present, writable
    mov dword [page_table_l3 + 1*8], (page_table_l2 + 0x1000*1) + 0b11     ; Second GiB present, writable
    mov dword [page_table_l3 + 2*8], (page_table_l2 + 0x1000*2) + 0b11     ; Third GiB present, writable
    mov dword [page_table_l3 + 3*8], (page_table_l2 + 0x1000*3) + 0b11     ; Fourth GiB present, writable

    mov ecx, 0 ; counter
.loop:
    mov eax, 0x200000 ; 2MiB
    mul ecx
    or eax, 0b10000011 ; present, writable, huge page
    mov [page_table_l2 + ecx * 8], eax

    inc ecx ; increment counter
    cmp ecx, 512*4 ; checks if the first 4 page directories are mapped
    jne .loop ; if not, continue

    ret

Code: Select all

page_table_l2:
    resb 4096*4
I have changed my QEMU startup code and parameters.

After that, my OS started crashing again.

Here are the logs from QEMU:

Code: Select all

check_exception old: 0xffffffff new 0xd
     0: v=0d e=0000 i=0 cpl=0 IP=0010:000000000010029f pc=000000000010029f SP=0018:000000000010b000 env->regs[R_EAX]=0000000000060fb1
EAX=00060fb1 EBX=00000000 ECX=00000005 EDX=2193fbfd
ESI=0010c8c8 EDI=36d76289 EBP=00000000 ESP=0010b000
EIP=0010029f EFL=00200006 [-----P-] CPL=0 II=0 A20=1 SMM=0 HLT=0
ES =0018 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
CS =0010 00000000 ffffffff 00cf9a00 DPL=0 CS32 [-R-]
SS =0018 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
DS =0018 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
FS =0018 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
GS =0018 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
LDT=0000 00000000 0000ffff 00008200 DPL=0 LDT
TR =0000 00000000 0000ffff 00008b00 DPL=0 TSS32-busy
GDT=     00100528 0000000f
IDT=     00000000 00000000
CR0=00000011 CR2=00000000 CR3=00000000 CR4=00000000
DR0=0000000000000000 DR1=0000000000000000 DR2=0000000000000000 DR3=0000000000000000 
DR6=00000000ffff0ff0 DR7=0000000000000400
CCS=80000001 CCD=20000000 CCO=LOGICL
EFER=0000000000000000
check_exception old: 0xd new 0xd
     1: v=08 e=0000 i=0 cpl=0 IP=0010:000000000010029f pc=000000000010029f SP=0018:000000000010b000 env->regs[R_EAX]=0000000000060fb1
EAX=00060fb1 EBX=00000000 ECX=00000005 EDX=2193fbfd
ESI=0010c8c8 EDI=36d76289 EBP=00000000 ESP=0010b000
EIP=0010029f EFL=00200006 [-----P-] CPL=0 II=0 A20=1 SMM=0 HLT=0
ES =0018 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
CS =0010 00000000 ffffffff 00cf9a00 DPL=0 CS32 [-R-]
SS =0018 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
DS =0018 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
FS =0018 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
GS =0018 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
LDT=0000 00000000 0000ffff 00008200 DPL=0 LDT
TR =0000 00000000 0000ffff 00008b00 DPL=0 TSS32-busy
GDT=     00100528 0000000f
IDT=     00000000 00000000
CR0=00000011 CR2=00000000 CR3=00000000 CR4=00000000
DR0=0000000000000000 DR1=0000000000000000 DR2=0000000000000000 DR3=0000000000000000 
DR6=00000000ffff0ff0 DR7=0000000000000400
CCS=80000001 CCD=20000000 CCO=LOGICL
EFER=0000000000000000
check_exception old: 0x8 new 0xd
What can it mean?

Re: Drawing in 800x600x32 BPP

Posted: Tue Aug 01, 2023 1:03 pm
by MichaelPetch
m0NNESY wrote:

Code: Select all

check_exception old: 0xffffffff new 0xd
     0: v=0d e=0000 i=0 cpl=0 IP=0010:000000000010029f pc=000000000010029f SP=0018:000000000010b000 env->regs[R_EAX]=0000000000060fb1
EAX=00060fb1 EBX=00000000 ECX=00000005 EDX=2193fbfd
ESI=0010c8c8 EDI=36d76289 EBP=00000000 ESP=0010b000
EIP=0010029f EFL=00200006 [-----P-] CPL=0 II=0 A20=1 SMM=0 HLT=0
ES =0018 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
CS =0010 00000000 ffffffff 00cf9a00 DPL=0 CS32 [-R-]
SS =0018 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
DS =0018 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
FS =0018 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
GS =0018 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
LDT=0000 00000000 0000ffff 00008200 DPL=0 LDT
TR =0000 00000000 0000ffff 00008b00 DPL=0 TSS32-busy
GDT=     00100528 0000000f
IDT=     00000000 00000000
CR0=00000011 CR2=00000000 CR3=00000000 CR4=00000000
DR0=0000000000000000 DR1=0000000000000000 DR2=0000000000000000 DR3=0000000000000000 
DR6=00000000ffff0ff0 DR7=0000000000000400
CCS=80000001 CCD=20000000 CCO=LOGICL
EFER=0000000000000000
What can it mean?
It could be a lot of things and really difficult to tell from this output. Have you considered just making your entire project available on Github so we can see the whole thing (and build it if necessary). 0: v=0d e=0000 i=0 cpl=0 IP=0010:000000000010029f . v=0d means you got a general protection fault executing an instruction at 0x10029f. Have you checked what instruction is at that memory location (it may hint at what might have caused the failure). I find it peculiar that GDT=00100528 0000000f . A GDT that is 16 bytes long (0xf+1). The null descriptor takes up 8 and that means there is only one other entry. Usually there are at least 3 (or could be more) including the NULL descriptor. Each one taking up 8 bytes each. Your CS and DS selectors of 0x10 and 0x18 suggest you should have more. Did you create your own GDT and if you did, are you sure you set it up correctly and reloaded all the segment registers (DS,ES,GS,FS,SS and CS)? Are you maybe using the GDT that GRUB/Multiboot setup that you shouldn't be relying on as being valid (per the Multiboot specification). Part of me seems to think you may have set up your own GDT/GDTR (because of the address 0x100528 ) and that it has problems. The first time you go to modify any segment selector (DS,ES,GS,FS,SS, and CS) I think there is a good chance it would fault if the selector was a value above >= 0x10.

All I can say is there seems to be things amiss but without seeing ALL your code, narrowing it down for you is incredibly difficult.

I highly recommend using BOCHS and it's debugger at such an early stage to see where things might be a problem. BOCHS will give hints/warnings in the output about things that it sees as unexpected often before a crash. BOCHS has a reasonable debugger suitable for early stage OSDev despite symbolic debugging being very limited.

I assume that from the output IDT=00000000 00000000 you haven't got to a state of making an IDT yet. Because you don't have an IDT pretty much any exception you get will cascade into a v=08 (double fault) and then a triple fault that would normally reboot the machine. You can run without an IDT to start, just expect getting any exception will cause a double fault (v=08) and that would normally cause a triple fault and a reboot.

Edit: Are you still building a 64-bit kernel? If you are, are you using qemu-system-i386 or qemu-system-x86_64 to run the code? The output shows the code is still running in 32-bit protected mode but without paging on. Appears whatever is happening is early on in setting things up.