• Please review our updated Terms and Rules here

"Fun with BIOS" Programming Thread

Yeah, you've got your binary to hex messed up. There really is a conditional implied because '9' = hex 39 and 'A' = hex 41. One trick that's about as old as the 8008 is:

Code:
    and    al,0fh
    add    al,90h
    daa    
    adc    al,40h
    daa

Another "trick" is to use the DAS instruction--not quite as old, but pretty crufty:

Code:
    and     al,0fh
    cmp     al,0ah
    sbb     al,69h
    das

You can also use the XLAT instruction using a table that looks like '0123456789ABCDEF'

Your poison.
 
Yeah, you've got your binary to hex messed up. There really is a conditional implied because '9' = hex 39 and 'A' = hex 41. One trick that's about as old as the 8008 is:

Code:
    and    al,0fh
    add    al,90h
    daa    
    adc    al,40h
    daa

Your poison.

I just pulled out a piece of paper and convinced myself it works using an example, but... that is voodoo o.0;!
 
It's a start!

It's a start!

Not all of the register values are correct (I'll figure out what's up soon enough... after I take a break!), but this is indeed a start to a functional debugger.
dgb_start.jpg

Not sure if it makes much of a difference, but unlike Tim Paterson's debugger, which uses a data area to store the registers of the calling process, I store the registers on the caller's (or interrupted process', if a distinction is necessary) stack and then use displacement-base-index addressing to get the registers. I probably have some indices for SI incorrect.

0x04- Displacement to account for the previous value of BP and the previous IP. Registers are printed in a function called print_regs.
BP- Points to the top of the previous stack-frame, which has the previous BP. Because "print_regs" is a near function, the next element 'down*' on the stack is the previous IP. The registers at the time of interruption follow.
SI- Varies to point to different registers from the caller/interrupted process.

*Why does further down on the stack correspond to higher addresses? Let's make the terminology more confusing why don't we?!
 
Last edited:
Do you want to be able to breakpoint ISRs? That might mean that you have to handle all your own I/O outside of the BIOS.

I'm reviewing the thread to look for some specific information, but I figured I'd comment on this since it caught my eye.


Not sure how tracing inside an interrupt would be an issue. Disregarding that the BIOS is ROM/can't have breakpoints, I don't see how tracing inside the ISRs would be an issue... MS-DOS DEBUG gets away with it just fine.

If you are referring to debugging my own BIOS, some minimum level of functionality will be present before I attempt to use a DEBUG ROM to diagnose my own problems.

Besides hooking INTs 0x01, 0x03, 0x09, and 0x012, the debugger uses INT 0x10 to put video on the screen, and INT 0x16 for keyboard. This is because I wanted to support not entirely PC-compatible machines such as the Tandy 2000.

I intend to make debugger re-entrant in the sense that, calling the debugger within an interrupt that it has already hooked OR invoking the debugger within a debugger session won't crash the machine... it will just silently return to the previous invocation. How I do detect that the debugger is running already? I could check the CS:IP pair on the stack to see if it's within the ROM, or set an "inDebugger" flag.

EDIT: And this kids, is what happens when you called a debugger within a debugger too many times :D! Seriously, I can think of no good reason for doing this other than having a 10,000,001st way to crash DOS. I haven't a clue how Zenith's ROM handled it, but I somehow doubt you could invoke that debugger from within itself either... mmruzek, any ideas?
stk_overflow.jpg

And yes, this is perfectly avoidable by setting an aforementioned flag.
 
Last edited:
Well, my intention was that you might want to breakpoint IRQs 8 or 9. Suppose you set a breakpoint in the IRQ 9 ISR. How on earth would your debugger get a keystroke to tell it proceed? Most ISRs enter with interrupts disabled.

If you breakpoint the INT 10h video service, you'll going to have issues, but that's not an ISR, strictly speaking.

If you want to observe how arcane things can get, consider, say, a background floppy formatter that's activated with a hotkey combination. You can get the hotkey combination all right, but you don't know what the foreground program is doing or where it might be--and yet you need to display things, get keyboard input and run from your own stack, but leave the user's program running while you format the floppy. It's an interesting mental exercise.
 
Last edited:
Well, my intention was that you might want to breakpoint IRQs 8 or 9. Suppose you set a breakpoint in the IRQ 9 ISR. How on earth would your debugger get a keystroke to tell it proceed?
Hmmm... yes, that might be a problem. My naive solution is to just tell the user 'bad things will happen if you try that'. A better solution might be to hook the hardware interrupts and trigger a semaphore. If the semaphore is nonzero, the debugger cannot be invoked.

On a potentially similar note, I can make it so that the main loop of the debugger is invoked AFTER the keyboard interrupt is serviced in full (both the new routine and the BIOS routine)- by altering the return address on the stack a la a context switch. Currently the debugger is invoked within an invocation of INT 9 if the correct key combination is detected (which the register values already on the stack). I'm not exactly sure what other issues that would cause at this time, though.

Consider an arbitrary stack frame for a program. The debugger will allocate it's own call frame. When the debugger is invoked- either from keypress, absolute jump, breakpoint int, or trace int... doesn't matter!- the stack frame allocated for it- and the current SP/BP- should be considered the new 'bottom' of physical stack as far as the debugger is concerned. The program's stack frame below this address is 'illegal' for the debugger to use or modify (read is okay, and required if we want the program's stack frame :p). We should NEVER be executing ANY debugging code if the current address of top of the stack (SP) is greater than the address when the debugger was invoked.

