OSDev.org

The Place to Start for Operating System Developers
It is currently Thu Mar 28, 2024 3:46 pm

All times are UTC - 6 hours




Post new topic Reply to topic  [ 10 posts ] 
Author Message
 Post subject: Vesa bitmap drawing not working after interrupts
PostPosted: Thu Apr 19, 2018 6:50 am 
Offline
Member
Member

Joined: Thu Apr 19, 2018 5:31 am
Posts: 27
Hello Everyone,

I would like to start of saying that I am, like a lot of people here, a beginner in developing operating systems. So perhaps this is a really stupid question, but I have not found a similar issue online. This is the problem:
Right now I have a method that can draw a bmp bitmap to the screen, this works perfectly when I run it at the kernel boot (so before all the initializations). The image is drawn using Vesa that is set/configured by GRUB. Like I said everything works before the initialization, but after I enable interrupts the image is not drawn anymore and the os crashes. The interrupts are enabled using:
Code:
asm("sti");

The weird thing is though that it works fine on Virtualbox and Qemu but crashes on bochs and real hardware.
Any help would be highly appreciated :D

_________________
Currently working on https://github.com/Remco123/CactusOS


Top
 Profile  
 
 Post subject: Re: Vesa bitmap drawing not working after interrupts
PostPosted: Thu Apr 19, 2018 7:05 am 
Offline
Member
Member

Joined: Thu Jul 05, 2007 8:58 am
Posts: 223
Have you set up interrupt handlers properly? Once you enable interrupts hardware can, for any number of reasons, fire a hardware interrupt, and unless you are handling that properly things will crash.


Top
 Profile  
 
 Post subject: Re: Vesa bitmap drawing not working after interrupts
PostPosted: Thu Apr 19, 2018 7:13 am 
Offline
Member
Member

Joined: Thu Apr 19, 2018 5:31 am
Posts: 27
Yes I think I have because I can use the keyboard/mouse interrupts and I have a working network drivers that needs interrupts to work. Is it possible that Vesa calls a interrupt, because everything works fine when I use the bochs graphics adaptor/vga for graphics.

EDIT: If it helps I get the following error in bochs:
Quote:
check_cs(0x0acc): not a valid code segment !

_________________
Currently working on https://github.com/Remco123/CactusOS


Top
 Profile  
 
 Post subject: Re: Vesa bitmap drawing not working after interrupts
PostPosted: Thu Apr 19, 2018 7:28 am 
Offline
Member
Member
User avatar

Joined: Fri Oct 27, 2006 9:42 am
Posts: 1925
Location: Athens, GA, USA
The fact that it works under a hypervisor, but not when emulated or on un-virtualized hardware, sounds like a clue, but I am not sure what it points to.

Please post a link to your offsite repo (assuming you have one; if you don't, I recommend that you drop everything and set one up ASAP), and/or post the relevant code sections (and remember to use CODE tags around the code snippets if if you do post any). That will let us go over the potential sources of the problem.

_________________
Rev. First Speaker Schol-R-LEA;2 LCF ELF JAM POEE KoR KCO PPWMTF
Ordo OS Project
Lisp programmers tend to seem very odd to outsiders, just like anyone else who has had a religious experience they can't quite explain to others.


Top
 Profile  
 
 Post subject: Re: Vesa bitmap drawing not working after interrupts
PostPosted: Thu Apr 19, 2018 7:51 am 
Offline
Member
Member

Joined: Thu Apr 19, 2018 5:31 am
Posts: 27
I did not have a repository yet so I created one.
https://github.com/Remco123/OSDev-Simple
The os itself is mostly created with the following tutorial because I had no idea where to start.
https://github.com/AlgorithMan-de/wyoos
And this is where it goes wrong:
https://github.com/Remco123/OSDev-Simple/blob/master/src/kernel.cpp#L196

_________________
Currently working on https://github.com/Remco123/CactusOS


Top
 Profile  
 
 Post subject: Re: Vesa bitmap drawing not working after interrupts
PostPosted: Thu Apr 19, 2018 10:54 am 
Offline
Member
Member
User avatar

Joined: Fri Oct 27, 2006 9:42 am
Posts: 1925
Location: Athens, GA, USA
Thank you. I've taken a quick look at the code already, and while there are a few things I might have done differently (e.g., using the c'tor initialization list syntax in more places rather than explicit assignment, and maybe the new uniform initialization syntax for commonly defaulted member values like the nullSegmentSelector of your GDT class), I haven't seen any glaring errors.

