Maximum Overkill
Maximum Overkill
Posted on July 24, 2026 by Michal Necasek
A reader pointed at the 1992 game Comanche: Maximum Overkill by NovaLogic as a possible unreal mode user—since the original game release was known to be problematic.
Screenshot of original 1992 Comanche game when it is not horribly crashing
I examined the game and found no unreal mode usage, just a rather unusual homebrew DOS extender (which, among other things, uses expand-down segments for data, presumably in order to catch null pointer accesses).
The original 1992 Comanche release is extremely picky about memory managers. The game refuses to work with EMM emulators and crashes when EMM386 is active. The game may also hang or reboot the system when HIMEM.SYS is not loaded (the problem seems to be system configuration dependent).
These issues are clearly noted in the Comanche documentations, but at the same time they’re also all signs of a substandard DOS extender.
On top of that, the game authors accomplished a remarkable feat: By only loosely following Intel’s instructions on how to enter protected mode, NovaLogic managed to write a game which worked on the then-existing 386 and 486 processors, but under some circumstances failed to run on Pentium and later processors.
To NovaLogic’s credit, these problems were fixed, and an updated CD-ROM release from 1994 works significantly better.
Here is the original 1992 Comanche code sequence which switches from real to protected mode:
1318:0b96 fa cli 1318:0b97 2e 0f 01 0e 62 00 sidt [cs:00062h] 1318:0b9d 2e 0f 01 06 5a 00 sgdt [cs:0005ah] 1318:0ba3 66 9c pushfd 1318:0ba5 66 2e 8f 06 68 00 pop dword [cs:00068h] 1318:0bab e8 cb 08 call 01479h 1318:0bae 0f 20 c0 mov eax, cr0 1318:0bb1 66 83 c8 01 or eax, byte 000000001h 1318:0bb5 0f 22 c0 mov cr0, eax 1318:0bb8 b8 18 13 mov ax, 01318h 1318:0bbb 8a d4 mov dl, ah 1318:0bbd c1 e0 04 sal ax, 004h 1318:0bc0 c0 ea 04 shr dl, 004h 1318:0bc3 2a f6 sub dh, dh 1318:0bc5 05 00 00 add ax, 00000h 1318:0bc8 80 d2 00 adc dl, 000h 1318:0bcb 2e a3 44 00 mov word [cs:00044h], ax 1318:0bcf 2e 89 16 46 00 mov word [cs:00046h], dx 1318:0bd4 2e 0f 01 16 42 00 lgdt [cs:00042h] 1318:0bda b8 18 13 mov ax, 01318h
At address 1318:0BB5, the game enables protected mode by writing to control register CR0. The following instructions then execute in protected mode.
A key detail is that contrary to Intel’s recommendations, the game does not reload segment registers, and does not perform any jumps prescribed by Intel. As a consequence, the game continues running with real-mode CS after switching to protected mode.
The trouble hits at address 1318:0BCB. The game tries to write to memory using a CS segment override. Ironically, the game could just as easily have used an ES segment override because at that point, ES and CS (but not DS!) point to the same segment.
This code worked just fine on 386 and 486 processors, which is obviously how it made it into a commercial game release (because that’s all that was available when the game was released). But the same code often failed on the Pentium, released just a few months later, in March 1993 (the game files are dated November 1992).
How is that possible? In the Pentium, Intel made a subtle but crucial architectural change.
Why CS Is Weird
As discussed elsewhere, real mode works very much like Ring-0 protected mode, with a few relatively minor differences that relate primarily to segment register loading and interrupt dispatching.
However, Intel needed to solve an important problem for which there was no obvious clean solution. In real mode, the CS segment must be writable, yet in protected mode, a writable code segment simply does not exist. This is not such a problem after CPU reset, because the initial CS value can be more or less anything. But it is a serious problem after returning to real mode from protected mode—because by definition, protected mode only loads code selectors into CS, and those can’t be writable. Yet after returning to real mode, CS somehow must be made writable, otherwise real-mode code cannot function.
According to Robert Collins (usually a very reliable source), Intel’s original solution was to modify the CS access rights on any far control transfer (i.e. every time CS got reloaded) in real mode to 93h, which indicates a read-write data segment. This is how the Intel 80386 and 80486 worked.
In the Pentium, the processor does not change the CS access rights in real mode but rather actively ignores them, allowing CS to be writable.
The difference, obviously, is what happens immediately after a transition to protected mode. On the 386/486, CS will be writable because the access rights were (practically guaranteed to have been) modified to read/write. On the Pentium and later processors, the processor will enforce the existing access rights, usually set to 9Bh (an executable and readable but not writable code segment) after a previous transition from protected to real mode. The CS segment will not be writable.
Once again, this is an example of a bug that could not have been caught by any amount of testing, unless perhaps the authors had access to pre-production Pentium processors… but that’s not something game developers usually need.
NovaLogic also released the game at about the worst possible time. If it had been a few months later, the problem with Pentium processors would have been likely noticed and fixed before the release. If it had been a few months earlier, there is some chance that Intel could have found the problem and either changed the Pentium behavior or notified NovaLogic that their code is about to stop working—obviously having a popular game break on a new processor wasn’t in Intel’s interest either.
Documentation
At first, I was about to put the blame entirely on the NovaLogic developers for ignoring Intel’s documentation, which says the following (Pentium Pro Family Developer’s Manual Volume 3: Operating System Writer’s Guide, December 1995, section 8.8.1, Switching to Protected Mode): Immediately following the MOV CR0 instruction, execute a far JMP or far CALL instruction. (This operation is typically a far jump or call to the next instruction in the instruction stream.)
Note that a far jump executed in protected mode is guaranteed to load the CS attributes from the GDT, fully the under control of the software performing the mode switch.
But the Comanche developers presumably did not possess a time machine, hence expecting them to read a 1995 manual back in 1992 could be considered unfair. What, then, did the existing 386 and 486 documentation available in 1992 say? Surely it said the same thing?
Well… not really. This is section 10.4.3, PE Bit, from the 1992 Intel486 Microprocessor Family Programmer’s Reference Manual:
Protected mode is entered by setting the PE bit in the CR0 register. Either an LMSW or MOV CR0 instruction may be used to set this bit (the MSW register is part of the CR0 register). Because the processor overlaps the interpretation of several instructions, it is necessary to discard the instructions which already have been read into the processor. A JMP instruction immediately after the LMSW instruction changes the flow of execution, so it has the effect of emptying the processor of instructions which have been fetched or decoded. After entering protected mode, the segment registers continue to hold the contents they had in real address mode. Software should reload all the segment registers. Execution in protected mode begins with a CPL of 0.
Note that the 1992 manual required a JMP instruction, but the purpose was to flush the prefetch queue and ensure that all following instructions were decoded and executed in protected mode; for this purpose, a near jump was sufficient. On newer processors (starting with the Pentium), branch prediction avoids prefetch flushes, so the microarchitecture had to solve the problem differently (e.g. by the MOV CR0 instruction causing a prefetch queue flush).
The 1992 Intel manual states that “software should reload all the segment registers”, but does not indicate that this must be done. The 1995 manual is much less ambiguous. Note that a far jump serves two purposes—it flushes the prefetch queue and it reloads the CS register, ensuring that the CS attributes are set to a known value.
Although Intel does not spell it out, CS must be reloaded in protected mode for interrupts and exceptions to function. The reason is that if an exception or interrupt happens while the real-mode CS is still in effect, it is extremely unlikely that execution could continue after an interrupt—because the real-mode CS saved on the stack is more or less guaranteed to be invalid in protected mode. The same goes for other segment registers—the real-mode DS/ES/FS/GS/SS values can be saved in protected mode, but not restored.
#### Switching Back to Real Mode
There is also a question of how Intel documented the process of switching from protected mode back to real mode. The 1986 Intel 80386 Programmer’s Reference Manual in section 14.5, Switching Back to Real-Address Mode, recommends the following: Jump to the real mode code to be executed using a far JMP. This action flushes the instruction queue and puts appropriate values in the access rights of the CS register.
Although this is perhaps not entirely explicit, the documentation clearly says that a far control transfer in real mode will modify CS access rights.
The documentation changed for the Pentium. The 1993 Pentium Processor User’s Manual Volume 3: Architecture and Programming Manual in section 16.6.2, Switching Back to Real-Address Mode, states the following: Jump to the real mode program using a far JMP instruction. This flushes the instruction queue (of the Intel386 and Intel486 processors) and puts appropriate values in the access rights of the CS register. This step is not required on the Pentium processor, however, for downwards compatibility, a far JMP should be included as part of the switching back to real-address mode process.
Once again the language is not entirely clear, but implies that a far jump is not required because the Pentium needs neither the explicit prefetch queue flush nor the CS attribute fix.
Testing
My initial theory was that the 1992 Comanche release should work on 386 and 486 systems but fail on all Pentium and newer CPUs. Unfortunately, testing quickly destroyed that theory.
I found that the original Comanche release works on an Alaris Cougar board with the built-in IBM 486BL3 as well as Intel 486 DX4 OverDrive (as expected), but also works with an Intel Pentium OverDrive, which was much less expected.
Next I found that the game also works on an Intel Advanced/ATX (Thor) board with Pentium 133 MHz, Pentium OverDrive 166 MHz (PODP3V166), as well as Pentium MMX OverDrive 200 MHz (PODPMT66X200) CPUs.
At the same time, the original game fails on an IBM ThinkPad 760XL (Pentium MMX 166 MHz), and it also fails on a somewhat newer Intel AN430TX board with a Pentium MMX 166 MHz processor.
To be clear, I verified that the problem is exactly the one described above—trying to write to memory using a CS segment override. Patching the original game executable to use ES segment overrides instead of CS avoids the problem.
At any rate, it’s not the case that the game works or fails on Pentium CPUs in general. It works on some Pentium-based systems, yet fails on others. How is that possible?
Initial CPU State and BIOS
I strongly suspect the difference is caused by the system’s BIOS.
On Intel CPUs, the documented initial state of CS attributes after reset or INIT is “Present, R/W, Accessed”—exactly the same as DS/ES/SS/FS/GS attributes. The catch is that (as explained above) once a Pentium or newer CPU reloads CS in protected mode, it is no longer possible to return to the initial CS attributes.
That is, “transferring execution to a segment which is not executable” is one of the causes of a #GP exception. Because it is impossible to load a non-code segment into CS in protected mode, it is also not possible to make CS writable (since writable code segments don’t exist — the attribute bit which means “writable” for data segments means “readable” for code segments, since code segments may be executable but not readable).
386+ BIOSes typically need to briefly switch to protected mode. It’s the only way to access extended memory, which may also include accessing the APIC registers. However, switching to protected mode does not necessarily require loading CS in protected mode. In fact many older BIOSes run short (or short-ish) code sequences in protected mode while only using short jumps. Newer BIOSes tend to run some 32-bit code during initialization, and that does require loading CS in protected mode.
The upshot is that on some systems with a Pentium or newer CPU, when an operating system boots, CS still contains the initial reset attributes and therefore the code segment will be writable immediately after switching to protected mode. On other systems, the BIOS reloads CS in protected mode, inevitably making CS read-only. After switching to protected mode, the code segment will not be writable.
Note again that with a 386/486 CPU, the behavior is different. Regardless of what the BIOS does in protected mode, CS attributes get changed to read/write on real-mode far control transfers, hence the code segment is effectively guaranteed to be writable after transition to protected mode.
Walking on Eggshells
On 386/486 systems, the original 1992 Comanche release works reliably. On Pentium and newer systems, it may work if the BIOS is “cooperative” and leaves the CS attributes in their reset state.
However, the state of such systems is somewhat unstable. Running more or less any protected-mode program will change the CS attributes, and Comanche will then immediately crash. Needless to say, this includes running Comanche itself! Running the game once works, but running it a second time does not. I imagine this may have caused some head scratching.
To verify the behavior I wrote a small test program which replicates what Comanche does—attempts to write through a CS override immediately after switching to protected mode. Unlike the game, my program is able to handle a #GP fault which occurs on the write and exits cleanly.
The program allowed me to verify that yes, 386/486 CPUs can reliably write through CS immediately after switching to protected mode. Pentium systems may be able to do so, depending on the BIOS. But even on such systems, running almost any protected-mode software irreversibly changes the CS attributes; writing through CS then no longer works in protected mode, and Comanche crashes on startup.
Summary
The Comanche: Maximum Overkill game released in late 1992 uses an unsafe code sequence which reliably works on 386/486 systems, but sometimes or always fails on Pentium processors released in early 1993, as well as on later Intel CPU models.
The problem is caused by a subtle change in handling real-mode CS attributes. While 386 and 486 CPUs change the CS attributes to read/write when reloading CS in real mode, Pentium and later systems ignore the attributes in real mode but do not change them.
As a consequence, immediately after entering protected mode, CS is reliably writable on 386/486 CPUs, but may or may not be writable on Pentium and later systems, depending on whether CS had been reloaded in protected mode since reset or not.
In practice, the game ran on many older Pentium systems whose system BIOS never changed CS in protected mode… but only once. An attempt to start Comanche second time is guaranteed to fail on Pentium and later CPUs.
The old game executable can be easily patched to use ES instead of CS segment overrides, but the original release is still extremely picky about the memory managers used. An updated Comanche executable works significantly better.
Addendum One
The behavior of the 80386 is well explained in a confidential January 1986 Intel memo titled Returning to real mode on the 80386. The memo says: The CS is a special case: since it is not possible since it is not possible to make CS writeable while in protected mode, an architectural feature reloads real mode attributes into the CS descriptor during real mode far jumps.
Addendum Two
The above article describes the behavior of Intel CPUs. I suspect that modern AMD CPUs behave slightly differently, in a manner that is not identical to either the old 386/486 behavior or the newer Pentium behavior.
This entry was posted in 486, Bugs, DOS Extenders, Intel, PC history, Pentium, x86. Bookmark the permalink.
28 Responses to Maximum Overkill
1. Volkert says:
July 24, 2026 at 2:20 pm
Really cool read, thank you! 🙂
NovaLogic later released a patch for the game that makes it work with EMM managers, and most likely fixes this Pentium issue too. I think it fixes is by just patching in the DOS/4GW DOS extender that most other protected mode DOS games used, but I’m not sure.
The patch file is called Comanche-Maximum-Overkill\_Patch\_DOS\_EN.zip. The site that hosts the patch is an Abandonware site, so I don’t know if I’m allowed to link to it directly, but the patch itself can also be found as an attachment in this GitHub thread post: <https://github.com/dosemu2/dosemu2/issues/1703#issuecomment-1180907421>
SHA-256 checksum and VirusTotal scan result (still, use at your own risk!): <https://www.virustotal.com/gui/file/053919a97dad64b8134c4f2b6765f671a98ff52a977e9775686225c2fa6875ad>
2.
CL says:
July 24, 2026 at 9:47 pm
Two common implementations:
1) Treat CR0.PE=0 as W=1 for CS.
2) Have an actual internal CS.ar.W bit, which the CS descriptor from memory lacks.
Both have their pros and cons.
At sandpile it’s visible in two places: RESET/INIT state, and SMM entry state – look for “writeable”.
3.
CL says:
July 24, 2026 at 9:52 pm
And yes, clarification for 1) above: EFLAGS.VM=1 – same W=1 effect, of course.
4.
Michal Necasek says:
July 25, 2026 at 8:28 pm
At least on my Ryzen machine, V86 mode does not seem to need any kind of special handling because the CPU puts 0xF3 into all segment register attributes when entering V86 mode (the AMD IRET documentation lies about this from what I can tell). But maybe some CPUs work differently?
There is one other thing that bugs me about the AMD documentation which is that AMD (ARM Vol 2 section 14.1.3, Processor Initialization State) suggests that after reset, CS attributes are set to “Executable/Readable Code Segment”. I don’t have any easy way to check if the docs lie or if the behavior is really different from Intel CPUs.
5.
rasz\_pl says:
July 26, 2026 at 10:38 am
Just recently in a discussion on HN about fantastic nand2mario 386 clone progress rep\_lodsb mentioned <https://news.ycombinator.com/item?id=47818225> observing on his 286
\>executing three pre-decoded instructions without a jump after setting PE causes a triple fault for some reason
and
\> Intel documented that the very first instruction after enabling protected mode had to be an “intra-segment” (not inter-segment) jump, to flush the prefetch queue.
contemporary 286-Pentium bioses I looked at all do this, but I just so happened to be disassembling Atom N270 945GSE Mini-ITX industrial board Phoenix bios from 2010 that does not
seg000:FD56 Unreal\_FFD56 proc near
seg000:FD56 lgdt fword ptr cs:\[bx\]
seg000:FD5A mov eax, cr0
seg000:FD5D or al, 1
seg000:FD5F mov cr0, eax
seg000:FD62 jmp short $+2
seg000:FD64
seg000:FD64 loc\_FFD64:
seg000:FD64 mov ax, 8
seg000:FD67 mov ds, ax
seg000:FD69 mov es, ax
seg000:FD6B mov eax, cr0
seg000:FD6E and al, 0FEh
seg000:FD70 mov cr0, eax
seg000:FD73 jmp short $+2
seg000:FD75
seg000:FD75 loc\_FFD75:
seg000:FD75 xor ax, ax
seg000:FD77 mov ds, ax
seg000:FD79 mov es, ax
seg000:FD7B retn
just short jumps working fine on Atom. This surprised me quite a bit. Now after reading your post am I understanding this right that this works because no memory (especially cs indexed) is being touched?
6.
Michal Necasek says:
July 26, 2026 at 2:54 pm
My understanding is that on the Pentium and later, an intra-segment jump is pointless because the mov to CR0 already flushed things (and a jump might not cause a flush thanks to the branch predictor).
But yes, I am 99% certain that on the 386/486, the point of the jump was to flush the pipeline and make sure that there aren’t any partially executed instructions that access memory, although there could be other instructions that behave differently in real vs protected mode (except real mode and Ring-0 protected mode is not that different). I believe this is really much more of an issue when enabling/disabling paging. If the instructions immediately following the enabling of protected mode just do some arithmetic on registers, then flushing or not probably won’t make a difference because the results will be identical in real and protected mode anyway.
Part of it was no doubt future proofing — when the 386 was done, no one knew how deep the pipeline on some upcoming compatible CPU might be. The flush induced by a JMP was safe and avoided potential future problems. Intel knew that some instructions needed to be re-decoded/re-executed in protected mode and it was far simpler and safer to tell developers “always do the jump” than providing some super detailed documentation about exactly what’s safe and what’s not. It’s also a case where Intel would not want to document the internals in too much details because that would tie their hands in the future.
The Comanche game does not do any jumps and it works on 386/486 machines just fine.
For reference, the MR. BIOS on my Alaris Cougar board always follows toggling the PE bit by either ‘jmp short $+2’ or another short jump, in one case a near return. This BIOS works with 386/486 class CPUs as well as Pentium OverDrive.
Whether inter-segment (far) jumps are necessary in protected mode or not is a whole another kettle of fish. It is pretty clear that for short sequences, including enabling unreal mode, they’re not. But for establishing a protected-mode environment which either uses 32-bit code and/or can handle interrupts/exceptions, a far jump is an absolute requirement.
7.
Josh Rodd says:
July 27, 2026 at 4:18 am
The Pentium situation is interesting, because it means the state of the CPU immediately after a RESET isn’t the same as after doing a MOV CR0,CR0||-2 to leave protected mode… a little bit reminiscent of how an 80286 can’t return to real mode without a reset.
So a Pentium really has two kinds of real mode: two kinds of real mode which function identically, except only one can get you into the next thing; and then it has two kinds of protected mode, one where the current value of CS: is writeable, and one where it never can be.
(And yes, my own protected mode code I wrote tended to use the CS: that was already setup since all it was doing was moving a bit of data around, and doing a far jump made the operation that was already slow even slower. I never bothered thinking about writing with CS).
8.
Michal Necasek says:
July 27, 2026 at 2:57 pm
Yes, the Pentium behavior is indeed interesting because the reset state of CS attributes persists until it’s changed, but once it is changed, there’s no way to get back without resetting the CPU again (either a hard reset or INIT). Not really typical for x86 CPUs.
Actually I have not explored this but there could be a way to recover the original CS attributes using SMM.
A lot of this goes back to the fact that returning from protected mode was never properly architected on the 386. It was clearly a late change made with very minimal microcode modifications, and it didn’t take people long to figure out unreal mode and then Intel was stuck with it. Old (1985) 386 docs said that CR0.PE can be reset but clearing it “will have unpredictable effects”. It is possible that the only hardware change Intel made was reloading the CS attributes on real mode far control transfers, because that was the one thing software could not do.
9.
CL says:
July 27, 2026 at 8:02 pm
But wait… there is more! (With x86, there almost always is, isn’t there?)
Although sreg.{sel,base,lim,ar} are often referred to as the segment descriptor cache, that nomenclature is… well, keep reading. 🙂
The P5 actually had what was called the SDC – yep, you guessed it… a Segment Descriptor Cache. An actual 16-entry 2-way associative cache with LRU replacement policy, for recently loaded segment descriptors, operating in Protected Mode. The result was a hit latency of 2 cycles, and a miss latency of 11 cycles. The primary benefactor was 16-bit PM, though the SDC also operated in 32-bit PM.
The P6 shipped without this sort of thing… which contributed to its not-so-great performance in the presence of frequent segment descriptor loads… until that got addressed later on, in the P2 proliferation (“sreg renaming”).
The P5 SDC was visible via data breakpoint #DBs (not triggering for cached entries) and LGDT (invalidated it – the a recommendation that debuggers issue LGDT) and performance counter events (accesses vs hits), but in general it tried to maintain consistency with the L1 data cache.
So yeah… x86 segmentation… what’s not to like, eh? 🙂
10.
CL says:
July 27, 2026 at 8:05 pm
And yes, this also impacts CPL – i.e. using SS.ar.PL versus using a dedicated CPL “register”. Both of those implementations have been used in practice.
11.
CL says:
July 27, 2026 at 8:18 pm
And hey, while we’re at it… TR.ar.V is another interesting case.
It should act like a valid bit, and cause a #TS(0) exception on all implicit TSS accesses (stack switch, task switch, TSS32.IOPB, or TSS32.IRB).
Most processors don’t implement that behavior though.
12.
Andy Frueh says:
July 28, 2026 at 12:22 am
Probably a stupid question, but I noticed that all the pentiums that succeeded were Overdrives, and the two that failed were “real” Pentiums. I know they had to do some design changes to let a pentium-class processor work in a 486 mobo…could that possibly account for any of the differences (vs the BIOS theory)?
13.
Michal Necasek says:
July 28, 2026 at 12:55 pm
That was my first thought too, but no — check the Intel Thor board results. A regular 133 MHz Pentium (P54C) worked too.
Also note that yes, the PODP5V (P24T) aka Socket 2/3 Pentium OverDrive is an odd duck, the Socket 5/7 OverDrives were just regular P54C/P55C Pentiums with a voltage regulator added.
I’m fairly convinced that newer BIOSes tend to run 32-bit code during POST and that forces them to reload CS in protected mode. Once that’s done, then as the article describes, it is no longer possible to return to the initial CS attribute state as it was after reset, and CS will never be writable in protected mode.
14.
Michal Necasek says:
July 28, 2026 at 1:05 pm
Yes, I know about the Pentium SDC… probably a Windows 3.x optimization? I can imagine that although it worked in 32-bit mode too, it didn’t do that much because there’s no need for frequent segment register reloading in 32-bit code. But in 16-bit code definitely yes. Far calls everywhere, DS/ES reloaded all the time.
With the PPro, Intel’s big miscalculation was that by ’96 everyone would ditch the horrible old 16-bit code in order to preserve their own sanity. But then Windows 9x said NOPE, you have to wait another five years.
15.
Michal Necasek says:
July 28, 2026 at 1:06 pm
I’m aware that these two implementations exist, but do you know how they can be distinguished in software?
16.
Josh Rodd says:
July 28, 2026 at 11:33 pm
Is there anywhere in the SMM structure this flag bit would be hiding? I can’t find anything.
17.
Alex Czarnowski says:
July 29, 2026 at 9:38 am
Dear Michal, thank you for taking your time to investigate good ol’ Comanche game based on my poor memory and single comment 🙂
So it wasn’t unreal mode usage just strange entry into protected mode an issue with first release. Good to know after all those years. As legacy BIOSes grew in size, exploiting protected mode before any operating system code has been run become a must. At the end we’ve got over complicated UEFI firmware but that is a completely different story. Once again, thanks for your investigation.
18.
Michal Necasek says:
July 29, 2026 at 10:48 am
Right, not unreal mode, although the original Comanche was very picky about the DOS configuration (HIMEM/EMM386 etc) in a way very similar to unreal-mode software. On top of that their code might not run on Pentium systems at all. My guess is that the game worked reliably on a 386/486 with the right DOS config. On a Pentium machine not so much, because if it worked once then it crashed the second time.
19.
Alex Czarnowski says:
July 29, 2026 at 10:45 pm
Indeed I have been running the game originally on 486 (DX2 probably or so). Anyway it is always great to debunk another myth from DOS times.
20.
CL says:
July 30, 2026 at 12:57 am
An explicit W bit for CS might appear in the SMM SSM… or… it might not.
If dumped by SMI, then RSM would still need to reconcile it with PE/VM.
So it’s not actually worth dumping it in the first place.
In the long run the proper approach is of course to migrate the legacy complexity from HW/microcode to SW/emulators – you know… x86-S done right, and brought to its natural conclusion, rather than resisted by the cargo cult crowd… 😉
21.
Josh Rodd says:
July 30, 2026 at 3:59 am
A rather brief check on a machine that seems to have the “CS is still writeable after the BIOS boots” shows no difference in my attempt to dump the SMM SSM versus after entering protected mode, doing a far jump, and then exiting to real mode.
So this must be some bit that does not get covered by SMM. The real question is why it exists at all. After all, when in real mode, it’s not necessary, and in protected mode it’s not necessary either.
22.
Michal Necasek says:
July 30, 2026 at 12:11 pm
Intel/AMD could really help that by opening up their validation suites. From my own experience with x86 emulator work I know that there are very, very many dark corners in the x86 architecture and nailing down the exact behavior is real difficult. There are gigantic holes in the official documentation, like flag state after instruction execution (yes, I know in practice there are several different ways microarchitectures do it). Every time the documentation says “undefined” there’s a big fat red question mark, because in practice undefined does not even remotely mean unpredictable, and if it’s predictable then software may well intentionally or unintentionally rely on it.
23.
Michal Necasek says:
July 30, 2026 at 7:59 pm
So what are the CS attributes saved in the SSM? Can you tell? And do you have code to test SMM on regular hardware?
Also, is CS actually writable in SMM?
The hypothetical CS.ar.W bit would exist precisely so that it could be set upon entering real (and V86) mode and cleared when exiting. Because the CPU somehow needs to know that CS is writable. The 386/486 simply checks the actual attributes. The Pentium and later does not, which means some other mechanism must be used. The CPU could check the current mode when writing through CS, or it could have a CS.ar.W bit that is set when entering real/V86 mode. I imagine this would be faster and simpler because all segment registers could be treated symmetrically.
As Christian says, the CS.ar.W bit would not need to be saved because it should precisely reflect the current PE/VM state (something like ‘NOT (PE XOR VM)’, assuming that VM can’t be set in real mode). So mode transitions can always derive CS.ar.W based on other state.
24.
CL says:
July 30, 2026 at 10:44 pm
\> Also, is CS actually writable in SMM?
Historically… yes… by virtue of PE=VM=0 after entry.
Nowadays… no… because of Intel’s SMM Protected Mode feature, which – as its name suggests – causes SMM entry to go straight to Protected Mode, rather than Real Mode. (This got added in the HSW/BDW generation, iirc.)
Recall that classic SMM has a MCE gap, because SMM entry clears CR4.MCE and does not establish a SMM-based #MC handler. As a result, any #MC that occurs after SMM entry but before the SMM handler can establish its own #MC handler and enable CR4.MCE again, leads to shutdown – a rather undesirable outcome for RAS.
SMM\_MCA\_CAP.SMM\_PROT\_MODE (MSR 0x17D bit 54) => it’s supported
SMM\_PROT\_MODE\_BASE.ENABLE (MSR 0x9D bit 0) => it’s enabled
SMM\_PROT\_MODE\_BASE.SMMSEG\_PA (bits 31:8) => SMMSEG phys addr
SMMSEG structure:
dword +0 = reserved
dword +4 = enabled features (bit 0 = enabled (must be 1), bit 1 = CR4.MCE)
dword +8 = GDTR.limit (using lower 16 bits; upper 16 bits are reserved)
dword +12 = GDTR.base offset from SMBASE
dword +16 = CS.selector (if 0x0000, then 0x0008 will be used – i.e. no NULL)
dword +20 = reserved
dword +24 = ESP offset from SMBASE
dword +28 = reserved
dword +32 = IDTR.limit (using lower 16 bits; upper 16 bits are reserved)
dword +36 = IDTR.base offset from SMBASE
It’s fairly obvious where in memory SMMSEG should vs shouldn’t go. 😎
Now, I don’t know why the above information is hard to find in public docs. Perhaps I should add it to sandpile’s SMM page. Including a look at what AMD does, of course.
25.
Josh Rodd says:
July 31, 2026 at 11:54 am
Tested it on a Pentium MMX class system (old Compaq laptop), definitely pre-bdw/hsw. The only way to trigger it is to use real mode APM to invoke SMM (and then immediately exit it), and inspect the memory range where it stores the SMM segment.
It is possible that performing a suspend and resume is actually resetting the CPU, but supposedly it doesn’t (it just slows the clock rate down a lot). Beyond that I can’t say.
26.
Michal Necasek says:
July 31, 2026 at 1:18 pm
What model is that? I might have the technical reference for it, I’d just be curious if it has any detail.
I suspect laptop OEMs were much more eager to use SMM as soon as they could because they had all those goodies like brightness/volume control and power management, which almost certainly needed SMM in order to be able to work in an OS-independent way. Especially in the era before ACPI. The 386SL/486SL processors had SMM for a loooong time.
27.
CL says:
August 1, 2026 at 2:27 am
Fwiw, SMM-based power management does not actually work for STD/STR, at all, period.
<https://www.sandpile.org/x86/legacy.htm>
“\[T\]he processor is subject to the limitations described in SDM Vol 1 pre-075, section D.3.5.”
<https://kib.kiev.ua/x86docs/Intel/SDMs/253665-074.pdf>
It’s too bad Intel decided to remove D.3.5. altogether in SDM 075, instead of moving it to a legacy section of the manual. Oh well.
PS: Interestingly enough… this exact problem did come up in my interview with Google back in early 2007… and my response may have helped in actually getting hired… LOL
28.
Josh Rodd says:
August 2, 2026 at 6:35 pm
The only reason I used that laptop is because it’s easy to get SMM on it and it leaves the SSM accessible from main memory. I’ll check the model next time I have access to it.
A rather glaring problem is I have no idea if the CPU goes through a RESET or not. Is there a way to tell?
Leave a Reply
Your email address will not be published. Required fields are marked \*
Comment \*
Name \*
Email \*
Website
Δ
This site uses Akismet to reduce spam. Learn how your comment data is processed.

