You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

TASM汇编调试与编译运行时输入校验逻辑表现不一致

类UNO汇编卡牌游戏Valid_Press循环逻辑不一致问题修复

问题现象

  • 项目为汇编实现的类UNO卡牌游戏,设计Valid_Press循环作为输入校验入口:仅检测到用户合法操作时才退出循环,否则持续等待有效输入
  • 直接编译后运行存在稳定复现的异常:非法操作(例如牌堆顶为红色3时打出蓝色1)会被误判为合法操作,Valid_Press循环直接退出,流程错误推进到下一回合
  • 完全相同的二进制在TASM调试器中运行时逻辑完全符合预期,可正确拦截非法输入,不会提前退出循环
  • 异常触发于Valid_Press循环执行阶段,对应核心汇编代码如下:
Valid_Press:
        cmp [First_Round], 0
        je Skip_Plus_Two ; the first round variable is to determine if the first card on pile was discard card
        cmp [Last_Value], -4 ; check for +2 played last turn
        je Check_For_Two
        Skip_Plus_Two:
        mov [First_Round], 1 ; change it to one so it works only once
        call WaitForLeftClick ; first wait for left click
        Clicked:
        shr cx, 1 ; actual x
        ; check for draw 
        cmp dx, 70
        jb Maybe_Card
        cmp dx, 120
        ja Maybe_Card
        ; now cmp x
        cmp cx, 30
        jb Maybe_Card
        cmp cx, 60
        ja Maybe_Card
        ; if all conditions not reached, press was on picture
        jmp Draw_One_Card   
        
        Maybe_Card:
        push [Interval] ; interval
        push dx
        push cx
        call Is_On_Card ; check for press on card
            
        cmp ax, 0FFFFh ; the check
        je Press_Is_Card    
        xor bx, bx
        xor cx, cx
        xor dx, dx ; card was not valid, jmping to Valid_Press
        jmp Valid_Press ; input not card
            
        Press_Is_Card: 
        ; these calculaions are done to understand position of card pressed in hand based on his x cord and interval between cards  
        ;--------------------------------------------------------
        mov bx, cx ; x cordinate    
        sub bx, 11 ; distance
        mov ax, 18 ; width of card
        add ax, [Interval] ; distance of each card plus interval
        mov cx, ax ; distance of each card + interval
        mov ax, bx ; the actual X   
        div cl ; index in al
        xor ah, ah
        ;---------------------------------------------------------
        add si, ax ; si points to the index of the card that was pressed
        mov [Current_Index], al ; save index of card ( it is legal) 
        mov ax, [si] ; card that was pressed
        xor ah, ah
        mov bx, ax
        mov [Current_Value], bl ; value of card

        push si ; card offset   
        call CanPlayCard
        
        cmp cx, 5 ; the check
        je Card_Is_Right
        jmp Valid_Press ; card cannot be played, waiting for legal input

根因分析

这是典型的未定义寄存器状态依赖导致的海森堡bug,调试环境和实机运行环境的初始状态差异触发了问题:

  1. 寄存器未初始化:代码在Press_Is_Card分支直接使用si作为手牌数组基址,和计算出的索引相加得到卡牌地址,但整个Valid_Press逻辑中从未给si赋手牌数组的初始基址。TASM调试器启动程序时会默认将所有通用寄存器清零,si=0时如果手牌数组恰好定义在数据段偏移0的位置,计算出的卡牌地址就是正确的;直接在DOS环境运行时,si中残留的是系统加载程序时留下的随机值,add si,ax后得到的是完全随机的内存地址,读到的卡牌值是垃圾数据,有概率刚好匹配出牌规则,被误判为合法操作。
  2. 未遵守寄存器调用约定:调用WaitForLeftClick/Is_On_Card/CanPlayCard等子函数时,没有按约定保护主逻辑使用的寄存器,子函数内部修改cx/si等值后,返回主逻辑时寄存器状态不可控。调试器中单步运行时子函数的执行路径和直接运行有细微差异,寄存器残留值恰好符合预期,不会触发判断错误。
  3. 返回值约定不规范:代码通过判断cx是否等于5确认CanPlayCard的校验结果,但x86汇编惯例通过ax传递返回值,子函数如果没有显式给cx赋值,cx的值就是之前逻辑残留的随机数,有概率刚好等于5触发合法判定。

修复方案

按以下顺序修改代码即可解决逻辑不一致问题:

  • 在Valid_Press循环入口处增加寄存器初始化逻辑,每次进入循环都明确加载手牌数组基址到si,不要依赖寄存器的初始残留值:
    Valid_Press:
            mov si, offset Player_Hand  ; 替换为代码中实际的手牌数组变量名
            cmp [First_Round], 0
            je Skip_Plus_Two
            ; 其余原有逻辑保持不变
    
  • 统一子函数返回值约定:修改CanPlayCard逻辑,校验通过时向ax写入5作为合法标记,主逻辑把cmp cx,5改为cmp ax,5做判断,避免依赖随机的cx值。
  • 补充寄存器保护逻辑:调用所有子函数前,把主逻辑后续要用到的si/bx/cx/dx寄存器压栈保存,子函数返回后立刻出栈恢复,避免子函数篡改寄存器值干扰主逻辑。
  • 在所有跳回Valid_Press的分支前,清空本次循环产生的临时寄存器值,避免上一次循环的计算结果污染下一轮输入判断。

内容的提问来源于stack exchange,提问作者ThErOmAnEmPiRe

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.30 10:03:15