I'll take a closer look shortly, and I expect others will as well.

EDIT: OK, I missed that a lot of this was taken from the WYOOS tutorial. I am not really familiar with it, but someone else here might be.

_________________
Rev. First Speaker Schol-R-LEA;2 LCF ELF JAM POEE KoR KCO PPWMTF
Ordo OS Project
Lisp programmers tend to seem very odd to outsiders, just like anyone else who has had a religious experience they can't quite explain to others.


Top
 Profile  
 
 Post subject: Re: Vesa bitmap drawing not working after interrupts
PostPosted: Sat Apr 21, 2018 1:24 am 
Offline
Member
Member

Joined: Fri Aug 19, 2016 10:28 pm
Posts: 360
I made suggestion my last post, that it may be the interruptDescriptorTable alignment, but since you have tested placing the vesa code before the activation and it works, the alignment shouldn't be the issue. So, forget about it.


Top
 Profile  
 
 Post subject: Re: Vesa bitmap drawing not working after interrupts
PostPosted: Sat Apr 21, 2018 9:45 am 
Offline
Member
Member

Joined: Fri Aug 26, 2016 1:41 pm
Posts: 671
I tossed it in BOCHS and one observation I had was that the GDT is defined with Data Segment that is limited to the first 64MB. That is done in this code:
Code:
GlobalDescriptorTable::GlobalDescriptorTable()
    : nullSegmentSelector(0, 0, 0),
        unusedSegmentSelector(0, 0, 0),
        codeSegmentSelector(0, 64*1024*1024, 0x9A),
        dataSegmentSelector(0, 64*1024*1024, 0x92)
It appears that the Frame Buffer is beyond the 64MB. What happens if you modify the dataSegmentSelector so its range is the entire 4gb address space with:
Code:
GlobalDescriptorTable::GlobalDescriptorTable()
    : nullSegmentSelector(0, 0, 0),
        unusedSegmentSelector(0, 0, 0),
        codeSegmentSelector(0, 64*1024*1024, 0x9A),
        dataSegmentSelector(0, 0xFFFFFFFF, 0x92)

As for why your code works if you place it before interrupt activation? It is because the author of the tutorial has done something incorrectly. It seems he decided to model his GDT after the one GRUB uses rather than do the correct thing. He shouldn't be relying on CS, DS, ES, FS, GS having the values he thinks GRUB is using (GRUB can change the GDT it uses, so there are no guarantees). After he does his LGDT instruction he doesn't reload these segment registers from his own GDT. He's still using GRUB's base, limits, and access rights through the cached descriptors. GRUB's GDT per the spec creates code and data selectors for the entire 4gb address space. So in your code that works (when placed before the interrupt activation) you are using 4gb flat memory descriptors that allow the framebuffer address to be accessible since it is within the 4gb address space. Note: the framebuffer address is usually an address >64MB

When you move your rendering code after interrupt activation your interrupt handler will fire at some point while trying to render your bitmap (likely before anything on screen gets updated). During the first interrupt you push all the segment registers (ES, FS, DS, GS) and then restore them with POPs. The problem here is the first time your interrupt handler exits the POP %DS will reload %DS with the descriptor that the OS author created in his GDT. That will limit the address space to 64MB. When the interrupt returns at some point you will access the frame buffer which is now outside the segment limits of DS and it will cause a fault. The end result is likely nothing from your bitmap will ever be displayed. Note: some virtual environment may check the limits before accessing memory and some do not. You may find despite the 64MB limits in your OS, some virtual machines will blindly allow you to exceed the limits. This speeds up emulators as an extra limit check doesn't have to be done on each memory access. QEMU may work if run without the -enable-kvm and fail with it. Bochs will always check the memory limits so will fail and warn on the console.

I would say that the code that calls LGDT should properly handle setting the CS,DS,ES,FS,GS segment registers explicitly after issuing LGDT. Why he limited the data segment to 64MB is something you'd have to ask him. I looked at his tutorial a couple years ago and saw only the first few videos. He may have an explanation as to why he set a 64MB limit. I'd say use the full 4gb address space as shown in the code change at the beginning of this answer.

