Page 1 of 2

Is there a way to make a manually-defined io device in QEMU?

Posted: Sun Oct 22, 2023 12:20 pm
by SomeGuyWithAKeyboard
I need help finding a way to debug some really annoying memory management bugs. I have my memory management almost working but strings still overwrite the first character of the next string in certain situations. The bochs breakpoint and memory view feature just isn't good enough to be helpful. For several reasons. What I wish I could do is emulate a UART serial out and every time memory is allocated and deallocated, I wish I could output "memory allocation of size 20 at position 0x502525" for example. I bet I could find the bug right away if I could do that.

Currently my only ways of debugging consist of the bochs debugger or viewing what gets outputted onto the screen (and annoyingly enough, I can't have both screen output and debugging at the same time in bochs). That's it. I really need a way to view larger quantities of information output to a text file or serial prompt, I have to be able to enable or disable this on command while the system is running and I have to have a way of viewing what's on the screen while debugging features are active. It would be nice if there was already a built-in way of defining a manual io device with a python script or something. If not, figuring out how to modify the source code of either qemu or bochs to do this would be a difficult but I think necessary task to try to undertake.

Does anyone have any better ideas?

Re: Is there a way to make a manually-defined io device in Q

Posted: Sun Oct 22, 2023 12:40 pm
by iansjack
Why not use gdb in conjunction with qemu?

Re: Is there a way to make a manually-defined io device in Q

Posted: Sun Oct 22, 2023 1:13 pm
by SomeGuyWithAKeyboard
iansjack wrote:Why not use gdb in conjunction with qemu?
Huh, I didn't know that would work. It seems to be able to pick up breakpoints in the qemu bios while outputting video at the same time which is a step in the right direction. I haven't yet figured out how to start qemu with gdb and have it load my disk.bin image but maybe if I keep digging I'll find it. Normally I launch my system with "qemu-system-i386 disk.bin". Intuition suggests "gdb qemu-system-i386 disk.bin" should work but it doesn't seem to be loading the disk.bin boot image. Trying to do whatever the documentation is suggesting also doesn't seem to work, possibly because disk.bin is not a linux kernel image. I'll keep digging though.

edit: oh never mind. if I omit the first step and just do "target remote localhost:1234" it starts up. Now hopefully I can use this to output the information I need

Re: Is there a way to make a manually-defined io device in Q

Posted: Sun Oct 22, 2023 4:04 pm
by Octocontrabass
If you still want debugging output separate from GDB, QEMU supports redirecting the serial port to various locations including stdio. And if you don't want to bother setting up a serial port, QEMU also supports the Bochs debug console (port 0xE9) device, which can be redirected to the same locations.

Re: Is there a way to make a manually-defined io device in Q

Posted: Sun Oct 22, 2023 6:49 pm
by SomeGuyWithAKeyboard
Yeah I'm definitely going to need some actual serial output, breakpoints just aren't enough. I can't really use any libraries. My system isn't far along enough to be compatible with all but the simplest of outside programs. If there's no other way, I'll put a wdc65c51 on an isa card if I have to and then go to real hardware viewing the output through an arduino for when I need serious debugging capability because that's something I am confident I can get working on my own, it's just a lot of work. I'm still fairly certain there's a software-only way to do this. I haven't been able to get any serial output on port 3F8 or port E9 yet. I've tried the obvious thing of trying to write bytes directly to the port which doesn't do anything but all my attempts of accessing registers, setting up a serial connection and getting the output haven't worked yet either. I'm sure getting the program code right is only half the story, I probably have to do special stuff in qemu to make it cooperate as well.

Re: Is there a way to make a manually-defined io device in Q

Posted: Sun Oct 22, 2023 8:13 pm
by Octocontrabass
SomeGuyWithAKeyboard wrote:I've tried the obvious thing of trying to write bytes directly to the port which doesn't do anything
That's the only thing you need in your code to use port 0xE9, so it should do something. What device did you pick for the "-debugcon" option when you started QEMU?

Re: Is there a way to make a manually-defined io device in Q

Posted: Sun Oct 22, 2023 11:35 pm
by iansjack
Have you tried using watchpoints to catch when a particular expression or memory location changes?

Re: Is there a way to make a manually-defined io device in Q

Posted: Mon Oct 23, 2023 11:16 am
by nullplan
SomeGuyWithAKeyboard wrote:What I wish I could do is emulate a UART serial out and every time memory is allocated and deallocated, I wish I could output "memory allocation of size 20 at position 0x502525" for example. I bet I could find the bug right away if I could do that.
You literally can. Just use an ISA serial port. Tell bochs/QEMU to output the serial data into a file. I have a simple serial driver written for GAS in 32-bit mode, looks like this

Code: Select all

.section .data
debug_en: .byte 0
.previous
PORT_BASE=0x3f8
init_deb_con:
// see if register 7 can hold two values. If so, set port to 115200/8/0/1
    movb $1, %al
    movw $PORT_BASE + 7, %dx
    outb %al, %dx
    inb %dx, %al
    cmpb $1, %al
    jne 1f
    movb $2, %al
    outb %al, %dx
    inb %dx, %al
    cmpb $2, %al
    jne 1f
// we have winner. Now to initialize it
    movw $PORT_BASE + 1, %dx
    movb $0x00, %al
    outb %al, %dx
    movw $PORT_BASE + 3, %dx
    movb $0x80, %al
    outb %al, %dx
    movw $PORT_BASE + 0, %dx
    movb $0x01, %al
    outb %al, %dx
    movw $PORT_BASE + 1, %dx
    movb $0x00, %al
    outb %al, %dx
    movw $PORT_BASE + 3, %dx
    movb $0x03, %al
    outb %al, %dx
    movw $PORT_BASE + 2, %dx
    movb $0x06, %al
    outb %al, %dx
    movb $1, debug_en
1:
    ret

.global output_debug_port
.type output_debug_port, @function
output_debug_port:
    pushl %esi
    cmpb $1, debug_en
    jne 2f
    movl 8(%esp), %esi
    movw $PORT_BASE + 5, %dx
1:
    inb %dx, %al
    testb $0x20, %al
    jz 1b
    lodsb
    testb %al, %al
    jz 2f
    movw $PORT_BASE, %dx
    outb %al, %dx
    movw $PORT_BASE + 5, %dx
    jmp 1b
2:
    popl %esi
    ret
Now you only need to call init_deb_con() once and then you can call output_debug_port() with whatever null-terminated string you want to output. Full printf()-like functionality is up to you to add, but using this as output primitive you should have no trouble. Because the rest of printf() is just portable C. Yes, a more mature serial driver might use DMA, but this function is for when everything is broken and you still need to talk to the outside world.

Re: Is there a way to make a manually-defined io device in Q

Posted: Mon Oct 23, 2023 1:34 pm
by SomeGuyWithAKeyboard
Octocontrabass wrote:
SomeGuyWithAKeyboard wrote:I've tried the obvious thing of trying to write bytes directly to the port which doesn't do anything
That's the only thing you need in your code to use port 0xE9, so it should do something. What device did you pick for the "-debugcon" option when you started QEMU?
Turns out I had to launch it with "qemu-system-i386 disk.bin -debugcon stdio". I was previously not using the -debugcon" option because I didn't know about it.

Things were going great and I was making actual progress until I ran into a linking issue. I seem to be at the size limit of what my setup will allow me to compile. I'm at the point where if I add so much as 1 more line of code, I get the following error:

Code: Select all

misc_tools.o: in function `vga_readMapSelect':
misc_tools.asm:(.auxfunctions+0x1f0): relocation truncated to fit: R_386_16 against `.auxfunctions'
misc_tools.asm:(.auxfunctions+0x1f9): relocation truncated to fit: R_386_16 against `.auxfunctions'
misc_tools.o: in function `vga_setreset_reg':
misc_tools.asm:(.auxfunctions+0x210): relocation truncated to fit: R_386_16 against `.auxfunctions'
misc_tools.asm:(.auxfunctions+0x219): relocation truncated to fit: R_386_16 against `.auxfunctions'
misc_tools.o: in function `vga_ChangeWriteMode':
misc_tools.asm:(.auxfunctions+0x221): relocation truncated to fit: R_386_16 against `.auxfunctions'
misc_tools.asm:(.auxfunctions+0x228): relocation truncated to fit: R_386_16 against `.auxfunctions'
misc_tools.asm:(.auxfunctions+0x232): relocation truncated to fit: R_386_16 against `.auxfunctions'
collect2: error: ld returned 1 exit status
I don't know why this is happening or what's causing. I can only guess because the size of all my .o files added up together is 60.3kb, maybe once they all get combined into 1 binary, it adds up to more 64kb and maybe for some reason beyond my understanding, this program is for some reason limited to 64kb.

I changed my link.ld file from this:

Code: Select all

OUTPUT_FORMAT("binary")
SECTIONS
{
    . = 0x7c00;
    
    .text : ALIGN(1K)
    {
        bootloader.o(.text)
        *(.text .text.*)
    }

    .rodata : ALIGN(4K)
    {
        *(.rodata .rodata.*)
    }

    .data : ALIGN(4K)
    {
        *(.data .data.*)
    }

    .bss : ALIGN(4K)
    {
        *(.bss .bss.*)
    }

    .auxFunctions : ALIGN(1K)
    {
        
    }

    end = .;
}
To this:

Code: Select all

OUTPUT_FORMAT("binary")
SECTIONS
{
    . = 0x7c00;
    
    .text : ALIGN(1K)
    {
        bootloader.o(.text)
        *(.text .text.*)
    }

    .rodata : ALIGN(4K)
    {
        *(.rodata .rodata.*)
    }

    .data : ALIGN(4K)
    {
        *(.data .data.*)
    }

    .bss : ALIGN(4K)
    {
        *(.bss .bss.*)
    }

    end = .;
    
    . = 0x10000;
    
    .auxFunctions : ALIGN(1K)
    {
        
    }

    end = .;
}
and it seems to be working or at least working well enough to allow it to compile and boot. At least now I have a port e9 output to help debug this stuff now.

edit: nope, false alarm. Doing that change only allowed me to add like 2 more lines of c++ code before it went back to the same error. I then cut "*(.text .text.*)" and put in in the same block as the .auxfunctions and its working for now at least. Link issues are usually hard.

Re: Is there a way to make a manually-defined io device in Q

Posted: Mon Oct 23, 2023 2:36 pm
by Octocontrabass
SomeGuyWithAKeyboard wrote:R_386_16
You're using 16-bit instructions but the symbols are located at addresses that don't fit in 16 bits. You need to either replace the instructions in question with 32-bit instructions or adjust the linker script to ensure those symbols have addresses below 0x10000.

Why are those instructions 16-bit? For that matter, why are you linking your kernel and bootloader together into a single binary?

Re: Is there a way to make a manually-defined io device in Q

Posted: Thu Oct 26, 2023 1:27 pm
by SomeGuyWithAKeyboard
Octocontrabass wrote:
SomeGuyWithAKeyboard wrote:R_386_16
You're using 16-bit instructions but the symbols are located at addresses that don't fit in 16 bits. You need to either replace the instructions in question with 32-bit instructions or adjust the linker script to ensure those symbols have addresses below 0x10000.

Why are those instructions 16-bit? For that matter, why are you linking your kernel and bootloader together into a single binary?
Because I don't know how to separate them. I tried putting "SECTION .kernel" right before the "call begin" in my bootloader thinking I could put the c++ code after the .auxFunctions block. This doesn't work. Here is what I really think should work and I am stumped as to why it doesn't work:

Code: Select all

OUTPUT_FORMAT("binary")
SECTIONS
{
    . = 0x7c00;
    
    .text : ALIGN(1K)
    {
        bootloader.o(.text)
        *(.text .text.*)
    }
    
    .auxFunctions : ALIGN(1K)
    {
    	misc_tools.o(.auxFunctions)
    	*(.auxFunctions .auxFunctions.*)
    }
    
    .kernel : ALIGN(4K)
    {
    	system.o(.kernel)
    	*(.kernel .kernel.*)
    }
    
    .rodata : ALIGN(4K)
    {
        *(.rodata .rodata.*)
    }

    .data : ALIGN(4K)
    {
        *(.data .data.*)
    }

    .bss : ALIGN(4K)
    {
        *(.bss .bss.*)
    }

    end = .;
}
I struggle with linking a lot. I think maybe the error I was getting is caused because the "enable gate A20" subroutine in .auxFunctions is more than 32kb or 64kb from where it gets called which causes errors (it gets called while the system is in protected mode so it shouldn't be causing issues anyway but that's not what actually happens so it is what it is) so it would make sense to put auxFunctions right after the bootloader and then all the c++ code after that. This is hard to do and im still trying to get it configured in a way that will work. It compiles and everything it just doesn't boot unless I remove the SECTION .kernel directive from the line right before the "call begin" in the bootloader. Call bgin is the part in the bootloader assembly file that jumps into the c++ part of the code.

Edit: adding #pragma SECTION .kernel t the top of my top level cpp file seemed to allow it to both compile and run on qemu. It still doesn't boot anymore once I copy the image to a drive but that's been broken ever since I fixed my memory manager bugs and is hopefully a different problem.

Re: Is there a way to make a manually-defined io device in Q

Posted: Thu Oct 26, 2023 2:26 pm
by Octocontrabass
SomeGuyWithAKeyboard wrote:Because I don't know how to separate them.
Define a binary format that includes a load address and an entry point, then write a bootloader that can load a binary in that format and link your kernel into that format. ELF is one such format.

If ELF is too complicated, you can make it really simple: have the load address and entry point both be fixed values. For example, you can say the load address is always 0x10000 and the entry point is always 0x10000. For that to work, you need to tell the linker to put an assembly function at the start of your kernel binary that jumps to your real kernel entry point. (And you already know how to do that, since that's what you're doing right now to put your boot sector at the start of your binary.)
SomeGuyWithAKeyboard wrote:

Code: Select all

    .text : ALIGN(1K)
    {
        bootloader.o(.text)
        *(.text .text.*)
    }
    
    .auxFunctions : ALIGN(1K)
    {
    	misc_tools.o(.auxFunctions)
    	*(.auxFunctions .auxFunctions.*)
    }
All of your kernel's code will be placed where you write "*(.text .text.*)". You need to move that somewhere else. Something like this:

Code: Select all

    .bootsector : ALIGN(1K)
    {
        bootloader.o(.text)
    }

    .auxFunctions : ALIGN(1K)
    {
       misc_tools.o(.auxFunctions)
       *(.auxFunctions .auxFunctions.*)
    }

    .text : ALIGN(4K)
    {
        *(.text .text.*)
    }
SomeGuyWithAKeyboard wrote:I think maybe the error I was getting is caused because the "enable gate A20" subroutine in .auxFunctions
Do you really call your "enable gate A20" subroutine in vga_readMapSelect, vga_setreset_reg, and vga_ChangeWriteMode?
SomeGuyWithAKeyboard wrote:is more than 32kb or 64kb from where it gets called which causes errors
Distance has nothing to do with it. The distance could be zero bytes and you'd still get the error if the address doesn't fit in 16 bits.
SomeGuyWithAKeyboard wrote:(it gets called while the system is in protected mode so it shouldn't be causing issues anyway but that's not what actually happens so it is what it is)
Protected mode also has nothing to do with it. You can still have 16-bit addresses in protected mode.
SomeGuyWithAKeyboard wrote:it just doesn't boot unless I remove the SECTION .kernel directive from the line right before the "call begin" in the bootloader.
You know the linker will rearrange sections, right?
SomeGuyWithAKeyboard wrote:Edit: adding #pragma SECTION .kernel t the top of my top level cpp file seemed to allow it to both compile and run on qemu. It still doesn't boot anymore once I copy the image to a drive but that's been broken ever since I fixed my memory manager bugs and is hopefully a different problem.
It's better to fix your linker script instead of doing that.

Re: Is there a way to make a manually-defined io device in Q

Posted: Sat Oct 28, 2023 4:01 pm
by SomeGuyWithAKeyboard
Hmm, I'm not sure that I fully understand what's going on but I think(?) I got it without employing some stupid hack. When I undo the changes I made today (to the best of my memory, should've backed up), it still works even though it wasn't working earlier today.

Can I not ever have any assembly code above 0x10000? Is that how it works?

If i'm not supposed to have pragma directives in my c++ code, how do I know what to call it in link.ld? Is .text just what it is by default? I think that must be how it works since that's also where .rodata, .data and .bss comes from.

I changed my link.ld file to the following and changed some section directive around and it seems to be working at least for now.

Code: Select all

OUTPUT_FORMAT("binary")
SECTIONS
{   
    . = 0x7c00;
    
    .bootloader : ALIGN(1K)
    {
        bootloader.o(.bootloader)
        *(.bootloader .bootloader)*
    }
    
    .auxFunctions : ALIGN(1K)
    {
    	misc_tools.o(.auxFunctions)
    	*(.auxFunctions .auxFunctions.*)
    }
    
    .text : ALIGN(4K)
    {
    	system.o(.text)
    	*(.text .text)*
    }
    
    .rodata : ALIGN(4K)
    {
        *(.rodata .rodata.*)
    }

    .data : ALIGN(4K)
    {
        *(.data .data.*)
    }

    .bss : ALIGN(4K)
    {
        *(.bss .bss.*)
    }
    

    end = .;
}
Also, what's the difference between doing "bootloader.o(.bootloader)" and "bootloader.o(.text)"? It seems to work with both (for now).

When I compile, my script does the following:

Code: Select all

g++ -c -march=i486 -mtune=i486 -m32 -mfpmath=387 -fno-pie -nodefaultlibs -nostdlib -fno-exceptions -fno-stack-protector -O0 -o system.o system.cpp
nasm -g -f elf32 -o bootloader.o bootloader.asm
nasm -g -f elf32 -o misc_tools.o misc_tools.asm
g++ -w -march=i486 -mtune=i486 -m32 -mfpmath=387 -fno-pie -nodefaultlibs -nostdlib -O0 -fstack-usage -o disk.bin -T link.ld system.o bootloader.o misc_tools.o -lgcc
Edit again: dangit! Nope. I wrote a bunch of new code and the problem came up again.

So I can't use linking to change the position of sections.
Anytime any assembly code touches 0x10000, it's a compilation error.
I could get rid of most of my assembly code and put it in c++ but there are some things that have to be in assembly like input and output to ports. The problem would just come back eventually anyway.
One of the things I was working on trying before it randomly stopped giving me the error and I thought I fixed it was I was going to try compiling the bootloader and the system separately, make the bootloader load all the non-bootloader code to memory starting at 0x10000 and then call 0x10000. This wouldn't work because .auxFunctions would be above 0x10000 and it makes those errors.

The only other way I can think of, since linking can't be used to dictate where sections will go, is splitting my program into three parts. I would have to define each assembly subroutine used in my c++ function by manually finding the call addresses for each one and that would mean I have to redo that every time the assembly code changes. Maybe I could link .auxFunctions and bootloader together just to save a step and only deal with 2 files that have to be combined but since there really doesn't seem to be a way to make the linker put .auxFunctions before system if I link them together, i'd still have to manually find the addresses of each subroutine.

Re: Is there a way to make a manually-defined io device in Q

Posted: Sat Oct 28, 2023 4:35 pm
by Octocontrabass
SomeGuyWithAKeyboard wrote:Can I not ever have any assembly code above 0x10000? Is that how it works?
No, that's not how it works. You can have assembly code anywhere. You can't have 16-bit references to symbols above 0x10000.
SomeGuyWithAKeyboard wrote:If i'm not supposed to have pragma directives in my c++ code, how do I know what to call it in link.ld? Is .text just what it is by default? I think that must be how it works since that's also where .rodata, .data and .bss comes from.
Yes, those are the default section names, and there's usually no good reason to change them.
SomeGuyWithAKeyboard wrote:

Code: Select all

        bootloader.o(.bootloader)
        *(.bootloader .bootloader)*
If bootloader.o is the only object file that contains a .bootloader section, you only need one of these two lines. Also, the second line looks like it has a typo.
SomeGuyWithAKeyboard wrote:

Code: Select all

    	misc_tools.o(.auxFunctions)
    	*(.auxFunctions .auxFunctions.*)
Same for misc_tools.o and the .auxFunctions section.
SomeGuyWithAKeyboard wrote:Also, what's the difference between doing "bootloader.o(.bootloader)" and "bootloader.o(.text)"?
The first one tells the linker to insert the contents of the ".bootloader" section from the file named "bootloader.o". The second one tells the linker to insert the contents of the ".text" section from the file named "bootloader.o".
SomeGuyWithAKeyboard wrote:It seems to work with both (for now).
Because bootloader.o doesn't have a .text section and the very next line tells the linker to insert the .bootloader section from every input file.
SomeGuyWithAKeyboard wrote:When I compile, my script does the following:
You should probably be using a cross-compiler. If you're not going to use a cross-compiler, at least use "-ffreestanding".

Why do you specify "-mfpmath=387"? A typical OS kernel would use "-mgeneral-regs-only" instead.

Do not use "-w". If your compiler gives you warnings, you're doing something wrong, even if your code seems to work.

Re: Is there a way to make a manually-defined io device in Q

Posted: Sat Oct 28, 2023 5:39 pm
by SomeGuyWithAKeyboard
Octocontrabass wrote:
SomeGuyWithAKeyboard wrote:Can I not ever have any assembly code above 0x10000? Is that how it works?
No, that's not how it works. You can have assembly code anywhere. You can't have 16-bit references to symbols above 0x10000.
Is there any way to make it only use 32 bit references? Whatever minor performance penalty I'm avoiding by doing it this way is way not worth it. Removing -fno-pie seems to generate more errors and not solve the ones I already have. If not maybe I can find a way to put each address to each subroutine in .auxFunctions into an interrupt vector or something since I'm not getting anywhere doing it the link way.
Octocontrabass wrote:
SomeGuyWithAKeyboard wrote:When I compile, my script does the following:
You should probably be using a cross-compiler. If you're not going to use a cross-compiler, at least use "-ffreestanding".
I seem to run into less problems using plain ol' out of the box g++ on Linux than I do with any cross compiler. Considering how hard I struggle with linking issues, I should stick to doing it this way. If I wanted to actually use outside libraries, it would be a different story.
Octocontrabass wrote: Why do you specify "-mfpmath=387"? A typical OS kernel would use "-mgeneral-regs-only" instead.
I don't know. I think this is leftover from when I couldn't get it to work on real hardware but it would work on qemu so I was trying random stuff. Does using "-mgeneral-regs-only" prevent it from generating FPU instructions? I didn't know that was a thing you could do.