assemblymemoryx86-16tasmoff-by-one

TASM addressing off by one


I'm currently implementing Snake for university, and we have to use TASM for that.

My main game data is laid out like this (using C syntax):

struct GameLine {
    uint8_t direction_bits[10];  // 2 bits per entry, the first entry is invalid since valid x positions start at one
    uint8_t collision_bits[5];  // 1 bit per entry, the first entry is again invalid but has to stay zero
    uint8_t aux;  // padding such that GameLine is 16 bytes big, also used to store other information at times.
};

struct GameData {
    struct GameLine lines[23];
} PHYSICAL_GAME_DATA;

The issue is that writing the direction bits each frame overwrites the read collision bits for large x positions (38 is the maximum position, but it happens earlier). I say "read collision bits" because I wasn't able to verify where the physical_game_data actually resides because I don't know how to instruct the assembler (tasm) and/or linker (tlink) and/or debugger (td) to show me that.

The declarations in my code are:

physical_game_data   DB 368 DUP (0)
; ..., Some `EQU`s for `aux` data I'm using to store other information
game_data EQU (offset physical_game_data) - 16  ; Since 1 <= y <= 23, this allows addressing a line via `game_data + (y << 4)`

The movs that I'm most worried about here (there are a few more, but these are the two that make the bug visible) are these two:

; This one is for writing a new direction
mov     BYTE [ds:game_data + bx],   dh
; ...
; This one is for reading a collision bit to check if the player lost
mov     dl,     [ds:game_data + 10 + si]

Looking at those in td however yields this:

mov [bx+05AF],dh
; ...
mov dl,[si+05B8]

The difference between 0x05AF and 0x05B8 however is 9, not 10. I've currently "fixed" the issue by adding 11 in the source code instead, which means the bug doesn't occur, but I'd rather fix this issue properly.

I'm assuming that this is me misunderstanding something about TASM or x86/x86-16 assembly, but I don't know what exactly that misunderstanding is.

Here's a complete file that exhibits this issue:


    .model  tiny
    .286

.data
datastart:
physical_game_data  DB 368 DUP (0)
; ..., Some `EQU`s for `aux` data I'm using to store other information
game_data           EQU (offset physical_game_data) - 16  ; Since 1 <= y <= 23, this allows addressing a line via `game_data + (y << 4)`

.code

        ORG 100h
start:
        mov         ax,                         seg datastart
        mov         ds,                         ax

        ; This one is for writing a new direction
        mov         BYTE [ds:game_data + bx],   dh
        ; ...
        ; This one is for reading a collision bit to check if the player lost
        mov         dl,                         [ds:game_data + 10 + si]

        ; Exit
        mov         ax,                         4C00h
        int         21h
end start

Compiled using tasm MRE.ASM, tlink MRE.OBJ and opened with td using td MRE.EXE, the decompiled code reads:

mov ax,48AF
mov ds,ax
mov [bx+0103],dh
mov dl,[si+010C]
mov ax,4C00
int 21

Where, again, 0x10C - 0x103 is 9.

If it is of interest, I'm running this code under Dosbox.

Thanks!


Solution

  • Your problem is the BYTE keyword. In MASM mode the BYTE keyword evaluates to the size of a BYTE: 1. This is added to the expression in the square brackets [] as square brackets primarily act as an addition operator in MASM mode. You'd need to write BYTE PTR [ds:game_data + bx] instead, although your next instruction demonstrates that you can also just leave it out.