2

Following on from my last question, I cannot get Move Extended Memory Block (Function 0Bh) working in Turbo C 2.01.

The following main.c contains only the minimum functions: get the XMS driver pointer, call the Move function. xms_move_xmb_internal() is as bare as I can get it, no variables except the parameters. I couldn't get it working with the XMB handles, so I decided to make sure a conventional->conventional copy worked first.

Compile with tcc -ml -d -G main.c.

In Dosbox 0.74-2, the call to the XMS driver will either hang indefinitely or crash Dosbox instantly. My other functions (query, freemem, allocation, deallocation, locking and unlocking) work, but not Move. Ethan Brodsky's SB16SND implementation of an XMS wrapper pushes and pops DS around calling the XMS driver pointer but that doesn't have any effect for me if I add it.

I'm aware that in the Large model, far is redundant but I added it to make sure I wasn't overlooking it.

main.c:

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

typedef union
{
   struct
   {
      unsigned int o;
      unsigned int s;
   } seg_off;
   void (far *function_ptr)();
   void far *ptr;
   unsigned long int offset;
} seg_off_ptr;

seg_off_ptr xms_driver;

int xms_init()
{
   union REGS inregs, outregs;
   struct SREGS sregs;
   inregs.x.ax = 0x4310u;   
   int86x(0x2Fu, &inregs, &outregs, &sregs);   
   if (!outregs.h.al) return 0;   
   xms_driver.seg_off.s = sregs.es;
   xms_driver.seg_off.o = outregs.x.bx;   
   return 1;
}

unsigned int xms_version_implemented()
{
   _AX = 0x0000;   
   (*xms_driver.function_ptr)();   
   return _AX;
}

void xms_move_xmb_internal(unsigned int ds, unsigned int si)
{      
   _AX = 0x0B00;
   _DS = ds;
   _SI = si;   
   (*xms_driver.function_ptr)();
}

typedef struct _moveparams
{
   unsigned long int length;
   unsigned int      src_handle;
   unsigned long int src;
   unsigned int      dst_handle;
   unsigned long int dst;
} moveparams;

void xms_move_xmb
(
   unsigned long int length,
   unsigned int      src_handle,
   unsigned long int src,
   unsigned int      dst_handle,
   unsigned long int dst
)
{      
   moveparams params;
   const void far *params_ptr = &params;
   
   params.length     = length;
   params.src_handle = src_handle;
   params.src        = src;
   params.dst_handle = dst_handle;
   params.dst        = dst;
   
   xms_move_xmb_internal(FP_SEG(params_ptr), FP_OFF(params_ptr));
}

int main(int argc, char *argv[])
{
   int i;
   char far *test;
   
   xms_init(); 
   printf("Version %X\r\n", xms_version_implemented());
   
   test = malloc(513);  
    test[0] = 0;
    
    /* Pattern, 64 XXX then 64 ---. */
    for (i = 0; i < (512/4); i++)
    {   
       if (i < 64)
       {
        strcat(test, "XXX ");
      }
      else
      {
         strcat(test, "--- ");
      }
   }
   
   printf("%s\r\n\r\n", test);
            
   if (1) xms_move_xmb(64ul, 0, (unsigned long)test, 0, (unsigned long)(test + 4*100));
   /* Should be equivalent to: */
   if (0) memcpy(test + 4*100, test, 64);
   
   printf("%s\r\n\r\n", test);

    printf("Did not crash.\r\n");
    
    free(test);
}

The correct output should copy a block of the XXXs into the middle of the ---s.

It's possible that I'm passing the pointer test into the driver function incorrectly, but I've tried printing the fields of the struct, and printing (and inserting) each individual byte of the params in turn and everything is landing in the correct place.

7
void xms_move_xmb_internal(unsigned int ds, unsigned int si)
{      
   _AX = 0x0B00;
   _DS = ds;
   _SI = si;   
   (*xms_driver.function_ptr)();
}

