How to subtract two 64 bit integers in 8086 assemb

2019-07-01 10:42发布

问题:

Write a program called SUB64 to subtract the 64-bit integer in memory locations 0x0150 and 0x0154 from the 64-bit integer in 0x0160 and 0x0164. Store the result in memory location 0x0170 and 0x0174.

I understand the logic behind separating it into smaller pieces since we can't fit 64 bits into the registers. And I know we subtract the least significant pieces first. I'm struggling with actually coding it. Does it matter which GPIO we use? This is my example, so maybe you can see how I'm thinking. Maybe I'm not that far off, but it feels like it.

MOV AX, 0X0150
MOV BX, 0X0154
MOV CX, 0X0160
MOV DX, 0X0164
SUB BX, DX
SUB AX, CX
MOVE 0X0174, BX
MOVE 0X0170, AX

I stored each half of the 64 bit integer into the registers. Then I subtracted the registers and put them into the memory locations. Is that hex format okay? or does it need to be something like 0174h?

Even if this is somehow correct, do I need a return statement or a header or anything to make this actually compile? (I'm using NASM and DosBox)

This is the first of 12 similar programs I have to write. SO ANY push in the right direction will hopefully get me on my way!

回答1:

1)

the 64-bit integer in memory locations 0x0150 and 0x154

that's not very exact task description, the 64b integer is in memory at locations from 0x0150 to 0x0157 (8 bytes in total). That description sounds like by default a memory unit dword (32 bits) is used, which in 16b real mode is not very sensible default data size.

2) mov ax,0x0150 does not load the value from memory, it loads that constant 0x0150 into ax. If you want to load the actual value from memory, you have to dereference that memory address, like for example:

mov si,0x0150  ; set si to contain address of input number1
mov ax,[si]    ; load least significant 16b word (LSW) of it into ax

; you can even use absolute addressing like:
mov  ax,[0x0150]  ; usually not practical, but possible

Now if you would want to put all addresses of all words (3 * 4 words of memory will be used, num1, num2 and result, all of them 4 words long), you would run out of registers quickly. But actually you can use relative addressing in 16b mode, so this is possible:

mov   si,0x0150  ; set si to contain address of input number1
mov   ax,[si]
mov   bx,[si+2]
mov   cx,[si+4]
mov   dx,[si+6]
; here dx:cx:bx:ax concatenated into single 64b number contains the number1

But the number2 can be addressed by base address of number1 too, so you can also continue by subtraction:

sub   ax,[si+0x10]   ; subtraction of LSW, ignoring CF (borrow)
sbb   bx,[si+0x12]   ; continuing by subtracting adjoining 16 bits
    ; but this time CF will affect the result, to make any "borrowing"
    ; happening while subtracting the LSW to propagate into upper 16 bits
... etc..

About your question in comments... the result modified by CF is already stored in bx, the old CF is lost, CF contains new "borrow" of bx - [0x162] - old_borrow. You can store CF somewhere if you wish, the CPU has instructions to detect/store/set value of CF, but with numbers once you calculate 15 - 8 is 7, the 7 is final, you don't need to remember you did borrow the "1" to subtract 8 from 5.

And the result address is again easy to calculate relatively to num1 address, so you can just store the result back into memory

mov   [si+0x20],ax  ; 0x0150 + 0x20 = 0x0170
mov   [si+0x22],bx
...

And I basically gave you whole solution :/ ... so let's add some more info at least, to make you work it out a bit.

You can do the same without using 4 16b registers to hold the single 64b value, like:

mov   ax,[si]
sub   ax,[si+0x10]
mov   [si+0x20],ax
mov   ax,[si+2]
sbb   ax,[si+0x10+2]
mov   [si+0x20+2],ax
...

After looking at that one, you may get a great idea, how about:

mov   ax,[si]
sub   ax,[si+0x10]
mov   [si+0x20],ax
add   si,2          ; adjust rather base pointer then changing all those offsets
mov   ax,[si]
sbb   ax,[si+0x10]
mov   [si+0x20],ax
...

Does it work? Not any more. The add si,2 will modify the CF, so the next sbb does not "continue" in the subtraction. The lesson to learn here is, that you should often check instruction reference guide for detailed ins. description, including which flags it does affect. With x86 - some instruction can surprise you, for example the code above modified to:

mov   ax,[si]
sub   ax,[si+0x10]
mov   [si+0x20],ax
inc   si        ; adjust base pointer
inc   si
mov   ax,[si]
sbb   ax,[si+0x10]
mov   [si+0x20],ax
...

WILL work, because inc does NOT affect CF, only other arithmetic flags are modified.

Another way to add two to valid addressing register without modifying any flag is lea si,[si+2].

BTW dosbox by default emulates 386+, so you can use even in 16b real mode:

mov   si,0x0150
mov   eax,[si]
mov   ebx,[si+4]    ; ebx:eax = 64b number1 (note +4 this time)
sub   eax,[si+0x10]
sbb   ebx,[si+0x14] ; ebx:eax = (number1 - number2)
mov   [si+0x20],eax
...

Unless you were constrained to use only 8086 (80286) instruction sets, the 32 bit registers were introduced with 80386 CPU.