Is there a way to make a manually-defined io device in QEMU?
-
- Member
- Posts: 27
- Joined: Thu Aug 25, 2022 3:54 pm
Is there a way to make a manually-defined io device in QEMU?
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?
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
Why not use gdb in conjunction with qemu?
-
- Member
- Posts: 27
- Joined: Thu Aug 25, 2022 3:54 pm
Re: Is there a way to make a manually-defined io device in Q
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.iansjack wrote:Why not use gdb in conjunction with qemu?
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
-
- Member
- Posts: 5560
- Joined: Mon Mar 25, 2013 7:01 pm
Re: Is there a way to make a manually-defined io device in Q
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.
-
- Member
- Posts: 27
- Joined: Thu Aug 25, 2022 3:54 pm
Re: Is there a way to make a manually-defined io device in Q
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.
-
- Member
- Posts: 5560
- Joined: Mon Mar 25, 2013 7:01 pm
Re: Is there a way to make a manually-defined io device in Q
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?SomeGuyWithAKeyboard wrote:I've tried the obvious thing of trying to write bytes directly to the port which doesn't do anything
Re: Is there a way to make a manually-defined io device in Q
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
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 thisSomeGuyWithAKeyboard 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.
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
Carpe diem!
-
- Member
- Posts: 27
- Joined: Thu Aug 25, 2022 3:54 pm
Re: Is there a way to make a manually-defined io device in Q
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.Octocontrabass wrote: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?SomeGuyWithAKeyboard wrote:I've tried the obvious thing of trying to write bytes directly to the port which doesn't do anything
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 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 = .;
}
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 = .;
}
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.
-
- Member
- Posts: 5560
- Joined: Mon Mar 25, 2013 7:01 pm
Re: Is there a way to make a manually-defined io device in Q
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.SomeGuyWithAKeyboard wrote:R_386_16
Why are those instructions 16-bit? For that matter, why are you linking your kernel and bootloader together into a single binary?
-
- Member
- Posts: 27
- Joined: Thu Aug 25, 2022 3:54 pm
Re: Is there a way to make a manually-defined io device in Q
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:Octocontrabass wrote: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.SomeGuyWithAKeyboard wrote:R_386_16
Why are those instructions 16-bit? For that matter, why are you linking your kernel and bootloader together into a single binary?
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 = .;
}
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.
-
- Member
- Posts: 5560
- Joined: Mon Mar 25, 2013 7:01 pm
Re: Is there a way to make a manually-defined io device in Q
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.SomeGuyWithAKeyboard wrote:Because I don't know how to separate them.
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.)
All of your kernel's code will be placed where you write "*(.text .text.*)". You need to move that somewhere else. Something like this:SomeGuyWithAKeyboard wrote:Code: Select all
.text : ALIGN(1K) { bootloader.o(.text) *(.text .text.*) } .auxFunctions : ALIGN(1K) { misc_tools.o(.auxFunctions) *(.auxFunctions .auxFunctions.*) }
Code: Select all
.bootsector : ALIGN(1K)
{
bootloader.o(.text)
}
.auxFunctions : ALIGN(1K)
{
misc_tools.o(.auxFunctions)
*(.auxFunctions .auxFunctions.*)
}
.text : ALIGN(4K)
{
*(.text .text.*)
}
Do you really call your "enable gate A20" subroutine in vga_readMapSelect, vga_setreset_reg, and vga_ChangeWriteMode?SomeGuyWithAKeyboard wrote:I think maybe the error I was getting is caused because the "enable gate A20" subroutine in .auxFunctions
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:is more than 32kb or 64kb from where it gets called which causes errors
Protected mode also has nothing to do with it. You can still have 16-bit addresses in protected mode.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)
You know the linker will rearrange sections, right?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.
It's better to fix your linker script instead of doing that.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.
-
- Member
- Posts: 27
- Joined: Thu Aug 25, 2022 3:54 pm
Re: Is there a way to make a manually-defined io device in Q
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.
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:
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.
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 = .;
}
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
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.
-
- Member
- Posts: 5560
- Joined: Mon Mar 25, 2013 7:01 pm
Re: Is there a way to make a manually-defined io device in Q
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:Can I not ever have any assembly code above 0x10000? Is that how it works?
Yes, those are the default section names, and there's usually no good reason to change them.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.
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
bootloader.o(.bootloader) *(.bootloader .bootloader)*
Same for misc_tools.o and the .auxFunctions section.SomeGuyWithAKeyboard wrote:Code: Select all
misc_tools.o(.auxFunctions) *(.auxFunctions .auxFunctions.*)
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:Also, what's the difference between doing "bootloader.o(.bootloader)" and "bootloader.o(.text)"?
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:It seems to work with both (for now).
You should probably be using a cross-compiler. If you're not going to use a cross-compiler, at least use "-ffreestanding".SomeGuyWithAKeyboard wrote:When I compile, my script does the following:
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.
-
- Member
- Posts: 27
- Joined: Thu Aug 25, 2022 3:54 pm
Re: Is there a way to make a manually-defined io device in Q
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: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:Can I not ever have any assembly code above 0x10000? Is that how it works?
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:You should probably be using a cross-compiler. If you're not going to use a cross-compiler, at least use "-ffreestanding".SomeGuyWithAKeyboard wrote:When I compile, my script does the following:
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.Octocontrabass wrote: Why do you specify "-mfpmath=387"? A typical OS kernel would use "-mgeneral-regs-only" instead.