While trying to use a "modern" sound card (an Aztech Sound Galaxy Pro 16 II) in an XT compatible 8086 computer, I encountered the problem that the drivers and tools (like the mixer initialization tool and the resource configuration tool) are compiled with 286 real mode instructions (ENTER, LEAVE, PUSH imm8, PUSH imm16, SHL r/m, imm8). I considered different approaches:

  • Disassemble the whole program with IDA, mark up all offsets, and reassemble it with an assembler that synthesizes 8086 instructions to emulate the 80286 instructions.
  • Re-Engineer the tools I need
  • Write an automatic debugger, that places breakpoints on all 80286 instructions and emulates them.

I dropped the first two ideas because of the great amount of manual work they need after getting the idea for the third approach. I am in no way striving for perfect 286 emulation, just emulation of the features typically used by compiled C programs. I am sure some one had that idea before me, though. Does anyone know of an implementation of that idea, so I don't have to implement it myself?

I am aware that I don't have to use that specific sound card, but I anticipate I will repeatedly encounter software that uses 286 instructions without being otherwise useless on XT computers, so a generic solution might be helpful in the future.

The program at hand, HWSET.EXE, contains the following 286 real mode instructions (and a consideration, how easy the JMP FAR suggestion by Raffzahn could be implemented). This list is complete according to the list of unsupported first bytes of the 8086. I made no effort to detect instructions that are invalid on an 8086 only due to certain bits in subsequent bytes, as I am not aware of any:

  • 27 instances of ENTER, 22 of them as ENTER 2, 0. Enter is 4 Bytes, so a JMP FAR overwrites the first byte of the following instruction, which is PUSH SI, PUSH DI, MOV AX, imm16 or PUSH imm8
  • 61 insances of LEAVE; RET (this is more than ENTER because of functions with multiple return instructions). This is just 2 bytes, and the bytes after these two bytes can not be patched, as RET is followed by a new entry point.
  • 33 instances of PUSH imm16
  • 145 instances of PUSH imm8 (most of them PUSH 0 or PUSH 1 (these push instructions are generally used in conjunction with CALL (near), so there are 5 bytes at least in total.
  • 18 instances of SHL r8, imm8 (with imm8 != 1), no obvious usage pattern, but 2 bytes afterwards seem to be at all samples I inspected by hand.
  • 25 instances of SHR r8, imm8 (with imm8 != 1), seems to be from macro-assisted assembly code. This 3-byte instruction is mostly followed by 2 pops, so it can be replaced by a far jump.
  • 6 instances of imul r16, rm16, imm8
  • 11
    The simplest solution would to replace the 8088 CPU with a NEC V20 CPU, which is 80186 compatible, and so implements all the instructions added to the 80286 except those specific to protected mode. Otherwise reverse engineering the code to write your own program would be the easiest solution, even if you have to do it for a number of programs.
    – user722
    Oct 26 '19 at 22:48
  • There's also the Orchid Tiny Turbo 286 accelerator card, but it takes up an ISA slot. Oct 26 '19 at 23:01
  • Ross Ridge mentioned this, but I wanted to explicitly add that all listed instructions are actually from the 186 instruction set, and not added only with the 286. This detail is often overlooked because the NEC V20/V30 CPUs and the 188/186 were seldom used in PC compatibles.
    – ecm
    Nov 30 '19 at 17:01

There are 386 real-mode emulators for 286s, such as Eko Priono’s EMU386; but they relied on one important feature which 8086s don’t have, the invalid instruction exception. Whenever a 286 attempts to run an invalid instruction, it traps, and a handler can be put in place to emulate the instruction (if it really is a “missing” instruction from a later architecture).

On 8086s, you’d really have to patch the binary, either by disassembling it and re-assembling it as described in your first approach, or by tracing it at runtime and replacing the missing instructions with hooks. The latter would involve re-implementing something like IDA’s block analysis, ultimately, although it might be possible to get away with something less involved — at worst, run the programs in single-stepping mode (TF set, and handle INT 1), assuming the programs in question don’t disable that mode themselves, or insert CCs at entry points and scan ahead for jumps or calls...

Single-stepping mode would work quite well for this scenario: when TF is set, the CPU invokes interrupt 1 after the next instruction, and clears TF (so that the handler isn’t run in single-stepping mode...). The return pointer pushed on the stack points to the next instruction; the handler can then examine it and determine what to do with it: let the CPU run it directly, emulate it, replace it with something else... The handler can choose whether TF should be set again on return by changing the flags pushed on the stack, and it can also skip instructions by changing the return address (after making sure the instruction at the new return address doesn’t need special handling either).

There are some further peculiarities on single-stepping one has to consider:

  • If you don't have the first stepping of the 8088, no single-step interrupt is entered for the instruction following a segment register change, even if the target segment register is not SS. There is real world code in which a IMUL r16, r/m16, immed8 follows MOV ES, [screen_segment].
  • If an interrupt is recognized during the execution of an instruction, that interrupt gets entered before the single step is recognized — i.e. you get a single step interrupt "pointing to" first instruction of the interrupt handler. What happens when that handler returns differs between internal and external interrupts, even if that is not clearly mentioned in the 8086 family user's manual

I’m not aware of any existing, available program which would do this.

  • 1
    Well, Since single step means an interrupt per instruction, it'll slow down the CPU by more than 100 times, wouldn't it?
    – Raffzahn
    Oct 26 '19 at 21:11
  • 1
    @StephenKitt Jup, just checked, INT1 is issued at the END of the previous instruction. So it works. Except, when the instruction the return address points to is one of the instructions to be emulated, the program has to check for the next as well, as returning after adjusting IP would not issue another trap.
    – Raffzahn
    Oct 27 '19 at 17:02
  • 1
    @MichaelKarcher: yes, single-stepping is probably only viable to assist the detection pass of a dynamic translation system. A sound-card driver will have real-time performance requirements to not stutter. Oct 27 '19 at 18:41
  • 1
    @PeterCordes I’m not sure an 8086 could run a sound card driver with real-time requirements anyway... IIRC that sort of thing was done on 386s and later (SB emulation on GUS etc.). Most sound card tools which it would be useful to run on an 8086 are of the setup variety (e.g. PnP configuration on a non-PnP system), or mixers, and the additional delay shouldn’t be too much of a nuisance. Oct 27 '19 at 18:45
  • 2

Have you ever thought of changing the 8088 in your PC to a NEC V20 (uPD70108)?

The V20 is basically a 80186/286 EU with an 8088 BU. This offers all the 'new' real mode instructions (*1) you need, while still being pin-compatible with the 8088. And in addition you'll get some 30% sped up as well - quite handy, isn't it?

Using a V20 would remove all need to modify or patch any software on the PC, and it is contemporary to 1980s PCs. Many (including me) replaced their 8088 by a V20 as soon as they were available (ca.1983). In fact, many late 'turbo' PCs used it by default - after all, it could be clocked up to 16 MHz, making a V20-based XT quite capable of outrunning an 8-10 MHz AT under DOS.

Unlike speed upgrades on game machines / home computers (like C64 or Amiga), it is reasonable to assume PC games will cope with the speed-up. Even early on, way before the PC became a serious game platform, a huge spread in speed was real. XTs running at anything from 4.77 MHz to 16 MHz as well as being 8 or 16 bit (8088 vs. 8086) or using a compete different timing (86 vs. 186 vs. 286 ...) made up the landscape in the mid 1980s. Any software intended to sell across as many machines as possible used timers and interrupts for synchronization; timing loops were almost never used.

V20 improvements were:

  • 8086-2 instruction set (aka 286 real mode).
  • General speed up, mostly due to a much improved BIU (*2).
  • Most instruction timings are similar to the 80188 (or 80286 working on odd addresses).
  • 8080 emulation mode - a gift from heaven for 'porting' of CP/M software to MS-DOS. Nowadays a great way for retro-nerds to run an upscale CP/M and MS-DOS in one setup.
  • Additional instructions for BCD/Nibble handling.
  • Greatly improved instructions for bit handling, including bit field extraction.

There are plenty of NOS dealers offering V20 anywhere between €5 and €50 apiece.

*1 - Intel called the enhanced ISA 8086-2 instruction set.

*2 - Bus Interface Unit - the part of the x86 dandling all data address calculation as well as data access and instruction prefetch.

  • 1
    Thanks for the idea! I considered the V20, but didn't know enough. I assumed it does not offer 80286 real mode compatibility. I considered the upgrade to the V20 a bad idea, because it weakens IBM PC timing compatibility, even though it is contemporary. Knowing about the 286 instruction compatibility makes me reconsider the idea. It might be interesting to stack the 8088 and a V20 and add a switch to hold one of the processors from accessing the bus. (Just as other people have ROM switchers for their Amiga). Oct 27 '19 at 18:50
  • 2
    @MichaelKarcher Try it and you'll be surprised how good it works with next to all software.Stacking CPUs is not really anything worth heating the soldering iron for(unless you want to do it by putting it on an expansion card, which might offer even more ways to improve). As for myself I haven't found a single game becoming unplayable, including very early ones - even less any user software. Keep in mind, speed up for XT computers as well as the advent of 186 and 286 computers made games to obey timers instead of using code loops. They had to cover way more variety than typical game machines.
    – Raffzahn
    Oct 27 '19 at 18:57
  • 3
    "It is reasonable to assume PC games will cope with the speedup" -- on the contrary, many did not, including "Mechwarrior" (1987). Oct 29 '19 at 23:14
  • 1
    @snips-n-snails So, tell me again what's the point of listing some exceptions? There will always be some. Should I answer with all the games that do? From POR to Flightsimulator? I've played on an original PC back then, and I got not just a V20, but as well an overclocking system speeding it up according to RAM speed (a rather nifty board :)) no memories of games not working as expected. In most cases I was rather grateful of any speedup noticed.
    – Raffzahn
    Oct 31 '19 at 8:41
  • 1
    @Raffzahn I assume that wasn’t your intention, but the tone of your comments comes across as rather aggressive. You gave your anecdotal experience, as did snips-n-snails; since it’s all anecdotal, it’s not right or wrong — no one doubts that you never came across a game which was unplayable on a V20, and that’s not contradicted by someone else having done so. For what it’s worth, I never spent much time with a V20, but I did have an 8MHz 8086 which could be down-clocked to 4.77MHz, and had to be for a few games. Nov 2 '19 at 22:21

I am sure some one had that idea before me,

As always :)

