9

I have a partly-written emulation program for the 8086 CPU. And I'm not sure how to go about adding I/O capabilities to it. The emulator implements a subset of the 8086 instruction set described here.

I plan to implement bios routines for character I/O in int 10 and int 16. But then I realized that the bios code is still just machine code, so I'll still need some extra step to peek outside of the VM and call "host" functions like putchar() and getchar().

The two option that occur to me are to use the in and out instructions and then implementation of those opcodes can do the file I/O; -or- I think I could use the esc instructions and have a sort of "watchdog" on the instruction stream, and the watchdog can call extra code.

Which of these would be less headache in moving forward to a full emulator that can run dos programs? Is there another, better way I haven't thought of?

2
  • have a look at bochs bochs.sourceforge.net Jun 3, 2018 at 2:08
  • 1
    Well if you're writing your own emulator, you can make it so the int 10 and int 16 instructions themselves do the I/O - BUT that would make it impossible for software to override them.
    – user253751
    Aug 20, 2018 at 2:01

3 Answers 3

8
+50

Not sure what the goal of your emulator is, but the most simple way is just to trap the INT instruction itself. So whenever an INT 10h comes up, don't emulate the INT instruction, but rather let the emulator handle the request instead of some BIOS code (within the emulation).

Much the same way as a CP/M-86 emulation layer atop DOS works, or a CP/M-80 emulation layer for a V20/V30.

Depending on what you intend to do, a hybrid aproach could be possible as well. Here 'hooking' is only done for some sub functions, while others are still handeled by the BIOS within the sandbox.

13

It really depends on what you’re planning on emulating. If you want a precise emulation of a PC (like PCem), you’ll need to emulate all the peripherals too, which moves the problem away from the CPU and its instruction stream. If you’re interested (perhaps even as an intermediate step) in emulating I/O from the CPU’s perspective, there are a number of ways to go about it. Since you’re looking into adding support for BIOS routines, one simple way is to provide a special interrupt which requests services from the emulator — DOSEMU does this for some services with its interrupt 0xE6.

However since your goal is to be able to run DOS programs, you’ll need to do more than implement the BIOS routines using escapes: many DOS programs access the hardware directly. That “only” involves port I/O or memory accesses (including DMA); you’d certainly need to deal with that for sound support, floppy support, serial/parallel port I/O, and video (since most DOS programs accessed the video segments directly rather than go through the BIOS). You’d then implement your BIOS services on top of that hardware support. The big exception you could simplify is hard drive access, since all DOS programs you’d reasonably want to run in an emulator went through the BIOS for that.

11

There are quite a number of possibilities that you can use when emulating a system. Note, for screen emulation, I would rather try to detect memory accesses to the frame buffer address of your emulated video adapter than looking into I/O commands. Following are some general example approaches:

  1. Patch the BIOS (or, in general, any piece of guest code that accesses specific hardware) Replace instructions in strategic places with, for example, an invalid opcode - This can then be caught by the CPU emulation and can trap out into host code doing the host I/O. For this, you need to have pretty exact knowledge of your guest OS and/or BIOS. Note, when guest code by-passes the OS or BIOS, you won't catch that. Your emulator wll only be able to run "well-behaved" code.
  2. Monitor memory/I/O access in your CPU emulation. This is the simplest, but the slowest method. For any memory or I/O access in your CPU emulation, check the target address - If it goes into a specific hardware areas (screen memory, for example), leave the emulation, try to find out what guest code tried to do and call appropriate host functions. This can be quite cumbersome, tends to clobber up your emulation code with tests for addresses, and will also slow down your emulation significantly. This is, however, the best method to handle I/O (in and out instructions)
  3. mprotect (or, in general, protect specific areas in your emulated hardware memory as read-only or even "no read, no write"). For console I/O, you would normally mprotect (or, on Windows, the equivalent VirtualProtect function) the area used for screen memory (depending in your emulated video adapter, this would be 0xB0000, for example, for an MDA adapter) - This will cause a trap in your host code as soon as your guest code tries to write to that memory. Write a trap handler (or, on Unix-like systems, a signal handler) that can then check what the guest code tried to do and call appropriate host I/O mechanisms. This has the added advantage that your emulation can also follow if the client code by-passes the BIOS and directly accesses hardware or memory. This is maybe the most advanced function, as it can detect hardware accesses in arbitrary guest code just like (2), but with much less overhead for "normal" accesses.
2
  • I don't think mprotect is available for 8086.... Jun 3, 2018 at 17:59
  • 9
    @RadovanGarabík I was not talking about implementation on a 8086 (and I guess the OP didn't, as well). Obviously, you need virtual memory protection on the host, not on the guest.
    – tofro
    Jun 3, 2018 at 20:12

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .