Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Weird Macros (TASM)

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.

like image 380
Itamar Avatar asked Jul 25 '16 18:07

Itamar


1 Answers

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.

like image 86
Margaret Bloom Avatar answered Sep 22 '22 08:09

Margaret Bloom