Does anyone know of an implementation of that idea, so I don't have to implement it myself?

Nop. Sorry. You may have to go ahead with your debug idea, catching them using Int3.

  • Still, it wouldn't be anywhere near acceptable speed as Int/Ret already eats up 72+44=116 cycles - a table look up (Pulling the interrupt return address and checkign which instruction has been replaced) followed interpretation (including parameter load from the follow up bytes) may take several hundert cycles. Not really cool.

  • Next best thing might be a hybrid approach, still using INT3 to enter the emulator, but build a separate replacement code for each and every replaced instruction. Thus only INT and table lookup has to happen, followed by execution of the replacement code and a direct return jump instead of IRET (JMP far is just 15 clocks) as easy to implement, but only shaves of 29 cycles - while still burning way past 200, depending on the instruction.

  • The eventually best performing approach might be classic patching. Replace the offending instruction (and maybe a following to gain space) by a far jump direct into a replacement routine (like before) - just this time eventually including the additionaly overwritten instructions and return. This reduces the overhead added per instruction to a minimum of 30 clocks (two near or far jumps).

Your choice.

In any case, some statistics about all instances would give some oversight for further decisions.

  • 1
    I am aware of the drawbacks. I don't care if changing the sound card IRQ takes 2 seconds instead of half a second. But I do care that I don't have to put the card in a different computer to do that. Actually, I also thought about "fast path" processing for cases where I have enough bytes (like leave; ret) or (enter XXX,0) where I can spare 2 bytes for INT 80, INT 81 and so on. Too bad CC aka INT 3 seems to be the only single-byte instruction that traps for sure. The "copy enough and jump" idea seems like a good way to go. Thanks for the input! Oct 26 '19 at 20:53
  • I didn't argue about any of that. Did I? Anyway, your 'fast path' doesn't bring any performance gain worth the effort - keep in mind, INT and IRET is incredible sluggish on an 8088. First step ahead for any solution would be a complete listing of all instances and listing them by variation.
    – Raffzahn
    Oct 26 '19 at 21:01
  • 2
    I added statistics to the question. Oct 26 '19 at 21:43

There seems to be no known software product implementing the idea yet - possibly the idea is not viable in the end. To find it out, I started a project on github to develop said 286 emulator. There is nothing interesting to see yet, though.

The idea of this answer is to provide a link to my GitHub project. If that project yields anything useful, I will edit this answer and accept it. If the project fails, I will document the reason of the failure and accept one of the answers already given.


Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.