Partition tables explained

Introduction

partition_viewer

I’ve always been curious about how disks are partitioned, and how the partition table looks like. This article will explain the format of the partition table and will provide a small tool (Partition Viewer) to demonstrate what we explained.

The information in this article apply for x86 systems, I don’t know about disk partitioning on other systems.
You are not required to have prior knowledge about disk partitioning; The goal of this article is to provide just the needed knowledge to understand disk partitioning, hence many details are left out for the readers to investigate by themselves.

Please note that this article does NOT cover the GPT (GUID Partition table).

Background

In this section I will introduce some concepts that are needed to understand the article.

When you purchase a new hard-disk with a given capacity, all you can do with it is read/write raw data from first address up to last address. You can achieve that with special tools that can read an unformatted/unpartitioned disk. The data you read/write have no standard structure but the structure you devise.

When we partition a disk, we are simply dividing the disk’s space into partitions (logical boundaries). For example a 10GB can be divided (theoratically) into as many partitions as one desires, say 3GB, 4GB and 3GB partitions.

[======10 GB============] (before partitioning)
[(3GB===)(4GB====)(3GB===)] (after partitioning)

Practically, disk partitioning is more than just dividing the disk’s space into smaller partitions, but also attaching a given structure to each partition.
Those structures are named “File systems”. You may have heard of FAT and NTFS file systems (supported by Microsoft Operating systems).

File systems are a way to allow you to store your data in an organized manner (managable by the host operating system). That is why disk-partitioning has rules and limits (discussed later). For example, you can create a 10GB partition but you cannot attach to it the FAT16 file system, since FAT16 can only manage up to 2GB of disk space. Each file system has its cons and pros, which are not discussed in this document.

To finalize the disk partitioning and file system relationship, you can imagine that a raw non-partitioned disk can be compared to a big room with no divisions where every employee scatters his stuff here and there, but when we divide (partitioning) that space with cubicals we have organized offices and rooms (File system).

Disk addressing and capacity limitation

In this section we will try to explain the disk addressing concepts.

Cylinder/Head/Sector (CHS) addressing

CHS stands for (C)ylinder/(H)ead/(S)ector addressing mode. Those three component together designates the target disk address.

Logical Block Addressing (LBA)

LBA is linear addressing mode, starting from address 0 up to the maximum addressable location in the disk.
As opposed to CHS addressing, LBA is much simpler and easier to understand and is used in modern operating systems.

MBR and partition table format

In this section we will explain about the MBR (Master Boot record) and the format of the partition table and where it is located on the physical disk.

The master boot record (MBR) is a structure that holds the boot loader program and the partition table. The MBR is always located at the very start of the disk, at LBA 0 or 0,0,1 in CHS addressing.

The length of the MBR structure is 512 bytes.

Offset (hex) Size (in bytes) Item
0x0-0x1BD 446 Boot loader
0x1BE-0x1FD 64 Partition table
0x1FE-0x1FF 2 Signature (should be 0x55, 0xAA)

Usually, the role of the MBR is to find which parition table is active, load its boot sector and execute it

The MBR loader can be infected with an MBR virus (sometimes refered to as bootkits) that runs even before the operating system boots (or the boot sector executes)

Boot loader

The boot loader is a program whose sole purpose is to interpret the partition table and decide which partition should be booted from.

The boot loader can be infected with a boot-sector virus that runs even before the operating system boots

Here are the usual steps that are done by the boot loader:

  1. Load the partition table
  2. Iterate through its entries looking for a partition entry marked as active
  3. Once an active partition is found, we load the correspondance boot sector of that partition
  4. If no active partitions are found then we halt
  5. Once an active partition is found and its corresponding boot sector is loaded, then MBR executes the MBR code

Boot sector

In the previous section we introduced the term “boot sector” which is not to be confused with MBR’s “boot loader”
The boot sector and the MBR are two different things.

A boot sector is yet another structure, 512 bytes in size, that is file-system specific.
Its function is to initiate the operating system and load it from the disk.

Since the MBR will load a boot sector and execute it, the least requirements for a boot sector are:

  1. Offset 0 should have a valid x86 instruction so that when MBR runs it, the code does not crash the system
  2. Offset 0x1FE should have the signature 0x55 0xAA (like the MBR)

In the case of MS-DOS, the boot sector program is responsible for loading IO.SYS and MS-DOS.SYS which will in turn load the COMMAND.COM

Partition Table

