16

When creating a FAT file system, mtools and newer versions of mkfs.fat write a stub MBR partition table to the boot sector. This makes the file system accessible when written directly to media that some operating systems (i.e. Windows) expect to be partitioned, while simultaneously being directly readable as a file system. The partition table defines a single partition that starts on logical sector 0 (cylinder 0, track 0, sector 1 in CHS coordinates) and spans the entirety of the disk. This can be very useful when constructing disk images: you can create a FAT disk image, use mtools to copy files into it without mounting it under the host OS, and then perform a direct block-level copy or mount it under a virtual machine to make it available to a different OS, without ever having to worry about partition tables.

MS-DOS also requires hard disks to be partitioned to be accessible, so one would expect that a disk image created with either of those tools should be readable under MS-DOS. It is not, however. Attempting to access the partition results in an error message:

Not ready reading drive D
Abort, Retry, Fail?

Non-Microsoft versions of DOS seem unaffected: DR DOS and FreeDOS can access the file system just fine.

What is the reason for that? Is there a way to fix it?

6
  • @StephenKitt I always thought ‘can it not do’ is supposed to be parsed as ‘can it {not do}’, not as ‘{can it not} do’. (Compare ‘can you not do it?’ vs ‘can’t you just do it?’) Jul 2 at 16:24
  • “Can’t you just do it?” is different because of the “just”. “Can’t you do it?” and “Can you not do it?” can be largely the same; the difference between “Can you {not do} it?” and “{Can you not} do it?” would be obvious when spoken, but doesn’t come across directly when written; I would say something like “Are you not able to do it?” (for the latter) or “Can you stop doing it?” (for the former) etc. as appropriate to clarify my intent. “Why cannot MS-DOS ...” isn’t correct in any case AFAIK. However “Why can’t MS-DOS ...” would be fine. Jul 2 at 16:29
  • 1
    In this instance though, it’s quite clear IMO that this is “Why {can MS-DOS not} read partitions”. “Why can MS-DOS {not read partitions}” doesn’t make much sense ;-). Jul 2 at 16:30
  • 1
    "Can you not do it?" is correctly used as in "Can you not kick my airplane seat, you obnoxious little snot?". Jul 2 at 17:10
  • For me, 'Why MSDOS cannot ..." would be most clear. Using "cannot" as a single word without spaces makes it clear, and is similar to the contracted form, "Why MSDOS can't ...". Ultimately, however, I presume Stephen's intent is to make it more clear for the site's purposes, which include other users seeking the same information, or other volunteers wanting to help give good answers. Some phrases and wording may result in poor search results or be ambiguous/confusing to the general public.
    – C. M.
    Jul 3 at 20:20
24

This is a consequence of a buggy overflow check.

Internally, MS-DOS uses logical block addressing to access file systems. Since version 4.0, MS-DOS uses 32 bits for sector addresses in order to support disks larger than 32 MiB (i.e. than 65536 sectors). To ensure all sectors are addressable with 32 bits, MS-DOS tries to compute the address of the last sector as (start − 1) + count (where start and count are the last two fields in the MBR partition table entry); if there is an overflow at the end of the calculation, a flag is set at offset 0x1f in a data structure the RBIL calls the ‘drive data table’ that disables all accesses to the disk. Subtracting 1 from the starting sector number prevents triggering overflow when the final sector’s number is exactly 4294967295 (232 − 1)… but it also ensures that if the starting sector is zero, this computation always overflows, as 0 − 1 is 4294967295 in 32-bit unsigned arithmetic. Apparently it didn’t occur to Microsoft that someone might want to create such partitions. In earlier MS-DOS versions there was no problem, as the overflow check instead tested whether the upper 16 bits of the 32-bit result are zero, not whether there was any overflow at all. Since MS-DOS and PC DOS share the same codebase, this bug is also present in the latter (and is still around in later versions only developed by IBM).

One may wonder why Microsoft didn’t drop the check entirely, and why did they keep around code to support partitions ending exactly on the 2 TiB boundary, given that the CHS addressing scheme used by the interrupt 0x13 services to which DOS delegates disk accesses only uses 24 bits (while wasting some of that addressing space by using 1-based indexing for the last coordinate!), so it can only actually address 8064 MiB. Your guess is as good as mine.