The above procedure probably also means that I have to keep track of how the debugger was invoked (either keypress, break, trace, jump, etc), but we'll see...

Addendum after giving it some more thought:
Basically, the debugger will always exit before executing the next instruction prior to invocation, even if it's in the middle of an interrupt such as the keyboard interrupt or even timer interrupt.

The only routines I can really envision being non-reentrant is the timer (0x08) and keyboard hardware interrupts (0x09 and 0x16)- even then the debugger shouldn't really crash... just debugging such a routine would be unreliable because data was modified outside the context of the debugger.

If you want to observe how arcane things can get, consider, say, a background floppy formatter that's activated with a hotkey combination. You can get the hotkey combination all right, but you don't know what the foreground program is doing or where it might be--and yet you need to display things, get keyboard input and run from your own stack, but leave the user's program running while you format the floppy. It's an interesting mental exercise.

I'm not sure I see the issue here... why would I need to display things and get keyboard input for a program that formats a floppy in the background? Even if it needs to, I don't see the issue with the same interrupt being called twice... I thought the BIOS interrupts at least were re-entrant. The OS can still handle keyboard and video while the floppy drive does its format, and the timer interrupt can be hooked to check the floppy drive and send a command if necessary every 18.2 ms.
 
Last edited:
The PC BIOS routines are not reentrant in all cases. Consider, for example, the video I/O routines; INT 10H, funciton 0Eh, uses and updates the cursor position, but does not set it. The floppy I/O routines store the FDC status in BIOS RAM (read up during execution of the ISR). In addition, there are the "call and stall" BIOS invocations, such as "Wait until a keystroke is ready, then return it". Returning prematurely (as in a hot-key invocation) could really disrupt the function of a program.
 
Here is a view of the Zenith Debugger window on a Zenith Data Systems Z466X+ running the BIOS 4.2F The Monitor program is invoked by making a special keypress (Control, Alt, Ins). The Monitor program offers 5 choices: One of which is the Debugger. The Monitor program can be invoked while in DOS, but not from Windows 3.1 The Monitor program can also be invoked while inside a DOS application, such as for example from DOS Debug which is also on the machine. However in all cases the exit from the Monitor program does a cold system reboot. For what it is worth, I also found you can use the special key combination to invoke the Monitor program while you are in the Zenith Debug screen.

zenbug.jpg
 
I'm interested in giving your BIOS a try when you've got it to some reasonably useable state!

The corresponding ROM debugger should be at a reasonably useful state soon... I'll use that to debug parts of the BIOS. The 'MS-DOS on FPGA' project gives a list of minimum requirements for BIOS to boot into DOS, so I'll start with those.
 
After giving the 'non-entrancy' issue some thought, this is my solution at least for my int 0x09 handler. It does work, and it DOES enable me to stop execution either during POST or at the DOS prompt, or in FORMAT.