A fix to the LGDT concerns I raised could be just to create an assembly routine that sets the data selectors (including the stack) and code segment selector after issuing an LGDT instruction. Add this to one of the assembly files like interruptstubs.s (or create a new assembly file):
Code:
.global load_gdt
load_gdt:
    mov 4(%esp), %edx   # EDX is 1st argument - GDT record pointer
    mov 8(%esp), %eax   # EAX is 2nd argument - Data Selector
    lgdt (%edx)         # Load GDT Register with GDT record at pointer passed as 1st argument
    mov %eax, %ds       # Reload all the data descriptors with Data selector (2nd argument)
    mov %eax, %es
    mov %eax, %gs
    mov %eax, %fs
    mov %eax, %ss

    pushl 12(%esp)      # Create FAR pointer on stack using Code selector (3rd argument)
    pushl $.setcs       # Offset of FAR JMP will be setcs label below
    ljmp *(%esp)        # Do the FAR JMP to next instruction to set CS with Code selector, and
                        #    set the EIP (instruction pointer) to offset of setcs
.setcs:
    add $8, %esp        # Restore stack (remove 2 DWORD values we put on stack to create FAR Pointer)
    ret
Then modify gdt.cpp to be something like:
Code:
extern "C" void load_gdt(uint8_t *gdt_ptr, uint32_t data_sel, uint32_t code_sel);

GlobalDescriptorTable::GlobalDescriptorTable()
    : nullSegmentSelector(0, 0, 0),
        unusedSegmentSelector(0, 0, 0),
        codeSegmentSelector(0, 64*1024*1024, 0x9A),
        dataSegmentSelector(0, 0xFFFFFFFF, 0x92)
{
    uint32_t i[2];
    i[1] = (uint32_t)this;
    i[0] = sizeof(GlobalDescriptorTable) << 16;
    load_gdt((((uint8_t *) i)+2), DataSegmentSelector(), CodeSegmentSelector());
}
If you are intent on using inline assembly it is possible to convert the external assembly function. This should work:
Code:
GlobalDescriptorTable::GlobalDescriptorTable()
    : nullSegmentSelector(0, 0, 0),
        unusedSegmentSelector(0, 0, 0),
        codeSegmentSelector(0, 64*1024*1024, 0x9A),
        dataSegmentSelector(0, 0xFFFFFFFF, 0x92)
{
    uint32_t i[2];
    i[1] = (uint32_t)this;
    i[0] = sizeof(GlobalDescriptorTable) << 16;
    asm volatile("lgdt %[gdtr]\n\t"
                 "mov %[datasel], %%ds\n\t"
                 "mov %[datasel], %%es\n\t"
                 "mov %[datasel], %%gs\n\t"
                 "mov %[datasel], %%fs\n\t"
                 "mov %[datasel], %%ss\n\t"
                 "pushl %[codesel]\n\t"
                 "pushl $1f\n\t"
                 "ljmp *(%%esp)\n"
                 "1:\n\t"
                 "add $8, %%esp"
                 :
                 :[gdtr]"m"(*(((uint8_t *) i)+2)),
                  [codesel]"g"((uint32_t)CodeSegmentSelector()),
                  [datasel]"r"((uint32_t)DataSegmentSelector())
                 : "memory");
}
This ensures that the selectors are reloaded based on the descriptors in your own GDT and not using GRUB's GDT. You can now eliminate the unusedSegmentSelector variable altogether. I don't think the original author of the tutorial understood how to properly reload all the segment registers (including CS).


Last edited by MichaelPetch on Sat Apr 21, 2018 9:23 pm, edited 5 times in total.

Top
 Profile  
 
 Post subject: Re: Vesa bitmap drawing not working after interrupts
PostPosted: Sat Apr 21, 2018 4:53 pm 
Offline
Member
Member

Joined: Fri Aug 26, 2016 1:41 pm
Posts: 671
Not related to your problem here, but something I noticed. There are 2 types of exceptions. Ones where the CPU pushes an error code on the stack after pushing CS:EIP and ones that don't. External interrupts don't have an error code passed. I noticed in your code that you had this:
Code:
.macro HandleException num
.global _ZN4myos21hardwarecommunication16InterruptManager19HandleException\num\()Ev
_ZN4myos21hardwarecommunication16InterruptManager19HandleException\num\()Ev:
    movb $\num, (interruptnumber)
    jmp int_bottom
.endm
The problem is that this won't necessarily work with exceptions that push an error code. Before doing an IRET you'll need to add 4 to ESP to skip past the error code. What people usually do is push a value of 0 for those exceptions that don't have the processor push an error code, and do nothing special for those that do. External Interrupts would also have a fake error code (you can set it to 0). If you do this then in all cases there is an error code on the stack (whether real or fake) and we simply do add $4, %esp before the IRET for all cases. As well you should be using CLD to set forward direction for string instructions as this is required by the 32-bit System V ABI. I've created a new macro that handles interrupts that require error code (see OSDev Exceptions Wiki). I push a 0 for the error code for external interrupts and fix up up the main interrupt routine accordingly.
Code:
.set IRQ_BASE, 0x20