The partition table is 64 bytes long and is located inside the MBR at sector 0x1BE.

The partition table is an array of 4 partition table entries each of 16 bytes thus: 16×4 = 64.

The format of each parition table entry is the following:

Offset Size Item
0x00 1 Boot indicator; 0x80 = Active partition / 0x00 Inactive partition (boot_indicator)
0x01 1 partition start: head (chs_start.head)
0x02 1 partition start: sector (chs_start.sect)
0x03 1 partition start: cylinder (chs_start.cyl)
0x04 1 Partition ID (example ID=1 for FAT12) (system_indicator)
0x05 1 partition end: head (chs_end.head)
0x06 1 partition end: sector (chs_end.sector)
0x07 1 partition end: cylinder (chs_end.cyl)
0x08 4 Number of sectors before the beginning of this partition (sectors_before)
0x0C 4 Number of sectos in this partition (number_of_sectors)

You should note that the start and end CHS values are not as they are stored, instead, we have to extract their values from the bits.

real cylinder value = ((chs.sector >> 6) << 8) | (chs.cyl)
We take bits 6-7 from chs_sector which are the high bits of real_cyl_value and then combine them with chs_cyl which are the low bits of real_cyl_value.

The reason they are encoded that way, is because the BIOS INT 0x13/AH=0x02 function (which reads a sector) requires this encoding:

INT 13 - DISK - READ SECTOR(S) INTO MEMORY
	AH = 02h
	AL = number of sectors to read (must be nonzero)
	CH = low eight bits of cylinder number
	CL = sector number 1-63 (bits 0-5)
	     high two bits of cylinder (bits 6-7, hard disk only)
	DH = head number
	DL = drive number (bit 7 set for hard disk)
	ES:BX -> data buffer

The number_of_sectors field describes how many sectors are in this partition. This field can be used to determine the length of the partition.

Similarly we can use (CHS_ENDCHS_START) to determine the length of the partition.

The number of sectors before (at offset 8) designate where the partition data begins starting from the partition table definition. If in the MBR, an entry has sectors_before equal 63 then this means that the boot sector of that partition is located at sector 63

Primary and Extended partitions

A primary partition is a partition entry with an ID different than 5 and 15 and can have file system associated with it.

Extended partitions unlike primary partitions, have IDs 5 or 15 and cannot have a file system associated with them, instead they point to another partition table that have at most two partition table entries.

The second one can be an extended partition, thus nesting more paritions inside of it).

Extended partitions can be considered as just containers of other partitions. We will demonstrate partition table nesting next.

Partition table rules

The following partition table rules are take from other sources, I list only the most relevant ones (in the context of this article):

  • Only partition can be active at a time
  • In the MBR partition table: at most 4 primary partitions can be created, or 3 primary partitions and 1 extended partition
  • In the extended partition table: there can be 0 or 1 extended partition link and 0 or 1 non-extended partition (thus two in total)
  • Partition table entries can have any order (we can exchange entry 1 with 2, etc…)

Nested Partitions

Because of the structure of the partition table located in the MBR, we realize that we can have at most 4 partiton table entries in each partition table.

To overcome this limitation, special partition table entries are introduced that allow us to have an unlimited number of partitions that are linked together.

Inside the MBR, the extended partition entry describes the size of all the partitions that will be contained inside of it (using the nb_of_sectors field), whereas the NbSectorsBefore or CHS_START will be used to compute the link to the next partition table which can contain at most 2 partition table entries.

Those two entries are: (1) A normal partition entry (2) another extended partition entry pointing to the next linked partition.

Interpreting a partition table manually with a hex editor

In this section we will demonstrate how to interpret the partition table manually using a hex editor that can display disk sectors.

partition_explain1

This screenshot shows the first sector of the disk.

The bytes marked in yellow are the boot loader code.

The bytes marked in red are the signature of the MBR which should always be 0x55 0xAA.

The green area shows a complete partition table entry (16 bytes):

  • 00 : Boot indicator -> Inactive
  • 01 01 00 : CHS_START -> Head = 1, Sector = 1, Cyl = 0
  • 06: System Indicator -> FAT16B (>= 32 MB)
  • FE 3F 0B: CHS_END -> Head=254, Sector = 63, Cyl = 11
  • 3F 00 00 00: Sectors Before -> 63
  • CD F0 02 00: Number of sectos -> 192717

Which means that this is a FAT16 partition of ~96MB.

In the same manner we can interpret the result of the table and read it as:

Entry # Boot Indicator CHS_START System ID CHS_END Sectors Before Nb Sectors
1 0 0,1,1 6: FAT16B (>= 32 MB) 11,254,63 63 192717
2 0 166,0,1 15: Win95 Extended (LBA) 12543,254,63 2666790 26748225
3 0 13,0,1 23: Hidden IFS (HPFS/NTFS) 154,254,63 208845 2281230
4 0 12543,0,1 28: Hidden Win95 FAT32 (LBA) 12543,254,63 29415015 4128705

Notice that entry #2 is an extended partition (sysid = 15) of size: (26748225 * 512) = 13695091200 bytes = 13060 GB

This partition starts at sector 2666790 (sectors_before), let us go there and see the partition table pointed by it:

Entry# Boot Indicator CHS_START System ID CHS_END Sectors Before Nb Sectors
1 0 166,1,1 11: Win95 FAT32 178,254,63 63 208782
2 0 184,0,1 5: Extended 209,254,63 289170 417690

As we said, now every partition table that is pointed from the initial extended parition table entry have at most two entries.

The first entry is a normal entry and the second entry (type = extended) is a link to another partition table.

To compute the LBA of the second entry, we add to the sectors before the “sectors before” value from the extended partition table entry of the MBR, that is: 2666790+289170 = sector 2955960.

Now we go there and read the partition table:

Entry# Boot Indicator CHS_START System ID CHS_END Sectors Before Nb Sectors
1 0 184,1,1 6: FAT16B (>= 32 MB) 209,254,63 63 417627
2 0 240,0,1 5: Extended 255,254,255 1188810 17462655

Next partition table is at: 2666790+1188810 = sector 3855600.

Entry# Boot Indicator CHS_START System ID CHS_END Sectors Before Nb Sectors
1 0 240,1,1 7: Installable File System (NTFS, HPFS) 255,254,255 63 17462592
2 0 255,0,193 5: Extended 255,254,255 19069155 3919860

Next partition table is at: 2666790+19069155 = sector 21735945.

Entry# Boot Indicator CHS_START System ID CHS_END Sectors Before Nb Sectors
1 0 255,1,193 6: FAT16B (>= 32 MB) 255,254,255 63 3919797
2 0 255,0,193 5: Extended 255,254,255 23246055 433755

Next partition table is at: 2666790+23246055 = sector 25912845.

Entry# Boot Indicator CHS_START System ID CHS_END Sectors Before Nb Sectors
1 0 255,1,193 130: Linux swap / Solaris 255,254,255 63 433692
2 0 255,0,193 5: Extended 255,254,255 23679810 3068415

Next partition table is at: 2666790+23679810 = sector 26346600.

Entry# Boot Indicator CHS_START System ID CHS_END Sectors Before Nb Sectors
1 0 255,1,193 11: Win95 FAT32 255,254,255 63 3068352
0 0 0,0,0 0 0,0,0 0 0

No next entry; end of partition nesting.

Some Partition Table System IDs

The table below lists some of the known system ids (or file system ids).