looks like a bad idea. The compiler assumes DS to point to the global data segment of your program. In the large memory model, the stack segment can differ (I don't remember the default) from the data segment. You call the function like this:

void xms_move_xmb( /*...*/ )
{      
   moveparams params;
   const void far *params_ptr = &params;

   /* ... */   
   
   xms_move_xmb_internal(FP_SEG(params_ptr), FP_OFF(params_ptr));
}

params is a local variable of xms_move_xmb, and thus the segment part of params_ptr indeed points to the stack segment. Setting DS to the stack segment makes the global variable xms_driver inaccessible and thus your call will jump to an unpredictable position and crash the machine.

You could most likely work around the problem by making params static in xms_move_xmb, so it resides in the data segment. You can omit setting DS in that case.

4
  • Yes, this is correct, the function pointer exists in data segment where DS points to, and by changing DS to point to local variable that exists in stack segment where SS points to, the entry point where to jump to is read from incorrect segment and arbitrary code is executed at arbitrary address. Another option to fix this is to just make the move structure a global variable, so DS already points to it, and thus there is no need to load DS register before call. – Justme Sep 23 '20 at 9:47
  • @Justme Making it global is just like making it a local static variable, which I suggested in my last paragraph. – Michael Karcher Sep 23 '20 at 10:18
  • 1
    Thanks - Given that I need to set DS:SI as parameters to the xms function, would it make sense for me to copy xms_driver's segment and offset into ES:BX at the top of xms_move_xmb_internal and use call dword ptr es:[bx]? I understand I could force xms_driver and the params struct to be in the same segment by several methods, but I would like to know how the xms call looks when they're not. – knol Sep 23 '20 at 23:00
  • 1
    @knol Copying DS to ES before you modify DS and subsequently calling the XMS driver using ES to access the variable is a correct method. You can still hardcode the offset, no need to put it into bx. I mean something like call dword ptr es:[xms_driver. You can't use the call instruction to load CS directly from another segment register, so putting the contents of the variable xms_driver into ES and BX won't help you unless you use push es; push bx; retf to call into the driver. – Michael Karcher Sep 23 '20 at 23:18
3

To make the various steps in Michael's answer explicit:

  • To call indirectly to another segment, ES:BX needs to contain the address of xms_driver.
  • DS is invalid after inline asm/pseudo-variable assigment, so xms_driver has to be inspected first.

This is the result:

void xms_move_xmb_internal(moveparams *params_ptr)
{      
   asm push es
   asm push ds
   
   _ES = FP_SEG(&xms_driver);
   _BX = FP_OFF(&xms_driver);
   
   _DS = FP_SEG(params_ptr);
   _SI = FP_OFF(params_ptr);   
   
   _AX = 0x0B00;
            
   asm call dword ptr es:[bx]
   
   asm pop ds
   asm pop es
}

void xms_move_xmb
(
   unsigned long int length,
   unsigned int      src_handle,
   unsigned long int src,
   unsigned int      dst_handle,
   unsigned long int dst
)
{      
   moveparams params;   
   params.length     = length;
   params.src_handle = src_handle;
   params.src        = src;
   params.dst_handle = dst_handle;
   params.dst        = dst;
   
   xms_move_xmb_internal(&params);
}
3
  • This is without inspecting the result of the call - placing the result from registers into a struct would have to be done after DS is popped. For 'some reason', this function at first didn't work unless I used my union instead of the FP macros, but then it did. Must've missed an ampersand somewhere the first time... – knol Sep 24 '20 at 2:03
  • You don't need to push/pop ES. Borland's calling convention allows function to modify ES at will. – Michael Karcher Sep 24 '20 at 7:56
  • 1
    I think it would make more sense to just write xms_move_xmb_internal in plain assembly. Your function doesn't contain any standard C so you might as well write it in "standard" assembly instead of something in between that's neither. – user722 Sep 24 '20 at 14:42

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.