7

Consider the following macros:

pixelFast MACRO
    ; This macro draws a pixel, assuming the coordinates are already loaded in cx&dx and the color is in al.
    xor bh, bh
    mov ah, 0ch
    int 10h
ENDM

drawRect MACRO x1, y1, x2, y2, color
    LOCAL @@loop, @@row_loop
    xor cx, cx
    mov dx, y1
    mov al, BYTE PTR [color]

    @@loop:
        mov cx, x1
        @@row_loop:
            pixelFast

            inc cx
            cmp cx, x2
            jna @@row_loop

        inc dx
        cmp dx, y2
        jna @@loop
ENDM

rendToolBar MACRO
    drawRect COLORDISP_X1, COLORDISP_Y1, COLORDISP_X2, COLORDISP_Y2, foreground_color
    mov temp_color, 36h
    drawRect COLORBTN1_X1, COLORBTN1_Y1, COLORBTN1_X2, COLORBTN1_Y2, temp_color
    mov temp_color, 2Eh
    drawRect COLORBTN2_X1, COLORBTN2_Y1, COLORBTN2_X2, COLORBTN2_Y2, temp_color
    mov temp_color, 4h
    drawRect COLORBTN3_X1, COLORBTN3_Y1, COLORBTN3_X2, COLORBTN3_Y2, temp_color
    mov temp_color, 2Bh
    drawRect COLORBTN4_X1, COLORBTN4_Y1, COLORBTN4_X2, COLORBTN4_Y2, temp_color
ENDM

Somewhere in my code, I use the rendToolBar macro. It is supposed to draw a big white canvas, and then a small square and next to it some smaller squares in a certain pattern, which is irrelevant for my question. Notice that rendToolBar calls drawRect 5 times. I followed this code in turbo debugger (because something went awfully wrong) and noticed that in the 4th execution of the drawRect macro, the "int 10h" from pixelFast is not actually "int 10h", but rather "int 2". This causes an NMI, which messes stuff up for my program. I want to know what makes TASM expand the macro differently for that line in the 4th call for that macro, despite the fact that this line "int 10h" does not rely on any macro arguments. enter image description here If you look at this image, you can see the unexpected "int 2" there, which was supposed to be an "int 10". After it, you can see:

cmp [bx+si], ax
add ch, bh
cmp [bx+03], dx

According to the macro's source code, These 3 instructions were in fact supposed to be

inc cx
cmp cx, COLORBTN3_X2
jna @@row_loop

There are some other instructions which are a bit off before the interrupt, but you get the point.

Itamar
  • 137
  • 1
  • 8
  • and what's the question? – Kamiccolo Jul 25 '16 at 18:43
  • @Kamiccolo The question is there, right above the picture of turbo debugger. – Itamar Jul 25 '16 at 22:05
  • I wish all the beginner debug-help questions on SO actually had a debugger screenshot or something containing all the information needed to debug the problem they're having. So often people post code and just ask why it segfaults without even saying which instruction faults. – Peter Cordes Jul 26 '16 at 13:05

1 Answers1

6

Consider the math to transform logical (segment:offset) addresses into linear ones:

CS:IP = 49ae:03cc = 49eac where 3cc is the offset of the first unexpected byte.

SS:SP = 39ed:fffc = 49ecc.

Visualizing the two linear addresses, we have

   |       |
   | 49ecc | <-- Stack pointer, going down
   |       |

 Only 32 bytes below

   |       |
   | 49eac | <-- Execution flow, going up
   |       |

Your stack must have clashed into the code segment at some point before the screenshot.
Try setting up the stack so that it is far enough from the code.


The maximum stack size in real mode is 64KiB, as this is the size of a segment.
In DOS is safe to assume that the memory after your program is not used1 and, as long as it exists, so you can use it for the stack. This is not wasting memory as DOS is not multitasking.
Note that the stack segment won't take space on the binary file unless you explicitly define things in it.

There are two ways to manage the stack:

  1. Using the assembler
    See the TASM manual for reference, page 92.

    If you are using the STACK directive just put an upper bound on the estimated size of the stack.

    If you are writing an EXE you can use the model modifier FARSTACK.
    SS:SP should be set based on the values the linker wrote on the MZ header.
    This let you have a full 64KiB stack by not putting the stack segment into the dgroup.

     

  2. Manually
    If you know you won't need a full 64KiB of stack, you can put it at the end of the data segment (that for COM is also the code segment)

    ;COM                      ;EXE
    mov ax, cs                mov ax, ds  ;Assume SMALL memory model
    mov ss, ax                mov ss, ax  ;see below
    xor sp, sp                xor sp, sp
    

    This gives 64KiB - <code+data size>.

    If you need a full 64KiB stack you can just use the next available segment

    ;COM                      ;EXE
    mov ax, cs                mov ax, ds      ;Assume SMALL memory model, if not          
    add ax, 1000h             add ax, 1000h   ;use the symbol for the last data      
    mov ss, ax                mov ss, ax      ;segment
    xor sp, sp                xor sp, sp
    

    This assumes that the last segment if fully used but saves you from some segment/offset/symbols arithmetic.


1 This is because DOS is not multitasking and programs are loaded above TSR programs.
A non resident part of COMMAND.COM is loaded a the top of the conventional memory but it can be overwritten.

Margaret Bloom
  • 41,768
  • 5
  • 78
  • 124
  • I did the math and determined that this is indeed the problem. What is the clear-cut way of re-arranging the locations of my segments? – Itamar Jul 25 '16 at 22:35
  • I managed to solve it by defining a larger stack, but I want to know if there is a "cleaner" solution, that doesn't involve using more memory than I actually need. – Itamar Jul 25 '16 at 22:44
  • 1
    @Itamar so use less stack in your code (less push/pop). Also you have to take into account the stack space used by external calls, like `int 10h`. "than I actually need" - but you need that stack, as you did use it (unused stack doesn't modify memory). Finally, I'm just curious, why do you call that macro pixel *fast*, when you call BIOS interrupt instead of writing the pixel directly to VRAM? It's the slowest possible way to draw a pixel in DOS (to call BIOS). :D A bit funny name... – Ped7g Jul 26 '16 at 07:02
  • @Ped7g I call it "fast" because I noticed that I can see the drawing process of big rectangles when I use a procedure for it, with arguments being passed through the stack. With the macro, no arguments are passed and there is no return being stored. I tried direct writes, and I still use them for certain things, but for some reason it didn't work in this case. – Itamar Jul 26 '16 at 13:27
  • @Ped7g So basically, I call it "pixelFast" because it is faster than the procedure that I was using before I switched to macros. – Itamar Jul 26 '16 at 13:43
  • @Ped7g Anyway, I just switched back to direct writes. Thanks for pointing that out. It is significantly faster. – Itamar Jul 26 '16 at 13:49