Feedback Search Top Backward Forward

A Note on the OS/2 Warp Boot Sequence

Written by David C. Zimmerli




In my article "Inside the OS/2 Kernel" ( I explored in some detail the sequence of events involved in booting OS/2 2.1 from a FAT partition. The purpose of this note is to round out the picture in two ways: first, by analyzing the operation of Boot Manager, which ships with OS/2 and is often installed on OS/2 systems, and second, by examining the boot-up of Warp Connect from an HPFS partition.

I. Boot Manager

Since it is sometimes necessary to have multiple versions of OS/2 installed on one computer, or even to boot other operating systems occasionally, IBM has provided Boot Manager to allow the user to select the boot partition immediately after the computer is turned on. Boot Manager resides in its own small partition and supports the selection of up to 32 bootable partitions spread among multiple physical disk drives.

The code and data for Boot Manager are imbedded in the file FDISK.COM (in Warp Connect, beginning at offset 12d0e hex), and FDISK will, at the user's request, install Boot Manager by copying this code and data to the appropriate sectors at the beginning of the Boot Manager partition. The size of the code and data is approximately 14K, and at system start-up, it is read into memory beginning at 0A00:0. It executes in real mode and (at least in the version supplied with Warp Connect, no fixpacks) uses only standard int 13h BIOS calls to read the hard disks, not the int 13h extensions available with newer BIOSes.

To trace the operation of Boot Manager, we first need a way of accessing and patching the code on the hard drive. The following program reads the Boot Manager sectors and stores them in a file named bmgrcode.bin. I will leave it to the reader to make the changes needed to write back a debugging version. The program also assumes that the boot drive is the first physical hard drive on the system, and will need minor modifications if this is not the case.

/* GET_BMGR.C.  Grab Boot Manager code.  12-20-98 dcz */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <os2.h>

char sectorBuff[512];
char bmgrCode[0x3800];

void read_chs(char * buffer,USHORT hDevice, int cyl, int head, int sector);

void main()

 ULONG     function;  /*  The type of information to obtain about the
                          partitionable disks. */

 PVOID pBuf;      /*  The address of the buffer where the returned
                      information is placed. */
 ULONG cbBuf;     /*  The length, in bytes, of the data buffer. */
 PVOID pParams;   /*  The address of the buffer used for input parameters. */
 /* HFILE hDevice; */ /*  Device handle returned by DosOpen, or a standard
                          (open) device handle. */
 USHORT usNumDrives = 0;                  /* Data return buffer */
 ULONG ulDataLen = sizeof(USHORT);     /* Data return buffer length */
 USHORT hDevice;
 unsigned short int bufPtr;
 int i;
 unsigned char partType;
 int bmgrHead,bmgrSec,bmgrCyl;
 FILE *fp;


 /* printf("sizeof HFILE is %d\n",sizeof(HFILE)); */  /* equals 4 */

 /* First count number of partitionable physical disks */
                          NULL,         /* No parameter for this function */

 if (rc != NO_ERROR) {
  printf("DosPhysicalDisk (INFO_COUNT_PARTITIONABLE_DISKS) error: return code = %u\n",
 else {
  printf("DosPhysicalDisk:  %u partitionable disk(s)\n",usNumDrives);

 /* Now get a handle to the first physical disk. */
 rc = DosPhysicalDisk(
          INFO_GETIOCTLHANDLE,  /* function */

 if (rc != NO_ERROR) {
  printf("DosPhysicalDisk (INFO_GETIOCTLHANDLE) error: return code = %u\n", rc);
 else {
  printf("DosPhysicalDisk:  Disk 1 handle: %ld\n",hDevice);

 /* Now read the Master Boot Record (MBR) from Disk 1 */
 bufPtr = 0x01be;
 for (i=0;i<4;i++) {
  partType = *(sectorBuff + bufPtr + (i * 16) + 4);
  /* printf("partition # %d has type %d\n",i+1,partType); */
  if (10 == partType) break;

 if (4 == i) {
  printf("No BootManager partition found.\n");

 bmgrHead = *(sectorBuff + bufPtr + (i * 16) + 1);
 bmgrSec = (*(sectorBuff + bufPtr + (i * 16) + 2)) & 63;
 bmgrCyl = (*(sectorBuff + bufPtr + (i * 16) + 2)) & 192;
 bmgrCyl = (*(sectorBuff + bufPtr + (i * 16) + 3)) + (bmgrCyl * 4);

 /* Now read 28 (decimal) Boot Manager sectors */
 /* (Instead of this loop, could probably do this better by using */
 /* the multiple sector features of function 09h/64h). */
 for (i = 0;i < 28;i++) {
  read_chs(bmgrCode + (i * 512),hDevice,bmgrCyl,bmgrHead,bmgrSec + i);
  /* printf("byte 1: %02X\n",*(bmgrCode + (i * 512) + 0)); */

 /* Now close the handle. */
 rc = DosPhysicalDisk(
          INFO_FREEIOCTLHANDLE,  /* function */

 if (rc != NO_ERROR) {
  printf("DosPhysicalDisk (INFO_FREEIOCTLHANDLE) error: return code = %u\n", rc);
 else {
  printf("DosPhysicalDisk:  Closed Disk 1 handle.\n");

 /* Now write out the file. */
 if (NULL == (fp = fopen("bmgrcode.bin","wb"))) {
  printf("Could not open file bmgrcode.bin for output.\n");
 else {
  printf("Wrote file bmgrcode.bin.\n");


void read_chs(char * buffer,USHORT hDevice, int cyl, int head, int sector)

 TRACKLAYOUT trackLayout;
 ULONG cbParams;   /*  The length, in bytes, of the parameter buffer. */
 ULONG cbData;

 /* printf("size of track layout is %d\n",sizeof(TRACKLAYOUT)); */
 /* size = 13 decimal */

 if (1 == sector)
  trackLayout.bCommand = 0x01;   /* means track layout starts w/sector 1
                                    & has consec sectors */
  trackLayout.bCommand = 0;
 trackLayout.usHead = head;
 trackLayout.usCylinder = cyl;
 trackLayout.usFirstSector = 0;
 trackLayout.cSectors = 1;

 trackLayout.TrackTable[0].usSectorNumber = sector;
 trackLayout.TrackTable[0].usSectorSize = 512;

 cbData = 512;

 cbParams = sizeof(trackLayout);   /* equals 13 decimal */

 rc = DosDevIOCtl(
          hDevice, /* auto - casted to 4 bytes ? */
          0x09, /* category -- Physical Disk IOCtl */
          0x64, /* function -- read track */
          &trackLayout,  /* pParams */
          cbParams,  /* cbParmLenMax */
          &cbParams, /* pcbParmLen */

 if (rc != NO_ERROR) {
  printf("DosDevIOCtl error: return code = %u\n", rc);
 else {
  /* printf("DosDevIOCtl: read 1 sector.\n"); */


Once we have the file bmgrcode.bin, we can run it through DEBUG.EXE (in a DOS session) or similar utility to patch in debugging code.

Here is a list of symbols to aid us in interpreting the file bmgrcode.bin. This list is valid for the Warp Connect base version of Boot Manager. The arguments to the functions are given in right-to-left order, that is, the rightmost argument is the first to be pushed on the stack, the leftmost is the last.

00F4 ExecuteInt13hCommands
013F WriteNTSOnScreen
0F20 EntryPoint
0F30 SaveRestoreInt1CHook(WORD action) [action=0, restore old; 1, patch new]
0F9F GetTimerValue()
0FAC WriteStringOnScreen(WORD row, WORD column, char far * string,
                         WORD length, WORD attribute, WORD page)
0FD0 GenericInt10h(WORD AHReg, RegStruct far * pRegStruct)
0FF6 GetKbdChar
0FFB GetKbdStatus
1004 WriteCharOnScreen
1012 GetPhysicalDriveCount
1059 GenericInt13h(WORD ah, WORD bx, WORD es, WORD dl, WORD dh,
                   WORD cl, WORD ch, WORD al)
1080 JumpToSelectedBoot
10A0 memcpy(void far * dest, void far * src, WORD count)
10BA memfill(void far *dest, WORD fillChar, WORD count)
10D0 memcmp(void far * dest, void far * src, WORD count)
10EF strlen(char * string)
110A sprintf(char * destString, char * formatString, args ...)
112E sprint01
1316 sprint02
134C sprint03
1388 sprint04
140E sprint05
14BC GetVideoMode
14D2 InitVideo1
14DE InitVideo2
153A SetCursorType(WORD startLine,WORD endLine)
1554 SetCursorPosition(WORD row, WORD column, WORD page)
1574 SetDisplayPage(WORD displayPage)
158A ScrollWindowUp(WORD numLines, CoordStruct * pCoordStruct, WORD attribute)
15BE ScrollWindowDown(WORD numLines, CoordStruct * pCoordStruct, WORD attribute)
15F2 WriteCharAtCursor(WORD char, WORD color, WORD count, WORD page)
1618 CenterRectOnScreen(CoordStruct * pCoordStruct)
165E FindLineDrawChar
1694 ScreenDrawChar(WORD row, WORD col, WORD char)
1786 ScreenDraw1(WORD row1, WORD col1, WORD row2, WORD col2)
1806 ScreenDraw2(CoordStruct * pCoordStruct)
18B6 ScreenDrawString(CoordStruct * pCoordStruct, WORD row, WORD col,
                      char * string, WORD attribute)
192A ScreenDrawCentered(CoordStruct * pCoordStruct, WORD row,
                        char * string, WORD attribute)
195C ScreenDraw3(CoordStruct * pCoordStruct, DisplayItemStruct * pDIStruct, WORD attrib)
19DA DrawInitialScreen(CoordStruct * pCoordStruct, WORD attrib,
                       DisplayItemStruct * pDIStructs, WORD param4, WORD param5)
1ADA DrawBootMenu(CoordStruct * pCoordStruct, WORD row, WORD attribute)
1B16 MainRoutine(CoordStruct * pCoordStruct, WORD color, WORD attribute,
                 WORD param4, PartnDataStruct * pDefaultPartition,
                 func * func1, WORD param7, func * func2)
1E50 DebugMessage(WORD action, char * string) [ action = 0,prompt & resume;
                  action=1,halt system]
1EB0 ScreenDrawStrings(CoordStruct * pCoordStruct, WORD attrib,
                       DisplayItemStruct * pDIStructs)
1EFA EntryRoutine()
2042 BootSelectedPartition(WORD dxValue, WORD cxValue)
2160 DrawScreenAndWaitForSelection
2174 GetBootablePartitions
22CC GetDriveNum
2312 InitWindow
23E6 AdjustPartitionActiveBit
25A6 WriteBackSector
2752 WritePartitionInfoLine
2836 GetPartitionType (WORD partnType)
292E SetTimer(WORD action)
29E8 WriteTimeoutMessage (WORD timeoutTicks)
2A84 InitPartitionTable
2B4C ScanPartitions
2C34 PartitionFunc1
2D5E PartitionFunc2
2F70 memcmp(void * mem1, void * mem2, WORD count)
2FA2 sprint06
303E sprint07
30E0 sprint08

Data Segment:
3114 LineDrawChars
3170 DisplayItemArray
319A CoordStruct
323C PartitionDataTable
390E SectorBuffer
3B10 ScreenMemory

The main structures used are defined as follows:

typedef struct {
 WORD ax;
 WORD bx;
 WORD cx;
 WORD dx;
 } RegStruct;

typedef struct {
 BYTE row1;
 BYTE col1;
 BYTE row2;
 BYTE col2;
 } CoordStruct;

typedef struct {
 WORD row;
 WORD col;
 BYTE action;
 char far * string;
 } DisplayItemStruct;

typedef struct {
 CHAR partitionName[8];
 BYTE byte1;
 BYTE partitionNumOnDrive;
 BYTE partitionLetter;
 BYTE byte2;
 BYTE partitionType;
 BYTE byte3;
 WORD dxValue1;
 WORD cxValue1;
 WORD dxValue2;
 WORD cxValue2;
 DWORD partitionStartLSN;
 WORD partitionSizeInMb;
 WORD partitionOffsetInBootRecord;
 } PartitionDataStruct;

Since Boot Manager scans for available boot partitions each time it runs, it is possible to add new drives and partitions without doing any additional configuration of Boot Manager's data areas.

Analysis of the partition scan routines also reveals some undocumented details about how extended partitions are set up. As we know, there is (at most) one extended partition on each physical drive, and an extended partition can hold any number of logical partitions. Boot Manager expects to see the following: each logical partition is preceded by an EPBR (Extended Partition Boot Record). Each EPBR contains two records-- at offsets 01BEh and 01CEh-- except for the last EPBR, which only contains one record at offset 01BEh. The first record "points" to the associated logical partition, and the second "points" to the following EPBR. So we have a singly-linked list of logical partitions, with no limit on the number of members. Since there is one EPBR per logical partition, perhaps the EPBRs really should be called Logical Partition Boot Records.

Although Andrew Pitonyak in his article "The Partition Table" ( describes a somewhat more flexible arrangement for setting up extended partitions, the version of Boot Manager shipping with Warp Connect 3.0 requires the above scheme to work properly.

The presence of the "sprintf" and "DebugMessage" routines in this code makes it reasonably easy to patch in tracepoints to Boot Manager. We can replace one of the little-used messages in the area 0D47 to 0E24 with our own, and modify one of the many existing calls to sprintf/DebugMessage to display our own message, possibly with the values of variables, without disturbing the offsets in the rest of the code.

II. Booting Warp Connect from an HPFS Partition

Once we have (wisely) chosen OS/2 Warp from the Boot Manager menu, the system starts to read sectors from the OS/2 boot partition and the boot sequence begins. The best IBM-supplied source of information on this subject is the "Remote IPL/Bootable IFS" section of the file IFS.INF, which can be found at:

I will proceed on the assumption that reader has read through this file, and will try to fill in some of the details and unclear items in it.

When booting from an HPFS partition, the first piece to be loaded is the "micro-FSD", comprising 5 sectors, or about 2.5 K of code, at the beginning of the partition. As explained in IFS.INF, the micro-FSD provides four entry points: Open, Read, Close and Terminate. Currently the latter two are simply stub routines which do nothing and return immediately. Next to be loaded are the "mini-FSD", which is the file OS2BOOT, and then OS2LDR. Control is transferred to OS2LDR and the three stages of the boot process begin, as described in IFS.INF. The most important pieces of information transferred between these three stages are: the C/H/S geometry of the boot drive (contained in the numbers S, sectors per track, and H, number of heads), the drive number of the boot drive (0,1,2, etc.), and the starting sector of the boot partition.

Again I will try to provide some debugging guideposts for the micro-FSD. This is significant because, unfortunately, the HPFS micro-FSD which shipped with Warp Connect 3.0 (no fixpacks) contained a ghastly bug which prevented the system from booting properly from HPFS partitions where the directory and system files lie past about the 2Gb mark. The symptoms of this bug are as follows: we can boot easily from floppies, read the CD-ROM, and the text-mode phase of the install proceeds uneventfully (assuming we have the updated *.ADD drivers from ). However, when we reboot to enter the PM phase of the install, the system hangs with nothing but a blank screen and a lonely flashing cursor in the upper left corner. This bug was fixed by fixpack 38, and we will have more to say later on the work-around.

Here is a program to read the micro-FSD from the C: partition (assuming this is the boot partition) and store it in the file microFSD.bin.

/* GETMUFSD.C.  Grab micro-FSD code.  12-24-98 dcz */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <os2.h>

char muFSDCode[0x0aae];

void main()

 PVOID pBuf;      /*  The address of the buffer where the
                      returned information is placed. */
 ULONG cbBuf;     /*  The length, in bytes, of the data buffer. */
 PVOID pParams;   /*  The address of the buffer used for input parameters. */
 HFILE hDevice; /*  Device handle returned by DosOpen, or a standard
                    (open) device handle. */
 ULONG ulDataLen ;
 /* USHORT hDevice; */
 int i;
 FILE *fp;
 ULONG ulAction;
 BYTE param;
 BYTE data;
 ULONG cbParams;   /*  The length, in bytes, of the parameter buffer. */
 ULONG cbData;


 rc = DosOpen(

 if (rc != NO_ERROR) {
  printf("DosOpen error: return code = %u\n", rc);
 else printf("DosOpen returned logical device handle %ld\n",hDevice);

 /* Now read data */
 if (rc != NO_ERROR) {
  printf("DosRead error: return code = %u\n", rc);
 else printf("DosRead returned ulDataLen %04xh\n",ulDataLen);

 /* Now close the file (device) handle. */

 /* Now write out the file. */
 if (NULL == (fp = fopen("microFSD.bin","wb"))) {
  printf("Could not open file microFSD.bin for output.\n");
 else {
  printf("Wrote file microFSD.bin.\n");


And here are some guideposts through the resulting hexadecimal jungle:

0003 Boot Parameter Block
0018 (WORD) SectorsPerTrack
001A (WORD) NumberOfHeads
001C (DWORD) PartitionStartLSN
003E (DWORD) StartingSectorLSN
0045 (WORD) NumSectorsToRead
00AE ReadSector
013F DebugMessage [writes string at DS:SI]
0272 EntryPoint
03DA DisplayBXInHex
0409 DisplayStartSectorLSN
0449 ErrorMissingKernelFile
0453 DisplayDebugPoint2
045D ReadSectors [uses NumSectorsToRead, StartingSectorLSN, writes data at ES:BX]
054F Read4Sectors [stores data at ds:2000]
0565 Read1Sector [ stores data at ds:(2000h + (0200h * AX)) ]
057D FindFilenameInRootDir [ depth-first B-Tree search, called with
                             DS:SI pointing to counted string with file name ]
061D ReadInFile
06BE GetKernelType
07DA DivideDxAxBy512
07E6 muFSD_Open
093A muFSD_Read

The startup code included with the micro-FSD uses the micro-FSD routines to load OS2BOOT and OS2LDR. However, it also reads a few pieces of the file OS2KRNL to see whether the latter is an "LX" or "NE" type executable. Of course in Warp Connect the kernel is a 32-bit "LX" type executable, but this startup code is apparently also prepared to load old 1.x kernels. But aside from that possibility, the next step is to pass control to OS2LDR using the protocol described in IFS.INF.

Once again there are built-in routines for debugging, and with a little ingenuity, we can bend routines such as DebugMessage and DisplayBXInHex to suit our own scientific purposes.

We have noted that the Boot Manager code is imbedded in FDISK.COM, from where it is copied to the hard disk during installation. What about the micro-FSD and the mini-FSD, OS2BOOT? How do they find their way onto the disk during system setup? They're not in FDISK.COM; FDISK only creates and deletes partitions, the skeletons of the disk structure. They're not in FORMAT.COM; FORMAT only sets up the allocation maps and directory blocks of the partition. The file IFS.INF refers to an OS/2 "SYS" utility, but there does not seem to be a SYS.COM file analogous to the DOS SYS utility, which creates bootable diskettes.

In fact, the micro- and mini-FSDs reside in the file UHPFS.DLL, and are used by the exported SYS routine in that file. As we know, the creator of an installable file system must supply not only the *.IFS file (analogous to HPFS.IFS), but also a user DLL (analogous to UHPFS.DLL) to support CHKDSK and a few other system utilities. The latter file must include a SYS export routine, which can be called from SYSINSTX.COM during system installation to plant the micro- and mini-FSDs in their proper places to make the file system bootable.

This raises an interesting problem in the case when the FSD needs updating. As noted above, the UHPFS.DLL in Warp Connect was defective regarding large disk drives. When attempting to install OS/2 on an extended HPFS partition on a friend's Gateway recently, I had a Warp Connect CD-ROM, and a set of Fixpack 38 diskettes with the updated UHPFS.DLL. But I couldn't apply the Fixpack because I couldn't install Warp Connect to begin with. Nor could I use the updated UHPFS.DLL to enable the installation, because UHPFS.DLL resides on the CD-ROM and the CD-ROM is read-only.

So I was left with two choices: one, cut another set of umpteen diskettes for the base Warp Connect install, copy in the new UHPFS.DLL onto the right diskette, and install from floppies. Or two, proceed with the text-mode phase of the CD-ROM install, and then, with a crafty slice of the hacker's scalpel, excise the micro- and mini-FSDs from the new UHPFS.DLL and paste them onto the boot partition (being careful not to disturb the Boot Parameter Block) to enable the PM phase. The reader who has come along this far can no doubt surmise which path I took, and fill in the rest of the happy ending.

I welcome comments and questions about this article. Please email

Copyright © 1998, David C. Zimmerli. All rights reserved.