VOGONS


Analyzing old CPUID code

Topic actions

Reply 20 of 27, by mkarcher

User metadata
Rank l33t
Rank
l33t
jakethompson1 wrote on 2023-04-17, 19:41:

It would be really radical and chipset specific, but could you switch the shadowed BIOS from read-only to read-write and temporarily overwrite F000:FFF0 to point somewhere else where you would like to go? As an aside, I wonder how hard of a reset (all the way up to physical reset switch or even power off) is required to force shadowing off and make the system re-execute the BIOS from ROM as part of a reboot.

Assuming BIOS shadow is enabled, this would in fact work (but as you say, it is chipset specific). The kind of processor reset you issue to "switch out of 286 protected mode" does not reset the chipset configuration and leaves shadowing enabled. A pulse on the reset pin of the chipset will disable shadow, so pushing the reset switch is enough on most (likely all) mainboards to force execution from ROM. Pushing the reset switch also clears the "system already initialized" bit in the keyboard controller, and this makes the BIOS ignore the "shutdown code byte" in CMOS at index 0Fh.

Reply 21 of 27, by jakethompson1

User metadata
Rank Oldbie
Rank
Oldbie
mkarcher wrote on 2023-04-17, 20:47:
jakethompson1 wrote on 2023-04-17, 19:41:

It would be really radical and chipset specific, but could you switch the shadowed BIOS from read-only to read-write and temporarily overwrite F000:FFF0 to point somewhere else where you would like to go? As an aside, I wonder how hard of a reset (all the way up to physical reset switch or even power off) is required to force shadowing off and make the system re-execute the BIOS from ROM as part of a reboot.

Assuming BIOS shadow is enabled, this would in fact work (but as you say, it is chipset specific). The kind of processor reset you issue to "switch out of 286 protected mode" does not reset the chipset configuration and leaves shadowing enabled. A pulse on the reset pin of the chipset will disable shadow, so pushing the reset switch is enough on most (likely all) mainboards to force execution from ROM. Pushing the reset switch also clears the "system already initialized" bit in the keyboard controller, and this makes the BIOS ignore the "shutdown code byte" in CMOS at index 0Fh.

I noticed the HWINFO utility (from Mumak, here on vogons) comes with a readme file that talks about ways of obtaining the CPUID from pre-CPUID processors, including temporarily overwriting the BIOS. It also describes forcing a reset with A20 disabled. I suppose that would cause the reset vector to go to 0xffefffff, which might point to a RAM location you can control? (it describes the system having a "fully terminated bus" I guess to pull down those unused, very high address lines to zero?)

Reply 22 of 27, by mkarcher

User metadata
Rank l33t
Rank
l33t
jakethompson1 wrote on 2023-05-23, 01:21:

I noticed the HWINFO utility (from Mumak, here on vogons) comes with a readme file that talks about ways of obtaining the CPUID from pre-CPUID processors, including temporarily overwriting the BIOS. It also describes forcing a reset with A20 disabled. I suppose that would cause the reset vector to go to 0xffefffff, which might point to a RAM location you can control? (it describes the system having a "fully terminated bus" I guess to pull down those unused, very high address lines to zero?)