Code:
int9_handler proc far
		;We need to prepare for the possibility that we will return to
		;the debugger!
		pushf
		push cs
		add sp, -0x02 ;We can't use any other registers now!
				;The original SP can be calculated later.
		push bp
		mov bp, sp
		;mov ss:[0x02 + bp], offset cs:[main_loop]- Crashes spectacularly
		mov ss:[0x02 + bp], offset main_loop
		
		;Not sure which registers are used.
		push ax
		push cx
		push dx
		push bx
		push si
		push di
		push ds
		push es
		
		int 0x12 ;Needs to be the modified ISR
		inc ax
		mov cl, 6 ;Multiply by 64 to get segment-equivalent (1024/16)
		shl ax, cl
		mov es, ax ;Data area is now prepared
		
		
		pushf ;Simulate interrupt
		call es:[old_int9_handler] ;Absolute indirect
		
		
		
		;Has the debugger been defeated?
		;If so, restore the interrupts.
		
		
		;Is key combination valid?
		
		;If so, are we in main_loop already?
		
		;if not call main_loop. It is the ma
		
		;otherwise restore program state, regardless of whether we are
		;in debugger or not.
		
		;push ax
		;push si		
		;push ds
		mov ax, cs
		mov ds, ax
		
		;mov si, offset key_message
		;call print_str
		
		
		assume ds:DGROUP
		xor ax, ax
		mov ah, 0x02
		int 0x16
		cmp al, 0x0C ;CTRL+ALT+ESCAPE pressed?
		
		;pop si
		pop es
		pop ds
		pop di
		pop si
		pop bx
		pop dx
		pop cx
		pop ax
		
		mov sp, bp
		pop bp
		
		;The POPped registers did not affect the comparison
		jnz exit_int
		iret ;Otherwise, use IRET to jump into the main loop!
exit_int:
		add sp, 0x06 ;Skip going into the main debug loop
		iret
	int9_handler endp

There is an iret at the end of main_loop, which takes us back to the original program.

I have attached a preliminary ROM demonstrating this handler in action, using the debugger code (but most of it inaccessible at the moment). By pressing CTRL+ALT (not a typo- ignore whatever I wrote in the above code... CTRL+ALT+ESC or CTRL+ALT+INS doesn't work properly outside of the DOS prompt), the current thread of execution can be halted until the user presses a key, regardless of where the PC is currently executing code (POST, DOS, FORMAT, probably GAMES, etc). I have confirmed this works in Bochs by attaching a ROM at 0xd000:0x0000 and 0xd200:0x0000. So it should work just fine at the IBM PC (and XT clones) ROM slot at 0xf400:0x0000

Michael, would you be willing to burn the attached bin to an EPROM and play with it a bit? It doesn't do anything useful- just print a 'press any key to continue' message, but it's meant to be a test more than anything. And if you should happen to happen to find ways to crash it (besides, "internal stack overflow", which is still there/I am aware of), please let me know :D. I'll just add it to my list of "1,001 Ways to Crash DOS" ;).
 

Attachments

  • monitor.zip
    1.2 KB · Views: 1
I took the Monitor code and edited it with a Hex editor to burn correctly into a 27128 ROM. Then I used an adapter to mount the ROM in socket U28 of an original IBM 5150 PC. The code is showing correctly at memory location F400:0000 on the PC.

During boot, at about the time I would expect the operating system to be checking for valid ROM extensions I see a few characters flash on the screen: usually a sequence something like "I S D S I D". The "D" usually remains persistent on the screen until the DOS prompt.

However, pressing the special 2 key combination after boot appears to have no effect from the DOS prompt. Loading DEBUG and executing a GO to F400:0003 creates the following DUMP.

AX=0007 BX=2404 CX=4B29 DX=0000 SP=20C8 BP=98AF SI=0000 DI=37B8
DS=1198 ES=0000 SS=1198 CS=FFD2 IP=0000 NV UP DI PL N2 AC PO NC
FFD2:0000 CC INT 3

Michael
 
I took the Monitor code and edited it with a Hex editor to burn correctly into a 27128 ROM. Then I used an adapter to mount the ROM in socket U28 of an original IBM 5150 PC. The code is showing correctly at memory location F400:0000 on the PC.

During boot, at about the time I would expect the operating system to be checking for valid ROM extensions I see a few characters flash on the screen: usually a sequence something like "I S D S I D". The "D" usually remains persistent on the screen until the DOS prompt.

However, pressing the special 2 key combination after boot appears to have no effect from the DOS prompt. Loading DEBUG and executing a GO to F400:0003 creates the following DUMP.

AX=0007 BX=2404 CX=4B29 DX=0000 SP=20C8 BP=98AF SI=0000 DI=37B8
DS=1198 ES=0000 SS=1198 CS=FFD2 IP=0000 NV UP DI PL N2 AC PO NC
FFD2:0000 CC INT 3

Michael

Well indeed, the offset in question within the BIOS (0x1FD2 in a hex editor) is indeed an "INT 3 instruction" (it's the internal font data being treated as code- definitely not correct!)... I have a few ideas of what is going wrong. The ROM does work in Bochs, and I make sure to limit myself to 8088 instructions. My guess is I'm using one of the BIOS functions improperly, and am not preserving some registers that should be preserved on an 8088- Bochs is a 386 emulator and uses a BIOS that is partially written in C, FWIW. It's telling that not even the banner pops up:

Code:
banner db "IBM PC Monitor",10,13
		db "(C) 2013 William D. Jones", 10,13, 0

Sorry I couldn't test this myself. Due to the way my workbench is organized, I wasn't (and still am not currently) in a position where I can easily test on real hardware, but I should be soon.
 
Last edited:
Well, I tested my own EPROM at slot U28, and the title screen comes on just fine, and I've verified that at least int 3 is working and transferring control to the main loop. The code locks up while waiting for keyboard input within the main loop as I added an int 3 invocation to the init routine since uploading the monitor.bin file last night. Int 3 in turn calls the main loop, which in turns calls the wait for key routine and displays "press any key to continue" on screen. I therefore conclude that my keyboard interrupt handler isn't playing nicely with the rest of the code. Why that is so remains elusive.
 
A video documenting the current status

A video documenting the current status


Hope you guys following the thread enjoy! You also get to hear my beautiful, angelic voice :D! :sarcasm:
 
Interesting video. I'm wondering if you also tried to boot using the original IBM 10/27/82 BIOS ? The only other difference that I can think of is that I am running an MDA video card, with an original green screen.
 
Last edited:
Interesting video. I'm wondering if you also tried to boot using the original IBM 10/27/82 BIOS ? The only other difference that I can think of is that I am running an MDA video card, with an original green screen.

The original BIOS also locks up waiting for keyboard input. This tells me that I must not be preserving registers correctly when calling int 0x16 from within int 0x09.

Okay, this is stupid... even between clones, I can't assume memory will stay intact after the expansion ROM search is completed. The Generic XT BIOS (and by extension, ERSO BIOSes) checks that memory is good AFTER the expansion ROM has set up its data structures in memory. And it doesn't use int 0x12 either (it reads the BIOS Data Area at offset 0x13- fair enough, as it IS the BIOS itself).

From writing this ROMable software, I've figured out the hard way that I cannot successfully write the software without assuming some IBM-compatible offsets, but I have tried to keep hardware dependency to a minimum. The target machines are the PC, PC XT, all PC and PC XT clones, and the Tandy 1000 and 2000 (The 1000/2000 has a ROM expansion, as I recall... but the 2000 is a non-PC compatible BIOS- so that's the toughest).

At this point, I think the best option is to hardcode the interrupt vector jumps, and give the end user an option to compile different jump locations for non-compatible machines. This would make the debugger completely ROMable, only using the stack for variables, but it would render certain features impractical- notably breakpoints. I'd have no means to store the table of breakpoint addresses and the correct instruction to restore to the patched code (0xCC). Kinda defeats the purpose of a debugger, no? Arggh!
 
Last edited:
Cool video. :)

