title: XMS allocation in 16-bit DOS
body: <p>Please forgive my attempts at necromancy, but I actually need to write some code for 16-bit DOS (!). I have to verify that a piece of software executes correctly when built for a 16-bit platform, and I discovered that our XP workstations can actually run 16-bit DOS apps, which makes it possible to use the existing batch test system. </p>
  
  <p>In any case, the software consists of one library and one database. Smaller databases (up to ~150kB) can either be defined as a static global array or read from file to a buffer allocated with <code>halloc()</code>, so I am fairly confident that the library and test tool is built correctly.</p>
  
  <p>However, we also have some larger databases to test, up to ~1.8Mb. This is too large to allocate normally, so I wrote a small support library to allocate XMS memory. With this, I can successfully allocate, use (i.e. write and read data) and free up to 16Mb of data in a small toy program. However, when using the XMS facilities in the "real" application, I get the following error:</p>
  
  <pre><code>The NTVDM CPU has encountered an illegal instruction.
  CS:0000 IP:00ba OP:0f 04 10 0e 51
  </code></pre>
  
  <p>Googling on this error gave little relevant results, this type of error seems to be usually blamed on miscellaneous malware. </p>
  
  <p>The code base is strict C90, the compiler currently used for DOS builds is OpenWatcom 1.9 using the "large" memory model. No warnings or errors while building.</p>
  
  <p>The XMS support library follows, the error seems to occurs after the call to xmsmalloc():</p>
  
  <pre><code>/* This file implements rudimentary XMS memory handling.
   * Documentation on the XMS API was found on http://www.qzx.com/pc-gpe/xms30.txt
   */
  
  #include &lt;stddef.h&gt; /* Definition of NULL */
  #include &lt;limits.h&gt; /* Definition of UINT_MAX */
  #include &lt;stdio.h&gt;  /* fprintf and (FILE *) */
  
  /* Allow external configuration of maximum concurrent XMS allocations */
  #ifndef MAX_XMS_ALLOCATIONS
  #define MAX_XMS_ALLOCATIONS 4
  #endif
  
  /* Address of the XMS driver */
  static long XMSControl;
  
  /* Mapping of XMS handle &lt;-&gt; normal pointer */
  typedef struct {
      unsigned int XMSHandle;
      void huge * XMSPointer; 
  } XMSHandleMap;
  
  static XMSHandleMap allocMap[MAX_XMS_ALLOCATIONS];
  
  /* Set up the XMS driver, returns 0 on success and non-zero on failure */
  static int initxms(void)
  {
      char XMSStatus = 0;
  
      if ( XMSControl == 0 )
      {
          __asm {
          ; Is an XMS driver installed?
              mov ax,4300h
              int 2Fh
              mov [XMSStatus], al
          }
  
          if ( XMSStatus == 0x80 )
          {
              __asm {
              ; Get the address of the driver control function
                  mov ax,4310h
                  int 2Fh
                  mov word ptr [XMSControl]  ,bx
                  mov word ptr [XMSControl+2],es
              }
          }
      }
  
      return ( XMSControl == 0 );
  }
  
  /* Allocate a slab of memory from XMS */
  void huge * xmsmalloc(long unsigned int size)
  {
      unsigned int kB;
      unsigned int XMSStatus = 0;
      unsigned int XMSHandle = 0;
      void huge * XMSPointer = NULL;
      int n;
  
      /* If we can not initialize XMS, the allocation fails */
      if ( initxms() )
          return NULL;
  
      /* It is not possible to allocate more kilobytes than a 16-bit register can hold :-) */
      if ( size / 1024 &gt; UINT_MAX )
          return NULL;
  
      /* Get the next free entry in the handle &lt;-&gt; pointer mapping */
      for ( n = 0; n &lt; MAX_XMS_ALLOCATIONS; n++ )
      {
          if ( allocMap[n].XMSPointer == NULL )
              break;
      }
  
      if ( n == MAX_XMS_ALLOCATIONS )
          return NULL;
  
      kB = size / 1024 + (size % 1024 &gt; 0);
  
      __asm {
      ; Allocate [kB] kilobytes of XMS memory
          mov ah, 09h
          mov dx, [kB]
          call [XMSControl]
          mov [XMSStatus], ax
          mov [XMSHandle], dx
      }
  
      /* Check if XMS allocation failed */
      if ( XMSStatus == 0)
          return NULL;
  
      __asm {
      ; Convert XMS handle to normal pointer
          mov ah, 0Ch
          mov dx, [XMSHandle]
          call [XMSControl]
          mov [XMSStatus], ax
  
          mov word ptr [XMSPointer],  bx 
          mov word ptr [XMSPointer+2],dx
      }
  
      if ( XMSStatus == 0 )
      {
          /* Lock failed, deallocate the handle */
          __asm {
          ; Free XMS handle
              mov ah, 0Ah
              mov dx, [XMSHandle]
              call [XMSControl]
  
          ; Return value is not really interesting 
          ;   mov [XMSStatus], ax
          }
          return NULL;
      }
  
      /* Create an entry in the handle &lt;-&gt; pointer mapping */
      allocMap[n].XMSHandle = XMSHandle;
      allocMap[n].XMSPointer = XMSPointer;
  
      return XMSPointer;
  }
  
  /* Free a pointer allocated with xmsalloc */
  void xmsfree(void huge * XMSPointer)
  {
      int n;
  
      if ( XMSPointer == NULL )
          return;
  
      if ( initxms() ) 
          return;
  
      for ( n = 0; n &lt; MAX_XMS_ALLOCATIONS; n++ )
      {
          if ( allocMap[n].XMSPointer == XMSPointer )
          {
              int XMSHandle = allocMap[n].XMSHandle;
  
              __asm {
              ; Unlock handle so we can free the memory block
                  mov ah, 0Dh
                  mov dx, [XMSHandle]
                  call [XMSControl]
  
              ; Free XMS memory
                  mov ah, 0Ah
                  mov dx, [XMSHandle]
                  call [XMSControl]
  
              ; Return value ignored
              }
  
              /* Clear handle &lt;-&gt; pointer map entry so it can be reused */
              allocMap[n].XMSHandle = 0;
              allocMap[n].XMSPointer = NULL;
  
              return;
          }
      }
  }
  
  /* Write a memory report for debugging purposes */
  void xmsreport(FILE * stream)
  {
      int XMSVersionNumber = 0;
      int XMSLargestBlock = 0;
      int XMSTotal = 0;
  
      if ( initxms() ) 
      {
          puts("Could not initialize XMS Driver!");
          return;
      }
  
      __asm {
      ; Get the driver version number
          mov ah,00h
          call [XMSControl] ; Get XMS Version Number
          mov [XMSVersionNumber], ax
  
      ; Get the amount of free XMS memory
          mov ah, 08h
          call [XMSControl]
          mov [XMSLargestBlock], ax
          mov [XMSTotal], dx
      }
  
      fprintf(stream, "XMS Version number: %d\n", XMSVersionNumber);
      fprintf(stream, "Largest available block: %d kB (%d kB total)\n", XMSLargestBlock, XMSTotal);
  }
  </code></pre>
  
  <p>Some concrete questions:</p>
  
  <ol>
  <li>Where can I find more information about the error message (I guess OP means opcode, but what about the other fields?)</li>
  <li>Does the XMS API reference I found still apply when running on Windows XP, or is there some newer version I should reference?</li>
  <li>Could I screwed up the system state with the inline assembler? How should I resolve that?</li>
  <li>Do you have any better idea on how to solve this? :-) DOS extenders seem to require 32-bit mode, the whole point of this exercise is using 16-bit mode.</li>
  </ol>