ID Name
0x00 Empty
0x01 FAT12
0x02 XENIX root
0x03 XENIX usr
0x04 FAT16 <32MB
0x05 Extended
0x06 FAT16B (>= 32 MB)
0x07 Installable File System (NTFS HPFS)
0x08 AIX
0x09 AIX bootable
0x0A OS/2 Boot Manager
0x0B Win95 FAT32
0x0C Win95 FAT32 (LBA)
0x0E Win95 FAT16 (LBA)
0x0F Win95 Extended (LBA)
0x10 OPUS
0x11 Hidden FAT12
0x12 Compaq diagnostics
0x14 Hidden FAT16 <32MB
0x16 Hidden FAT16
0x17 Hidden IFS (HPFS/NTFS)
0x18 AST SmartSleep
0x1B Hidden Win95 FAT32
0x1C Hidden Win95 FAT32 (LBA)
0x1E Hidden Win95 FAT16 (LBA)
0x24 NEC DOS
0x2C WildFile/Adaptec GOBack
0x39 Plan 9
0x3C PowerQuest Recoverable Partition
0x40 Venix 80286
0x41 PPC PReP Boot
0x42 Veritas Logical Disk Manager
0x4d QNX4.x
0x4e QNX4.x 2nd part
0x4f QNX4.x 3rd part
0x50 OnTrack DM
0x51 OnTrack DM6 Aux1
0x52 CP/M
0x53 OnTrack DM6 Aux3
0x54 OnTrackDM6
0x55 EZ-Drive
0x56 Golden Bow
0x5c Priam Edisk
0x61 SpeedStor
0x63 GNU HURD or SysV
0x64 Novell Netware 286
0x65 Novell Netware (3.11 and 4.1)
0x66 Novell Netware 386
0x70 DiskSecure Multi-Boot
0x75 PC/IX
0x78 XOSL
0x80 Old Minix
0x81 Linux/Minix v1.4b+
0x82 Linux swap / Solaris
0x83 Linux native file system (Ext2/3)
0x84 OS/2 hiding type 04h partition
0x85 Linux extended
0x86 NT FAT volume set
0x87 NT IFS volume set
0x8e Linux LVM
0x93 Amoeba/Hidden Linux native file system (Ext2/3)
0x94 Amoeba BBT
0x9f BSD/OS
0xA0 IBM Thinkpad hibernation
0xA5 FreeBSD
0xA6 OpenBSD
0xA7 NeXTSTEP
0xA9 NetBSD
0xB7 BSDI fs
0xb8 BSDI swap
0xbb Boot Wizard hidden
0xc1 DRDOS / sec (FAT-12)
0xc4 DRDOS / sec (FAT-16 < 32M)
0xc6 Disabled NT FAT (FAT-16) volume set/DRDOS
0xc7 Syrinx / Disabled NT IFS volume set
0xda Non-FS data
0xdb CP/M / CTOS / …
0xde Dell Corporation diagnostic partition
0xdf BootIt
0xe1 DOS access
0xe3 DOS R/O
0xe4 SpeedStor
0xeb BeOS fs
0xee EFI GPT
0xef EFI (FAT-12/16/32)
0xf0 Linux/PA-RISC boot
0xf1 SpeedStor
0xf4 SpeedStor
0xf2 DOS secondary
0xfd Linux raid autodetect
0xfe LANstep
0xff Bad Track Table

PartitionViewer Tool

For the sake of demonstration, I wrote a tool that ships with the following components:

  • DiskSector.cpp/.h – A class to read/write raw disk sectors
  • partitionmanager.cpp/.h – A class that parses partition tables
  • MyDrawBar.cpp/.h – MFC static component to draw the partition tables
  • MyHistory.cpp/.h – MFC static component to draw the partition tables
  • PartitionViewer[Dlg].cpp/.h – The UI that uses the above helper classes

The PartitionViewer tool hardcodes only a small subset of the system IDs and colors:

static const MYHISTORYBARTYPECOLOR part_colors[] = 
{
  {0x0,  RGB(255,255,255), "Free space"},
  {0x6,  RGB(100,10,0), "FAT16B (>= 32 MB)"},
  {0xC,  RGB(255,0,128), "Win95 FAT32 (LBA)"},
  {0xE,  RGB(0,110,110), "Win95 FAT16 (LBA)"},
  {0x7,  RGB(200,0,20), "Installable File System (NTFS, HPFS)"},
  {0x0B, RGB(10,100,0), "Win95 FAT32"},
  {0x83, RGB(20,200,0), "Linux native file system (Ext2/3)"},
  {0x17, RGB(10,0,100), "Hidden IFS (HPFS/NTFS)"},
  {0x82, RGB(101, 12, 130), "Linux swap / Solaris"},
  {0x1C, RGB(90, 127, 30), "Hidden Win95 FAT32 (LBA)"},
  {0x1E, RGB(0, 0, 255), "Hidden Win95 FAT16 (LBA)"},
  {0x04, RGB(101, 12, 130), "FAT16 <32MB"}
};

Feel free to extend that list.

This program requires admin privilege to work because it accesses the disk sectors.

Once executed:

partition_viewer

the partitions will be visualized.

  • Use the dropdown list to select another drive then press the “Represent” button
  • Hover the mouse over the drawn area to display the partition information in the panel just below the drive dropdown list

Reference

I will like to acknowledge all the people who wrote all those articles, open source programs and references that made the writing of this article possible.

Conclusion

I hope you found this article useful. In the future, another article can be written to cover the GPT.

Please leave your comments or suggestions below and I will try to answer questions or fix the code as much as my (limited) time and my knowledge allow.

Donation to support this blog and encourage me to write more useful blog entries are welcome:

Download the sources and compiled binaries from here

One Reply to “Partition tables explained”

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.