.section .text

.extern _ZN4myos21hardwarecommunication16InterruptManager15HandleInterruptEhj


.macro HandleException num
.global _ZN4myos21hardwarecommunication16InterruptManager19HandleException\num\()Ev
_ZN4myos21hardwarecommunication16InterruptManager19HandleException\num\()Ev:
    movb $\num, (interruptnumber)
    pushl $0
    jmp int_bottom
.endm

.macro HandleExceptionErrCode num
.global _ZN4myos21hardwarecommunication16InterruptManager19HandleException\num\()Ev
_ZN4myos21hardwarecommunication16InterruptManager19HandleException\num\()Ev:
    movb $\num, (interruptnumber)
    jmp int_bottom
.endm


.macro HandleInterruptRequest num
.global _ZN4myos21hardwarecommunication16InterruptManager26HandleInterruptRequest\num\()Ev
_ZN4myos21hardwarecommunication16InterruptManager26HandleInterruptRequest\num\()Ev:
    movb $\num + IRQ_BASE, (interruptnumber)
    pushl $0
    jmp int_bottom
.endm


HandleException        0x00
HandleException        0x01
HandleException        0x02
HandleException        0x03
HandleException        0x04
HandleException        0x05
HandleException        0x06
HandleException        0x07
HandleExceptionErrCode 0x08
HandleException        0x09
HandleExceptionErrCode 0x0A
HandleExceptionErrCode 0x0B
HandleExceptionErrCode 0x0C
HandleExceptionErrCode 0x0D
HandleExceptionErrCode 0x0E
HandleException        0x0F
HandleException        0x10
HandleExceptionErrCode 0x11
HandleException        0x12
HandleException        0x13

HandleInterruptRequest 0x00
HandleInterruptRequest 0x01
HandleInterruptRequest 0x02
HandleInterruptRequest 0x03
HandleInterruptRequest 0x04
HandleInterruptRequest 0x05
HandleInterruptRequest 0x06
HandleInterruptRequest 0x07
HandleInterruptRequest 0x08
HandleInterruptRequest 0x09
HandleInterruptRequest 0x0A
HandleInterruptRequest 0x0B
HandleInterruptRequest 0x0C
HandleInterruptRequest 0x0D
HandleInterruptRequest 0x0E
HandleInterruptRequest 0x0F
HandleInterruptRequest 0x31

int_bottom:

    # register sichern
    pusha
    pushl %ds
    pushl %es
    pushl %fs
    pushl %gs

    # ring 0 segment register laden
    #mov $0x10, %eax
    #mov %eax, %eds
    #mov %eax, %ees

    # C++ Handler aufrufen
    pushl %esp
    push (interruptnumber)
    cld
    call _ZN4myos21hardwarecommunication16InterruptManager15HandleInterruptEhj
    mov %eax, %esp # den stack wechseln

    # register laden
    pop %gs
    pop %fs
    pop %es
    pop %ds
    popa
    add $4, %esp             # Remove error code
.global _ZN4myos21hardwarecommunication16InterruptManager15InterruptIgnoreEv
_ZN4myos21hardwarecommunication16InterruptManager15InterruptIgnoreEv:

    iret


.data
    interruptnumber: .byte 0


Top
 Profile  
 
 Post subject: Re: Vesa bitmap drawing not working after interrupts
PostPosted: Sun Apr 22, 2018 3:48 am 
Offline
Member
Member

Joined: Thu Apr 19, 2018 5:31 am
Posts: 27
Thank you MichaelPetch for your great answer, now everything is working as it should when I draw a image to the screen, all I had to do is using your code for the GDT Constructor.
I will definitely look into changing the GDT as you said, and the interrupt controller needs to be changed to.
I really appreciate the help you guys gave me and the fact that you gave me a lot of theory helps a lot because I am still learning, it is a bit disappointing though that the tutorial is not completely correct because it is one of the most in-dept tutorials on the web so it will be a starting point for many beginners.

_________________
Currently working on https://github.com/Remco123/CactusOS


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 10 posts ] 

All times are UTC - 6 hours


Who is online

Users browsing this forum: Bing [Bot], Google [Bot] and 69 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group