我的 TSR 程序在第二次执行时冻结

My TSR program freezes when executed a second time

我对 FASM 有一些经验,并且学得很好。 现在,我想学习 TASM 语法。 我写了一个示例程序,它是 TSR。 这是我的代码

.model tiny
.8086
.stack 200h

.data
        Message db 'Example0 is loaded in memory',0,'$'
.code
        main proc       ;'main' is proc and code region
        mov ax,@data    ;Initialize data segment
        mov ds,ax       
        push es
        xor bx,bx
        mov es,bx
        ;Check interrupt vector 0f5h
        cmp word ptr es:[3d6h],0
        je init         ;If null initialize program and stay resident
        int 0f5h
        cmp ax,'ID'     ;Check string in ax
        jne init        ;If AX != 'GG' initialize TSR
        mov ah,9
        mov dx,offset Message
        int 21h
        init:
        ;Set interrupt vector 0f5h
        mov word ptr es:[3d4h],offset interruptroute
        mov word ptr es:[3d6h],cs
        pop es
        mov ax,3100h
        mov dx,64       ;Reserve 1KB (My .exe is lower than this)
        int 21h
        interruptroute proc far
                shl bx,2
                add bx,offset g0
                call far [cs:bx] ;I assume this array is in code segment 
                ;Here maybe fault in call far 
                iret
        endp interruptroute     


        g0:
                dw getID
                dw @code
        getID proc far
                mov ax,'ID'
                retf
        endp getID
        endp
        end main

还有我的 VirtualBox 截图:

另外我展示了我的命令行:

tasm src\gdos.asm,bin\gdos.obj tlink bin\gdos.obj,bin\gdos.exe

注意:GDOS 是我计划 OS 建造的。

TL;DR: FASM 的语法和语义可能与 MASM/TASM.

的完全不同

在 Turbo 汇编程序 (TASM) 中,FAR 修饰符本身不应在 JMPCALL 指令上使用。而是使用 FAR PTR procname,其中 procname 是用 PROC 定义的过程的名称。提供的代码使用:

call far [cs:bx]

由于这是 CALL 一个人可能倾向于尝试的指令:

call far ptr [cs:bx]

正如我上面所说的 FAR PTR 应该只在操作数是标签时使用。 [cs:bx] 不是标签。问题是 - 什么语法可以用来做一个间接的 FAR JMP without a label?答案是DWORD PTR:

call dword ptr [cs:bx]

第二个问题在这段代码中:

mov ah,9
mov dx,offset Message
int 21h
init:
;Set interrupt vector 0f5h
mov word ptr es:[3d4h],offset interruptroute
mov word ptr es:[3d6h],cs

Int 21h/AH=9 is being used to print a message to standard output. When finished it continues execution into the initialization code. An exit is needed after printing the already loaded message. Add a call to Int 21h/AH=4C 显示消息后。代码可能如下所示:

mov ah,9
mov dx,offset Message
int 21h
mov ax, 4c00h                ; DOS exit function return 0
int 21h

init:
;Set interrupt vector 0f5h
mov word ptr es:[3d4h],offset interruptroute
mov word ptr es:[3d6h],cs

其他观察结果

  • 在提供的代码中,TSR 通过检查中断 0F5h 的段(在 IVT 中)并与 0 进行比较来检查它是否已经加载。如果它是 0,则假定 TSR 未安装。这可能适用于正在测试的环境,但它可能不适用于更广泛的 DOS/hardware 环境。考虑查看 Int 2F multiplexer interrupt. It can be used for detecting the presence of an existing TSR. Randall Hyde has good information on this subject in his book Art of Assembly. Chapter 18.5 Installing a TSR 详细介绍了该主题。
  • 虽然不是bug,但是有这段代码定义了getID:

    getID proc far
            mov ax,'ID'
            retf
    endp getID
    

    我建议不要在这里使用 retf。在用 PROC 定义的任何函数中,如果函数定义为 FAR,TASM/MASM 足够聪明,可以将它们遇到的任何 ret 转换为 retf。他们会将在 NEAR 函数中找到的任何 ret 转换为 retn。这在函数从 NEAR 更改为 FAR 时很有用,反之亦然,因为汇编程序将编码正确的 return 指令。

  • 跳转tableg0定义为:

    g0:
            dw getID
            dw @code
    

    这行得通。相反,它可能是:

    g0:
            dw getID
            dw seg GetID
    

    此方法不依赖于明确使用固定段 @code。如果将来 GetID 放在 @code 以外的不同段中,则不需要对 table.

  • 进行修改
  • 如果像getID这样的所有函数都与中断处理程序在同一个段中,那么跳转中只需要近指针(偏移量)table g0。仅使用 table 的 NEAR 可以使用 interruptroute 中的间接近跳跃:

    shl bx,1           ; The table would have 2 byte NEAR pointers relative to CS
                       ; So multiply the index by 2 instead of 4
    add bx,offset g0   
    jmp [cs:bx]        ; This does a NEAR jmp to the address at [cs:bx]
    

    跳跃 table g0 现在看起来像这样:

    g0:
            dw getID
    

    getID 这样的函数将被标记为 NEAR 并且:

    getID proc near
    

    这仅在 interruptroute 中断处理程序调用的所有函数与 getID 位于同一代码段中时才有效。我假设意图是有一个 table 的调用向量,而 BX 是一个调用 table (g0) 的索引。在这段代码中它会起作用,但我不知道代码的设计意图是说这对于正在处理的实际项目来说是 suitable。