1

I'm writing a program that will run in 16bit real-mode in DOS, compiling with GCC, and testing under DOSBox.

This is the linker script I am using to create the executable (coped from https://github.com/skeeto/dosdefender-ld31/blob/master/com.ld):

OUTPUT_FORMAT(binary)
SECTIONS
{
    . = 0x0100;
    .text :
    {
        *(.text);
    }
    .data :
    {
        *(.data);
        *(.bss);
        *(.rodata);
    }
    _heap = ALIGN(4);
}

I can print strings terminated with a '$', but cannot with a 2 character string containing a digit and a '$'; I get a memory dump as you can see below:

DOSBox showing dumped memory

Here's my makefile, I pass flags to gcc minimize size, and not to link to a C runtime library.

CC      = gcc
DOS     = dosbox
CFLAGS  = -std=gnu99 -Wall -Wextra -Os -nostdlib -m32 -march=i386 \
  -Wno-unused-function \
  -ffreestanding -fomit-frame-pointer -fwrapv -fno-strict-aliasing \
  -fno-leading-underscore -fno-pic -fno-stack-protector \
  -Wl,--nmagic,-static,-Tcom.ld,--verbose=99


.PHONY : all clean test

all:
    $(CC) -o bottles.com $(CFLAGS) main.c

clean :
    $(RM) *.com

test : bottles.com
    $(DOS) $^

%.com : %.c
    $(CC) -o $@ $(CFLAGS) $<

Here is 'main.c':

asm (".code16gcc\n"
     "call  dosmain\n"
     "mov   $0x4C,%ah\n"
     "int   $0x21\n");

static void print(char *string)
{
    asm volatile ("mov   $0x09, %%ah\n"
                  "int   $0x21\n"
    : /* no output */
    : "d"(string)
    : "ah");
}

static int _pow(int a, int b)
{
    int x = a;
    for (int i=1; i < b; i++) {
        x = x * a;
    }
    return x;
}

static int getdigits(int val)
{
    int d = 0;
    int n = val;
    while (n != 0) {
        n /= 10;
        d++;
    }
    return d;
}

static void putint(int val)
{
    const int digits_num = getdigits(val);
    const int base10_m = _pow(10, (digits_num - 1));
    int r = val;
    const char eof = '$';
    char digit_s[2] = {0,eof};
    for (int i = base10_m; i >= 10 ; i/=10) {
        digit_s[0] = '0' + ( r - ( r % i ) ) / i ;
        print(digit_s);
        r -= ( r - ( r % i ));
    }
    digit_s[0] = '0' + r;
    print(digit_s);
}

int dosmain(void)
{
    print(1337);
    return 0;
}

What is causing the memory to be dumped as shown above?

edition
  • 638
  • 14
  • 31

1 Answers1

1

I solved my problem by looking at this page: http://spike.scu.edu.au/~barry/interrupts.html#ah02.

On the page there's another interrupt function, 0x02, which prints out a character pushed to the stack.

I wrote this function to call this interrupt function:

static void putchar(char ch)
{
    asm volatile ("mov $0x02, %%ah\n"
                  "int $0x21\n"
                  : /* no output */
                  : "d"(ch)
                  : "ah");
}

And sure enough, it works: DosBOX showing my program outputting "1337"

edition
  • 638
  • 14
  • 31
  • 1
    That inline asm is almost right, but it misses telling the compiler that AL gets a copy of the character output by `int $0x21`. So it could break with other surrounding code, with optimization enabled. You should tell it that the whole AX is clobbered, not just AH. (Or use a constraint to ask the compiler to put `2` in AH so it will know the value's still there after.) Also, `ch` is an unfortunately choice of C variable name, since it's also the name of an x86 register. – Peter Cordes Apr 25 '21 at 04:20
  • 1
    Similarly for the `print` function in your question, also clobber AX. Worse, it's missing a `"memory"` clobber (or dummy memory input) to tell the compiler that the memory pointed-to by DX is also an input, not just the DX integer itself. [How can I indicate that the memory \*pointed\* to by an inline ASM argument may be used?](https://stackoverflow.com/q/56432259). This question might be a duplicate, if that was the only bug in your int->string and print. Err, if you had passed an int to `putint` instead of to `print`, and don't check warnings for implicit conversion from int to `char*`. – Peter Cordes Apr 25 '21 at 04:23