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. 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.
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:
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With