奇怪的宏 (TASM)

Weird Macros (TASM)

考虑以下宏:

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

在我的代码中的某处,我使用了 rendToolBar 宏。它应该画一个大的白色canvas,然后是一个小方块,然后在它旁边以某种模式绘制一些较小的方块,这与我的问题无关。 请注意,rendToolBar 调用了 drawRect 5 次。我在 turbo 调试器中遵循了这段代码(因为出现了严重错误)并注意到在 drawRect 宏的第 4 次执行中,来自 pixelFast 的 "int 10h" 实际上不是 "int 10h",而是 "int 2" .这会导致 NMI,这会弄乱我的程序。我想知道是什么让 TASM 在第 4 次调用该宏时为该行以不同方式扩展宏,尽管这一行 "int 10h" 不依赖于任何宏参数。 如果你看这张图片,你会看到意想不到的 "int 2",它应该是 "int 10"。之后可以看到:

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

根据宏的源代码,这 3 条指令实际上应该是

inc cx
cmp cx, COLORBTN3_X2
jna @@row_loop

在中断之前还有一些其他指令有点偏离,但你明白了。

考虑将逻辑(段:偏移)地址转换为线性地址的数学:

CS:IP = 49ae:03cc = 49eac 其中 3cc 是第一个意外字节的偏移量。

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

可视化两个线性地址,我们有

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

 Only 32 bytes below

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

您的堆栈一定在屏幕截图之前的某个时刻与代码段发生冲突。
尝试设置堆栈,使其距离代码足够远。


实模式下的最大堆栈大小为 64KiB,因为这是一个段的大小。
在 DOS 中可以安全地假设你的程序之后的内存没有被使用1并且,只要它存在,那么你就可以将它用于堆栈。 这不是浪费内存,因为 DOS 不是多任务处理。
请注意,除非您在其中明确定义内容,否则堆栈段不会在二进制文件上使用 space。

有两种管理堆栈的方法:

  1. 使用汇编器
    请参阅 TASM manual 以供参考,第 92 页。

    如果您正在使用 STACK 指令,只需为堆栈的估计大小设置一个上限。

    如果您正在编写 EXE,则可以使用模型修饰符 FARSTACK
    SS:SP 应根据链接器在 MZ header.
    上写入的值进行设置 通过不将堆栈段放入 dgroup.

    ,您可以拥有完整的 64KiB 堆栈

  2. 手动
    如果你知道你不需要完整的 64KiB 堆栈,你可以把它放在数据段的末尾(对于 COM 来说也是代码段)

    ;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
    

    这给出了 64KiB - .

    如果您需要完整的 64KiB 堆栈,您可以使用下一个可用段

    ;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
    

    这假定最后一段如果被完全使用但可以使您免于某些 segment/offset/symbols 算法。


1 这是因为DOS不是多任务的,程序是loaded above TSR programs.
COMMAND.COM的非常驻部分被加载到常规内存的顶部,但它可以被覆盖。