Forcing a reset with A20 disabled is indeed one of the classic methods, known since 25 years (which doesn't make it bad or anything like that). You are neary right about the effect on the reset vector. It's not 0xffefffff, but 0xffeffff0, yet you still inferred the effect of masking the A20 line correctly. In most classic systems, you can not control that location. The idea is that no hardware responds to trying to read from that address. Keep in mind that on classic ISA, bus cycles are not explicitly acknowledged by the target, but complete after a "sensible amount of time" by default. Digital ICs in early home computers are based on TTL technology. Those chips are designed in a way that an input pin that is not actively connected to anything reads as "high". Modern systems are compatible, even if CMOS technology has no longer "high as default" as intrinsic property. You achieve that by including resistors to +5V on those lines ("pull-up") either as dedicated component on the mainboard or as part of the input circuit in the chip.

This means: On a A20-masked reset, the chipset initiates a bus cycle to read 0xffeffff0 (or 0xeffff0 on 24-bit address systems), and does not drive the data lines on the bus. The expectation is that some device responds to the read request and puts the intended data on the bus. If no device responds, the "high by default" convention applies on most 286/386/486 systems, and the processor reads bytes of FF as opcodes. In the x86 architecture, FF is a "group opcode", which means that some bits of the second byte are also contribute to selecting the instruction. If the next byte is also FF, the three relevant bits are also set. The resulting instruction is then written as FF/7. Interestingly, only FF/0 to FF/6 are valid instructions, and FF/7 is an invalid instruction. If the reset vector points to an invalid opcode, the first thing the processor does after fetching the reset vector is executing the "invalid opcode" exception. The address of the exception handler is in RAM you can control, and that's how you re-gain control after a reset with A20 masked.

Reply 23 of 27, by jakethompson1

User metadata
Rank Oldbie
Rank
Oldbie
mkarcher wrote on 2023-05-27, 07:35:
jakethompson1 wrote on 2023-05-23, 01:21:

I noticed the HWINFO utility (from Mumak, here on vogons) comes with a readme file that talks about ways of obtaining the CPUID from pre-CPUID processors, including temporarily overwriting the BIOS. It also describes forcing a reset with A20 disabled. I suppose that would cause the reset vector to go to 0xffefffff, which might point to a RAM location you can control? (it describes the system having a "fully terminated bus" I guess to pull down those unused, very high address lines to zero?)

Forcing a reset with A20 disabled is indeed one of the classic methods, known since 25 years (which doesn't make it bad or anything like that). You are neary right about the effect on the reset vector. It's not 0xffefffff, but 0xffeffff0, yet you still inferred the effect of masking the A20 line correctly. In most classic systems, you can not control that location. The idea is that no hardware responds to trying to read from that address. Keep in mind that on classic ISA, bus cycles are not explicitly acknowledged by the target, but complete after a "sensible amount of time" by default. Digital ICs in early home computers are based on TTL technology. Those chips are designed in a way that an input pin that is not actively connected to anything reads as "high". Modern systems are compatible, even if CMOS technology has no longer "high as default" as intrinsic property. You achieve that by including resistors to +5V on those lines ("pull-up") either as dedicated component on the mainboard or as part of the input circuit in the chip.

This means: On a A20-masked reset, the chipset initiates a bus cycle to read 0xffeffff0 (or 0xeffff0 on 24-bit address systems), and does not drive the data lines on the bus. The expectation is that some device responds to the read request and puts the intended data on the bus. If no device responds, the "high by default" convention applies on most 286/386/486 systems, and the processor reads bytes of FF as opcodes. In the x86 architecture, FF is a "group opcode", which means that some bits of the second byte are also contribute to selecting the instruction. If the next byte is also FF, the three relevant bits are also set. The resulting instruction is then written as FF/7. Interestingly, only FF/0 to FF/6 are valid instructions, and FF/7 is an invalid instruction. If the reset vector points to an invalid opcode, the first thing the processor does after fetching the reset vector is executing the "invalid opcode" exception. The address of the exception handler is in RAM you can control, and that's how you re-gain control after a reset with A20 masked.

I see. I was thinking too much about FFFF:0000 and didn't linearize it right. I'm a bit more familiar with 00h disassembling to add [eax],al which doesn't make any sense rather than thinking of 0ffh 0ffh.

This seems like it would have disastrous results if attempted from protected mode thus it wouldn't make sense for the CPU utility running under NT discussed in the other thread where this was coming up.

Reply 24 of 27, by mkarcher

User metadata
Rank l33t
Rank
l33t
jakethompson1 wrote on 2023-05-27, 20:52:

This seems like it would have disastrous results if attempted from protected mode thus it wouldn't make sense for the CPU utility running under NT discussed in the other thread where this was coming up.

Executing a CPU reset switches it to real mode (and that was the only way to get a 286 processor back into real mode). You can perfectly do that from protected mode if you use a kernel driver that masks all interrupts and switches back to protected mode before re-enabling them. But you can't do that from user space in protected mode.

Reply 25 of 27, by jakethompson1

User metadata
Rank Oldbie
Rank
Oldbie
mkarcher wrote on 2023-05-29, 05:34:
jakethompson1 wrote on 2023-05-27, 20:52:

This seems like it would have disastrous results if attempted from protected mode thus it wouldn't make sense for the CPU utility running under NT discussed in the other thread where this was coming up.

Executing a CPU reset switches it to real mode (and that was the only way to get a 286 processor back into real mode). You can perfectly do that from protected mode if you use a kernel driver that masks all interrupts and switches back to protected mode before re-enabling them. But you can't do that from user space in protected mode.

Hello,
I was interested in testing what would appear at FFEFFFF0h on different machines by following along with the Unreal Mode example on osdev in order to read from that physical address from plain DOS.
On a P5HX-A with a Pentium 166, I get BX=FFFFh, so (ignoring that it has CPUID) this technique would work on that machine.
On a 4DMU-HL3S with a Cyrix 486DX2-80, I get BX=??EBh, so it reads from the BIOS ROM, and this technique would not work on that machine.
On two Am386DX-40 machines... it hangs. Know of any 386 errata that would still be around by those late AMD steppings that would cause this?

Attachments

  • Filename
    chkvec.txt
    File size
    2.79 KiB
    Downloads
    16 downloads
    File license
    Public domain

Reply 26 of 27, by jakethompson1

User metadata
Rank Oldbie
Rank
Oldbie

Having rewritten "chkvec" to work on a real 386 (among other things, the osdev example is misleading, you can drastically simplify it with just jmp $+2 and need not reload CS nor have a code segment descriptor) I can confirm that with A20 enabled, and changing it to load [FFFFFFF0h] into BX, I get BX=5BEA, and with A20 disabled, I get BX=FFFF.

However, forcing a reset with A20 disabled and a custom int6 handler installed just reboots the system and doesn't seem to trigger the handler. I tried resets via triple fault and via the keyboard controller. Why wouldn't a chipset reenable A20 whenever the CPU resets or initiates a shutdown cycle? If it doesn't, it seems like accidentally resetting at FFEFFFF0h could easily happen, for machines that boot into DOS with A20 disabled, and then if software attempts to reset the system via the keyboard controller. Is there more of a trick to it than loading INT6 (0000:0018h) with a far pointer to the desired handler?

For my AMIBIOS system, the default int6 handler has code to skip over LOCK prefixes (f0 XX), to handle 286 LOADALL emulation (0f 05), to handle various protected mode table instructions (0f 00), and then a catch-all for other illegal instructions which messes with the 8259 that I have not analyzed.

Reply 27 of 27, by mkarcher

User metadata
Rank l33t
Rank
l33t
jakethompson1 wrote on 2023-06-02, 05:48:

However, forcing a reset with A20 disabled and a custom int6 handler installed just reboots the system and doesn't seem to trigger the handler. I tried resets via triple fault and via the keyboard controller. Why wouldn't a chipset reenable A20 whenever the CPU resets or initiates a shutdown cycle?

A "classic" chipset doesn't control the A20 gate. The A20 gate is a simple AND gate (which might be contained in the integrated chipset), with one input being the A20 line, and the other input being the general purpose output pin P2.0 of the 8042 keyboard controller. In case of a shutdown-induced reset, the keyboard controller doesn't notice that a CPU reset is happening, so it won't raise P2.0 in response to that. Things might be different using the Port92-based "fast A20 gate", though, so I suggest you use the classic KBC method to disable A20 just to be sure.

jakethompson1 wrote on 2023-06-02, 05:48:

If it doesn't, it seems like accidentally resetting at FFEFFFF0h could easily happen, for machines that boot into DOS with A20 disabled, and then if software attempts to reset the system via the keyboard controller.

Resetting the system via the keyboard controller is a new feature of the AT platform, i.e. it has been introduced exactly at the same time as the A20 gate. Software that tries to reset the CPU using the keyboard controller is supposed to know that the keyboard controller has that A20 gate. Primarily, resetting the CPU using the keyboard controller is meant as a means to get out of protected mode - and while the system is operating in protected mode, you better have the A20 gate open!

jakethompson1 wrote on 2023-06-02, 05:48:

Is there more of a trick to it than loading INT6 (0000:0018h) with a far pointer to the desired handler?

Remember that you need to initialize the stack to something sensible. The CPU resets with SS=0 and SP=<undefined> according to Intel's manuals, so just entering the INT6 handler is overwriting 6 bytes at a random location in the low 64K. The BIOS sets SP to 400 or 3FE to use the end of the vector table as temporary stack on a "real reset" before trying to push anything, but we don't get to initialize SP before entering the exception. This means in theory we have to save/restore the low 64KB across the reset, whereas in practice, CPUID utilities seem to get away with ignoring this issue. Just setting the vector at 0:0018h should be enough to regain control.

jakethompson1 wrote on 2023-06-02, 05:48:

For my AMIBIOS system, the default int6 handler has code to skip over LOCK prefixes (f0 XX),

That's default BIOS behaviour since the 286, as the 8088 accepted LOCK before all instructions, whereas the 286 reports an INT6 on instructions that are not LOCKable.

jakethompson1 wrote on 2023-06-02, 05:48:

to handle 286 LOADALL emulation (0f 05)

That's default BIOS behaviour since the 386. An Intel application note about this emulation code is believed to be the first official documentation on LOADALL that leaked into the public.

jakethompson1 wrote on 2023-06-02, 05:48:

to handle various protected mode table instructions (0f 00)

That's not that well known to me. I need to take a look at some AMI 386 BIOS to see what you are referring to. 0F 00 has been introduced with the 286 processor, and isn't supposed to have "incompatibly evolved" since then.

jakethompson1 wrote on 2023-06-02, 05:48:

and then a catch-all for other illegal instructions which messes with the 8259 that I have not analyzed.

That sounds like the generic "unexpected IRQ" handler. It makes little sense to jump to that code from INT 06, as no IRQ is mapped to INT 06 by default, but IIRC I've seen this behaviour in some BIOS code already.