Later versions of MS-DOS (those incorporated into Windows 9x) added support for LBA disk access for partitions marked with an appropriate type identifier, which would then make the overflow check serve some purpose… except that the check has no effect on those, making it entirely pointless again. (The extended interrupt 0x13 services use 64-bit addressing, and I have superficially tested MS-DOS 7.x to support 64-bit sector numbers on LBA partitions just fine, by mounting a sparse disk image in a VM.)

As for fixes, you can address the bug either by changing the disk image or by changing DOS.

  • As previously mentioned, MS-DOS 7.x and 8.x are not affected by this issue when accessing disks in LBA mode. If you only care about the file system being readable on (relatively) modern systems, you can simply change the partition type ID to 0x0c (FAT32) or 0x0e (FAT12 or FAT16). This disables the 32-bit overflow check and makes the partition accessible.

  • You can patch IO.SYS to fix the overflow bug. Open IO.SYS (or IBMBIO.COM for PC DOS) in a hex editor, and look for the following byte sequence:

    2D 01 00 83 DA 00 26 03 47 08 26 13 57 0A 73 05
    

    Then replace it with:

    26 03 47 08 26 13 57 0A 73 0B 09 D0 74 07 90 90
    

    Though the same overflow check is also present in MS-DOS 7.x and 8.x (i.e. Windows 9x), the opcodes are encoded differently. Additionally, to patch MS-DOS 8.0 in particular (i.e. Windows Me) you will have to uncompress IO.SYS first. In those versions, look for this sequence:

    83 E8 01 83 DA 00 03 47 08 13 57 0A 73 06
    

    and replace it with:

    03 47 08 13 57 0A 73 0C 09 D0 74 08 90 90
    

    This replaces the following instruction sequence:

    268B4704          8B4704            mov ax, [(es:)bx+?_start_lo]
    268B5706          8B5706            mov dx, [(es:)bx+?_start_hi]
    2D0100            83E801            sub ax, 1
    83DA00            83DA00            sbb dx, 0
    26034708          034708            add ax, [(es:)bx+?_count_lo]
    2613570A          13570A            adc dx, [(es:)bx+?_count_hi]
    7305              7306              jnc ?_ok
    

    with the following:

    268B4704          8B4704            mov ax, [(es:)bx+?_start_lo]
    268B5706          8B5706            mov dx, [(es:)bx+?_start_hi]
    26034708          034708            add ax, [(es:)bx+?_count_lo]
    2613570A          13570A            adc dx, [(es:)bx+?_count_hi]
    730B              730C              jnc ?_ok
    09D0              09D0              or ax, dx
    7407              7408              jz ?_ok
    90                90                nop
    90                90                nop
    

    Instead of subtracting 1 to compute the last sector, this computes the first sector after the partition, and additionally accepts a wrapped-around result of 0 as valid. This avoids overflow when the starting sector is zero, keeps the corner cases exactly the same otherwise, and even leaves two instruction bytes to spare. (Another victory for Dijkstra, I suppose.) You can keep using DOS with confidence on terabyte-sized disks, if by some miracle you manage to address their sectors with 24-bit CHS. (Yes, you may just patch out the subtraction, or the entire check. But I’ll stay on the safe side just in case this actually does something useful that I overlooked.)

5
  • 1
    The or+jz is not doing anything useful in this code. It can also be replaced with 4 nops. Or, better, an unconditional jump that reduces the need to execute NOPs (which actually cost cycles) as well as the total number of bytes that must be patched.
    – Cody Gray
    Jul 3 at 10:21
  • Well, I said as much: given that partitions this large are not CHS-addressable in their entirety anyway, one may as well remove the whole overflow check. But I left it in just in case someone needs it for some inexplicable purpose. (I’d love an explanation why this check is still there at all.) Jul 5 at 15:02
  • The bug exists because the 'solution' to 'problem a', caused 'problem b' to exist, which is the bug in question. The history of hard drives has many such bugs, going back to the days of the Winchester disk drives and controllers which were separately installed cards, instead of mounted (embedded) in the drive itself. Many of the issues evolved with the mappings of CHS geometry to and from physical sector numbers (which start at 1) and logical sector numbers (which start at 0). Then LBA came along and added yet another level of complexity... and bugs.
    – C. M.
    Jul 8 at 20:44
  • You can find out a little bit more here, https://wikipedia.org/wiki/Cylinder-head-sector
    – C. M.
    Jul 8 at 20:45
  • It's possible to boot these. I made a thing. github.com/joshudson/dossuperfloppy
    – Joshua
    Sep 8 at 3:31

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.