Typically I use DOS file services for disk access, so that I don't have to care if the files are on a network drive, or CD-ROM, or in a mounted DOSBox folder. In the future, however, I would like to write my 8086 assembly programs (for MS-DOS era machines) to run at boot without DOS, so I've been looking into accessing FAT16A/B (non-LBA) partitions using low level BIOS service int 13h
(non-extended).
Near the bottom of this question, just above the code example, I mention a few issues with robustness that I have considered. They will be corrected in the near future. I'm asking this question in the hope that others with more experience will notice potential bugs that I haven't considered.
The goal of this code is to give higher level procedures quick, and easy access to four data files:
- FIELDS.DAT
- TILES.DAT
- AREAS.DAT
- SPRITES.DAT
This is accomplished by determining, and storing as variables:
- The size of the clusters in sectors (
clstSize
) - The first sector of the FAT (
fatSect
) - The first sector of the disk data area (
datSect
) - The cluster numbers of the four data files mentioned above (
aClst
,fClst
,tClst
,sClst
)
The code expects that certain conditions be met (for the code to execute without errors):
- It expects a primary master, or slave ATA hard drive to be installed.
- It expects the variables
drv
andpart
to be set to the desired drive and partition number. - It expects the partition number in
part
to reference a FAT16A/B (non-LBA) partition which must exist entirely in the first 8 gigabytes of the selected hard drive (starts and ends before C/H/S 1023/254/63). - It expects to find in the root directory, a sub-directory named ENGINE, within that sub directory it expects to find the four data files mentioned above.
The main procedure INIT_DISK_VARS
takes as input:
- The output of
int 13h
function 8, which returns the (possibly BIOS translated) disk geometry. - Two byte variables
drv
, andpart
.drv
holds either 80h (master drive number) or 81h (slave drive number).part
holds the partition number (0-3). - The file names of the files that are being searched for in the form of an array of strings. (
fileNames
)
This code has been tested on several of my old machines (a 386, Pentium MMX, and early Pentium 3) and I have encountered no issues.
This code (at the moment) has several issues with robustness that will be corrected before the program is completed and released.
- Handling error if the selected drive isn't installed, or not working. (Error codes returned by
int 13h
when attempting to load the MBR) - Handling error if the MBR doesn't load correctly. (aa55h not last word in
secBuff
) - Handling error if the selected partition is not of type 0x4 (FAT16A), or 0x6 (FAT16B)
- Handling error if the boot sector doesn't load correctly, or isn't correctly located. (aa55h not last word in
secBuff
) - No support for long, mixed-case file names.
- Handling error if the asset sub-directory named ENGINE is not found.
- Handling error if the files FIELDS.DAT, TILES.DAT, AREAS.DAT, and SPRITES.DAT are not found in the sub-directory ENGINE.
- Confirming that the FAT lies at the stored sector number (
fatSect
) by testing it for a copy of the media descriptor byte. (The root directory, and disk data area are not so easy to confirm.) - Handling error if the calculated cylinder number is greater than 1023.
- Handling error if an attempt is made to access a sector that lies beyond the last sector of the partition.
- Confirming that the file cluster numbers point to the files. The four .DAT files are in my own file format. Their structure includes unique marker bytes used to ensure they load correctly.
INIT_DISK_VARS
is typically called as an external procedure from a library at the top of my program before the main loop. You can see the current form of my main assembly file in this Stack Overflow question I asked.
Here I've packaged INIT_DISK_VARS
into a complete program that can be directly assembled by MASM 5.10.
;----------------------------------------------------------------------------|
theStack SEGMENT STACK ;|
;____________________________________________________________________________|
db 16 dup ('THESTACK')
;----------------------------------------------------------------------------|
theStack ENDS ;|
;____________________________________________________________________________|
;----------------------------------------------------------------------------|
varData SEGMENT
;____________________________________________________________________________|
bytPerSec equ 512d ;Number of bytes assumed to be in sectors
;(other sector sizes not supported)
drv db 80h ;selected hard drive 80h=master,81h=slave
part db 0 ;selected partition of selected hdd (0-3)
secPerHd db ? ;number of sectors per head (per track)
hdPerCyl db ? ;number of heads (tracks) per cylinder
varSect dd 0 ;32-bit arbitrary Sector number.
clstSize db ? ;the size of a cluster in sectors
fatSect dd ? ;sector num of 1st sector of FAT
datSect dd ? ;sector num of 1st sector of disk data area
secBuff db bytPerSec dup ('$') ;A buffer for holding a loaded sector.
fileName db "ENGINE " ;the name of the asset sub-directory
db "AREAS DAT" ;the name of the areas data file
db "TILES DAT" ;the name of the tiles data file
db "FIELDS DAT" ;the name of the fields data file
db "SPRITES DAT" ;the name of the sprites data file
aClst dw ? ;the cluster number of AREAS.DAT
tClst dw ? ;the cluster number of TILES.DAT
fClst dw ? ;the cluster number of FIELDS.DAT
sClst dw ? ;the cluster number of SPRITES.DAT
;----------------------------------------------------------------------------|
varData ENDS ;|
;____________________________________________________________________________|
;----------------------------------------------------------------------------|
code SEGMENT
assume cs:code,ds:varData
main PROC
start:
;____________________________________________________________________________|
mov ax, varData
mov ds, ax ;Load the variable segment into ds
cld ;ensure that string operations auto-increment
call INIT_DISK_VARS ;Setup file access variables
mov ax, 4c00H
int 21H ;Return to DOS
INIT_DISK_VARS PROC
;INITIALIZES THE VARIABLES NEEDED TO LOAD FILES FROM THE HARD DRIVE.
;EXPECTS DS TO BE LOADED WITH varData
push ax
push bx
push cx
push dx
push bp
push es
push di
push si
;// INITIALIZE THE DISK GEOMETRY VARIABLES //
mov ah, 8 ;function 8, return disk parameters
mov dl, drv ;dl -> 80h=master or 81h=slave
int 13h ;return the disk parameters as follows:
;All of ch & upper 2 bits of cl form the
;drive's total number of cylinders. (0 based)
;Low 6 bits of cl is num of sectors per head
;dh -> number of heads per cyl (0 based)
inc dh ;dh -> number of heads (counting from 1)
mov hdPerCyl, dh ;initialize hdPerCyl
and cl, 3fh ;cl -> sector number
mov secPerHd, cl ;initialize secPerHd
;// LOAD THE MBR. WE'LL USE THE INFORMATION IN THE SELECTED //
;// PARTITION'S ENTRY TO NAVIGATE TO IT'S BOOT SECTOR. //
call LOAD_SECTOR ;varSect points to the MBR by default. Load
;the MBR into secBuff
lea bx, secBuff ;bx -> MBR in memory
add bx, 446d ;1st part. entry is at byte 446 in the MBR
xor ah, ah
mov al, part ;ax -> current partition number
mov cl, 4 ;multiply ax by 16 (length of part. entry)
shl al, cl ;ax -> offset to start of desired partition
add bx, ax ;bx -> start of desired partition entry
mov dx, WORD PTR [bx+10]
mov ax, WORD PTR [bx+8] ;dx:ax -> boot sector offset
call UPDATE_SECTOR ;varSect -> boot sector
;// LOAD THE BOOT SECTOR. IT CONTAINS ALL THE INFORMATION WE NEED TO //
;// INITIALIZE clstSize, fatSect, datSect, AND LOAD THE ROOT DIRECTORY. //
call LOAD_SECTOR ;load the boot sector into secBuff
lea bx, secBuff
mov al, BYTE PTR [bx+13] ;al -> cluster size in sectors
mov clstSize, al ;clstSize initialized
xor dx, dx
mov ax, WORD PTR [bx+14] ;ax -> number of reserved sectors.
call UPDATE_SECTOR ;offset of boot sector + number of reserved
;sectors = first sector of FAT.
mov dx, WORD PTR varSect+2
mov ax, WORD PTR varSect ;dx:ax -> sector num of 1st sector of the FAT
mov WORD PTR fatSect+2, dx
mov WORD PTR fatSect, ax ;fatSect initialized
xor ah, ah
mov al, BYTE PTR [bx+16] ;ax -> number of copies of the FAT
mov dx, WORD PTR [bx+22] ;dx -> number of sectors per FAT
mul dx ;dx:ax -> total number of sectors taken up
;by all the copies of the FATs
call UPDATE_SECTOR ;varSect -> first sector of root directory
mov di, WORD PTR varSect+2
mov bp, WORD PTR varSect ;Store the 1st sector of the root dir for now.
;datSect needs to be initialized before we
;load the root directory.
mov ax, WORD PTR [bx+17] ;ax -> number of root directory entries
mov cl, 5 ;multiplying ax by 32 (num bytes per entry)
shl ax, cl ;ax -> size of root directory in bytes
mov cl, 9 ;div ax by 512 (number of bytes per sector)
xor dx, dx
shr ax, cl ;dx:ax -> root directory size in sectors
mov cx, ax ;store it, we'll reuse it as the loop cntr
;when searching the root dir for the asset dir
call UPDATE_SECTOR ;varSect now points to the first sector of
;the disk data area
mov dx, WORD PTR varSect+2
mov ax, WORD PTR varSect ;dx:ax -> sector num of 1st sector of the FAT
mov WORD PTR datSect+2, dx
mov WORD PTR datSect, ax ;datSect initialized
mov WORD PTR varSect+2, di
mov WORD PTR varSect, bp ;varSect -> root directory
;// LOAD THE ROOT DIRECTORY AND THEN SEARCH FOR THE SUB-DIRECTORY //
;// CONTAINING THE DATA FILES. //
xor bp, bp ;Pass FIND_ENTRY the string index number of
;the asset directory's name.
xor dx, dx
mov ax, 1 ;We'll increment through root dir's sectors
;by passing UPDATE_SECTOR 1 iteratively.
FIND_ASSET_DIR:
call LOAD_SECTOR ;load sector of the root dir into secBuff
call FIND_ENTRY ;is the asset dir entry in this sector?
test bx, 0ffffh ;if so, bx will be non zero
jnz ASSET_FOUND ;if entry found, terminate the loop.
call UPDATE_SECTOR ;if not found, point to the next sector
loop FIND_ASSET_DIR ;Loop till asset dir is found, or all sectors
;have been searched through.
;handle error if not found
;(to be implemented in the future)
ASSET_FOUND:
mov di, bx ;store the cluster number in di as input for
;LOAD_DATA_SECTOR
;// LOAD THE ASSET DIRECTORY, SEARCH FOR THE DATA FILES, AND STORE //
;// THEIR CLUSTER NUMBERS. //
mov bp, 1 ;bp -> file name str index of 1st file to find
lea si, aClst ;si -> first cluster number to initialize
xor dx, dx ;dx -> initial sector offset into asset dir
mov cx, 4 ;loop cntr -> number of files to find
INIT_CLST_NUMS:
call LOAD_DATA_SECTOR ;load a sector of the asset directory
cmp di, 0ffffh ;If di does not = 0ffffh, sector offset valid.
jne NOT_END_OF_DIR ;If sect offset valid, search loaded sector
;If invalid, file not found. Handle error.
;(to be implemented in the future)
NOT_END_OF_DIR:
call FIND_ENTRY ;Search sector of the asset dir for file entry
test bx, 0ffffh ;If found, bx holds a sector num (non-zero).
jnz FILE_FOUND ;If not found (bx = 0), search next sector.
inc dx ;increment the sector offset
jmp INIT_CLST_NUMS ;load the next sector
FILE_FOUND:
mov WORD PTR [si], bx ;store cluster number in it's variable
inc bp ;move on to the next file name to search for
add si, 2 ;move on to next cluster number variable
xor dx, dx ;reinitialize dx to sector offset 0
loop INIT_CLST_NUMS ;loop until either all files are found, or one
;of the files is not found.
pop si
pop di
pop es
pop bp
pop dx
pop cx
pop bx
pop ax
ret
INIT_DISK_VARS ENDP
FIND_ENTRY PROC
;SEARCHES A SINGLE SECTOR OF A DIRECTORY LOADED INTO secBuff FOR A
;SPECIFIED FILE/SUB DIRECTORY ENTRY
;IF FOUND, RETURNS THE FILE/SUB DIRECTORY'S CLUSTER NUMBER IN BX
;IF NOT FOUND, RETURNS 0 IN BX
;ALTERS BX
;EXPECTS A FILE NAME STRING INDEX NUMBER IN BP
;EXPECTS A SECTOR OF A DIRECTORY (ROOT, OR SUB) TO BE LOADED INTO secBuff
;EXPECTS DS TO BE LOADED WITH varData
push ax
push cx
push es
push si
push di
lea si, fileName ;si -> file name strings
mov ax, 11d ;ax -> file name length in bytes/characters
mul bp ;ax -> offset to file name string
add si, ax ;ds:si -> desired file name as source input
;for LODS
mov di, ds
mov es, di
lea di, secBuff ;es:di -> first entry in ds:secBuff as input
;for SCAS
mov cx, 16d ;outer loop cntr -> num entries in a sector
ENTRY_SEARCH:
push cx ;store outer loop cntr
push si ;store start of the file name
push di ;store start of the entry
mov cx, 11d ;inner loop cntr -> length of file name
STR_MATCH:
lodsb ;al -> character from file name
scasb ;Does it match the char in the entry?
jne NOT_ENTRY ;If not, test next entry.
loop STR_MATCH ;If loop falls through, the strings match.
pop di ;di -> start of the entry
mov bx, WORD PTR [di+26] ;bx -> entry's cluster number
pop si
pop cx
pop di
pop si
pop es
pop cx
pop ax
ret
NOT_ENTRY:
pop di ;di -> start of the entry
add di, 32d ;di -> start of next entry
pop si ;si -> start of file name
pop cx ;restore the outer loop cntr
loop ENTRY_SEARCH ;loop till we've either found a match, or
;have tested every entry in the sector
;without finding a match.
xor bx, bx ;if we're here no match was found.
;return 0.
pop di
pop si
pop es
pop cx
pop ax
ret
FIND_ENTRY ENDP
LOAD_DATA_SECTOR PROC
;OFFSETS TO A SPECIFIED SECTOR OF A FILE/SUB-DIRECTORY BY CONSULTING
;THE FAT, AND FOLLOWING THE CLUSTER CHAIN. THE SECTOR IS THEN LOADED INTO
;secBuff. IF THE NEXT CLUSTER NUMBER IS 0FFFFH, THE END OF THE FILE HAS
;BEEN REACHED, AND 0FFFFH IS RETURNED IN DI.
;ALTERS DI IF END OF FILE IS ENCOUNTERED
;EXPECTS A CLUSTER NUMBER IN DI
;EXPECTS AN ABSOLUTE SECTOR OFFSET IN DX (FROM FIRST SECTOR OF FILE)
;EXPECTS DS TO BE LOADED WITH varData
;EXPECTS clstSize, fatSect, & datSect TO BE INITIALIZED WITH CORRECT VALUES
push ax
push bx
push cx
push dx
push bp
push di
;// DIVIDE THE ABSOLUTE SECTOR OFFSET BY clstSize. //
;// THE QUOTIENT IS THE CLUSTER OFFSET, THE REMAINDER IS THE SECTOR //
;// OFFSET INTO INTO THAT CLUSTER. //
;// CX -> CLUSTER OFFSET AS THE CLUSTER CHAIN LOOP COUNTER (IF NOT 0) //
;// BP -> THE SECTOR OFFSET INTO THE CLUSTER. LATER ADDED TO varSect. //
mov ax, dx ;ax -> absolute sector offset
xor bh, bh
mov bl, clstSize ;bx -> size of a cluster in sectors
xor dx, dx
div bx ;ax -> cluster offset
;dx -> sector offset into that cluster
mov bp, dx ;backup the sector offset into bp
test ax, 0ffffh ;If the cluster offset is 0, di should already
jz NO_CLST_CHN ;have desired cluster number. Skip the loop.
mov cx, ax ;loop cntr -> cluster offset
;// IF CLUSTER OFFSET > 0, DI NEEDS A NEW CLUSTER NUMBER. //
;// BEGIN THE CLUSTER CHAIN LOOP, WHICH OPERATES AS FOLLOW: //
;// STEP 1) LOAD THE FIRST SECTOR OF THE FAT //
;// STEP 2) DIVIDE OLD CLUSTER NUMBER BY 256 //
;// QUOTIENT -> OFFSET TO SECTOR WITH THE NEW CLUSTER NUMBER //
;// REMAINDER -> OFFSET TO CLUSTER NUMBER IN THAT SECTOR //
;// STEP 3) ADD THE SECTOR OFFSET (PREVIOUS QUOTIENT) TO varSect, THEN //
;// LOAD THE NEW SECTOR OF THE FAT. //
;// STEP 4) MULTIPLY THE CLUSTER NUMBER OFFSET (PREVIOUS REMAINDER) BY //
;// 2, IT IS NOW THE BYTE OFFSET TO THE CLUSTER NUMBER. //
;// STEP 5) CHECK THE CLUSTER NUMBER, IF IT'S 0FFFFH, THEN THE END OF //
;// THE FILE OR DIRECTORY HAS BEEN REACHED. IF NOT, THEN IT'S //
;// A NEW CLUSTER NUMBER IN THE CHAIN, PASS IT TO DI. //
;// STEP 6) REPEAT STEPS 1-5 <CLUSTER OFFSET> NUMBER OF TIMES. //
CLST_CHN:
mov bx, WORD PTR fatSect+2
mov ax, WORD PTR fatSect
mov WORD PTR varSect+2, bx
mov WORD PTR varSect, ax ;varSect -> first sector of the FAT
mov ax, di ;ax -> cluster number
mov bx, (bytPerSec/2) ;bx -> 256 since each clst num is 2 bytes
xor dx, dx
div bx ;ax -> offset to FAT sector with clst num
;dx -> offset to clst num in words
push dx ;must clear out dx as input for UPDATE_SECTOR
xor dx, dx ;dx:ax -> offset to FAT sector with clst num
call UPDATE_SECTOR ;varSect -> FAT sector that has clst num
call LOAD_SECTOR ;load the sector of the FAT into secBuff
pop dx ;restore offset to clst num in words
lea bx, secBuff ;bx -> secBuff
shl dx, 1 ;dx -> byte offset of the cluster number
add bx, dx ;bx -> desired cluster number
cmp WORD PTR [bx], 0ffffh ;is the cluster number 0ffffh?
jne NEW_CLST_NUM ;if not, accept it as the new cluster number
pop di ;if so, return.
or di, 0ffffh ;Return 0ffffh in di so that the calling
pop bp ;procedure knows that either the given
pop dx ;sector offset is greater than the length of
pop cx ;file/sub-dir in sectors, or the end of
pop bx ;the file/sub-dir has been reached.
pop ax
ret
NEW_CLST_NUM:
mov di, WORD PTR [bx] ;di -> new cluster number
loop CLST_CHN ;Loop until we've either encountered a cluster
;number of 0ffffh, or have offset to the
;desired cluster.
NO_CLST_CHN:
;// LOAD THE FIRST SECTOR OF THE DISK DATA AREA INTO varSect //
;// ADD TO varSect THE SECTOR OFFSET INTO THE CLUSTER. //
;// SUBTRACT 2 FROM THE CLUSTER NUMBER THEN MULTIPLY IT BY clstSize, //
;// PRODUCT IS SECTOR OFFSET (FROM THE SECTOR CURRENTLY IN varSect) //
;// TO THE DESIRED SECTOR OF THE FILE. //
mov cx, WORD PTR datSect+2
mov ax, WORD PTR datSect
mov WORD PTR varSect+2, cx
mov WORD PTR varSect, ax ;varSect -> first sector of disk data area
xor dx, dx
mov ax, bp ;dx:ax -> sector offset into cluster
call UPDATE_SECTOR ;add that to varSect
mov ax, di ;pass the cluster number into ax as input for
;UPDATE_SECTOR
dec ax ;dec the cluster number twice since the first
dec ax ;two entries in the FAT table do not have
;corresponding clusters
xor dh, dh
mov dl, clstSize ;dx -> size of the clusters in sectors
mul dx ;dx:ax -> offset to start of desired cluster
call UPDATE_SECTOR ;varSect -> desired sector of file/directory
call LOAD_SECTOR ;Load the sector into secBuff.
pop di
pop bp
pop dx
pop cx
pop bx
pop ax
ret
LOAD_DATA_SECTOR ENDP
LOAD_SECTOR PROC
;THIS PROCEDURE LOADS THE SECTOR POINTED TO BY varSect INTO DS:secBuff
;EXPECTS DS TO BE LOADED WITH varData
push ax
push bx
push cx
push dx
push es
;// USE DIV32 TO DIVIDE varSect INTO SEPARATE C/H/S NUMBERS //
mov dx, WORD PTR varSect+2 ;dx -> MSW of varSect
mov ax, WORD PTR varSect ;ax -> LSW of varSect
xor bh, bh
mov bl, secPerHd ;bx -> sectors per head
call DIV32 ;bl -> sector number (counting from 0, 0-62)
inc bl ;correct the range of the sector number (1-63)
mov cl, bl ;store that value in cl
mov bl, hdPerCyl ;bx -> heads per cylinder
call DIV32 ;bl -> head number, ax -> cylinder number
;// CONVERT THE SEPARATE CYLINDER NUMBER AND SECTOR NUMBER INTO THE //
;// UNIFIED VALUE IN CX THAT INT 13H, FUNCTION 2 ACCEPTS. //
;// THIS IS DONE USING THE FORMULA: //
;// (cylNum shl 8) OR ((cylNum AND 768d)shr 2) OR sectNum //
mov dx, ax ;need 2 copies of cylNum
xor ah, ah
xchg ah, al ;ah -> lower 8 bits of cylNum
and dx, 768d
shr dx, 1
shr dx, 1 ;dx -> upper 2 bits of cylNum
or ax, dx ;ax -> full cylinder number
or al, cl ;OR the sector number into al
mov cx, ax ;cx -> cyl/sectNum (in int 13h format)
mov dh, bl ;dh takes head number
mov ax, varData
mov es, ax
lea bx, secBuff ;es:bx -> ds:secBuff
mov ah, 2 ;calling int 13 function 2, read in sectors
mov al, 1 ;loading a single sector
mov dl, drv ;dl -> drive number. 80h=master, 81h=slave
int 13h ;load the sector into secBuff
pop es
pop dx
pop cx
pop bx
pop ax
ret
LOAD_SECTOR ENDP
UPDATE_SECTOR PROC
;ADDS A GIVEN NUMBER OF SECTORS TO varSect
;EXPECTS DS TO BE LOADED WITH varData
;EXPECTS VALID VALUES IN secPerHd & hdPerCyl
;EXPECTS THE AMOUNT OF SECTORS TO ADD IN DX:AX
push bp
mov bp, ax
or bp, dx
test bp, 0ffffh ;If no bits set, number of sectors to add is 0
jnz UPD_CHS ;Proceed if at least 1 bit is set.
pop bp ;Otherwise, return.
ret
UPD_CHS:
pop bp
push ax
push bx
push cx
push dx
mov cx, WORD PTR varSect+2
mov bx, WORD PTR varSect ;cx:bx -> varSect
call ADD32 ;add varSect to dx:ax
mov WORD PTR varSect+2, dx
mov WORD PTR varSect, ax ;varSect = dx:ax + varSect
pop dx
pop cx
pop bx
pop ax
ret
UPDATE_SECTOR ENDP
DIV32 PROC
;DIVIDES A 32-BIT VALUE BY A 16-BIT VALUE.
;ALTERS AX
;ALTERS BX
;ALTERS DX
;EXPECTS THE 32-BIT DIVIDEND IN DX:AX
;EXPECTS THE 16-BIT DIVISOR IN BX
;RETURNS THE 32-BIT QUOTIENT IN DX:AX
;RETURNS THE 16-BIT REMAINDER IN BX
push di
push si
mov di, ax ;di -> copy of LSW of given dividend
mov ax, dx ;ax -> MSW of given dividend
xor dx, dx ;dx:ax -> 0:MSW
div bx ;ax:dx -> ax=MSW of final quotient, dx=remainder
mov si, ax ;si -> MSW of final quotient
mov ax, di ;dx:ax -> dx=previous remainder, ax=LSW of given dividend
div bx ;ax:dx -> ax=LSW of final quotient, dx=final remainder
mov bx, dx ;bx -> final remainder
mov dx, si ;dx:ax -> final quotient
pop si
pop di
ret
DIV32 ENDP
ADD32 PROC
;SUMS TWO GIVEN 32-BIT VALUES.
;ALTERS AX
;ALTERS DX
;EXPECTS THE SOURCE 32-BIT VALUE IN CX:BX
;EXPECTS THE DESTINATION 32-BIT VALUE IN DX:AX
push bp ;use bp to see if cx:bx = 0
mov bp, bx ;bp -> bx
or bp, cx ;If bp now contains no set bits, cx:bx = 0
test bp, 0ffffh ;If the given value to add is 0, return.
jnz ADD_32 ;Otherwise, add cx:bx to dx:ax
pop bp
ret
ADD_32:
pop bp
add ax, bx ;LSW of 32-bit sum = ax + bx
adc dx, cx ;MSW of 32-bit sum = dx + cx + cf
ret
ADD32 ENDP
;----------------------------------------------------------------------------|
main ENDP ;|
;|
code ENDS ;|
;|
END start ;|
;____________________________________________________________________________|
CX:BX
is zero then the cascaded additionadd ax, bx
adc dx, cx
can still be safely executed. It won't harm results. \$\endgroup\$