Is int 16h even hooked by the BIOS yet though? I'm not looking at a BIOS listing right now, but I'm pretty sure option ROMs are executed before that happens. It happens very early in the POST process.
 
Cool video. :)

Is int 16h even hooked by the BIOS yet though? I'm not looking at a BIOS listing right now, but I'm pretty sure option ROMs are executed before that happens. It happens very early in the POST process.

Thank you!

ROM Expansion check starts at 0xE49C, and the interrupt vectors are set up at 0xE46D in the BIOS listings (assuming the BIOS segment at 0xF000). Both these offsets are within the POST routine, so it's safe to say that the interrupt vectors are set up before ROM Expansion check begins.

This also occurs in the Generic XT BIOS, with the caveat that the memory check happens AFTER the ROM Expansion check. This defeats me being able to reliably allocate memory at boot, and debugging my BIOS which was one of the purposes of writing this debugger.

Note: After thinking about this for a few minutes I could get rid of the breakpoint table by always prompting the user to input the correct instruction to patch when a breakpoint is invoked (and they keep a list of breakpoints on paper) :). Therefore, the patched opcode is stored not in computer memory, but the user's brain :D! Not exactly elegant, but it would work. This would also enable the debugger to be completely ROMable.
 
You're right. I guess that wouldn't make a lot of sense anyway as the other way around would just make the BIOS overwrite any option ROM hooks put in place. Not very helpful. :p

If you remain stuck here, maybe we could help if we saw your keyboard routines. You mentioned int 9. You've got your own int 9 ISR? Just to be sure, I'm sure you've got this covered but it can't hurt to ask... you are enabling interrupts before waiting for keystrokes, right?
 
Back
Top