Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Assembly Instructions: AAA

I am looking at the pseudo-code: The Hidden Power of BCD Instructions. Here's a snippet of the contents of the website:

So, let's take a look at what AAA does. Here is the pseudo-code equivalent (from Intel):

   IF ((AL AND 0FH) > 9) OR (AF = 1)
    THEN
            AL = (AL + 6) AND 0FH;
            AH = (AH + 1);
            AF = 1;
            CF = 1;
    ELSE
            AF = 0;
            CF = 0;
    FI;enter code here

That's true for a typical Intel documentation compliant use like this one:

    mov     al,6
    add     al,9    ;al=15=0Fh
    aaa             ;al=21=15h  =>  it's in decimal!

The algorithm above doesn't seem to give the result as commented in the code, so I am assuming some steps were omitted here. The comment state the following: "al=21=15h => it's in decimal!".

My interpretion of the code is as follows:

  • After the add instruction, the value stored in the AL register will be 15 (0Fh).
  • Since it's greater than 10, six will be added to the value and ANDed with 0Fh which will result in the 1s value being stored in the AL register ("AL = (AL + 6) AND 0FH;"). The value of the AL register should now be 05h.
  • The algorithm adds one to AH registers which I assume is the carry-over, since we have ANDed and zeroed 4 bits of the AL registers ("AH = (AH + 1);").

However, there is no mention of the values stored in AH registers prior to running the line "AH = (AH + 1);". If it was initialised to zero, it'll only work for numbers under 20. Now let's assume it was initialised to zero, I'll expect the result to stored as AH=1 and AL=5, but the comment says ";al=21=15h => it's in decimal!". It seems something else was done before and after the execution of this block of code.

Can you explain to me what I am missing here?

Also, how are the CF/AF flags used here?

  • I understand that the AF flag is set for when there is a carry out from bit 3. Is this used to adjust the AL register to get it to store the 15h values as mentioned in the comment? As mentioned before, the algorithm did seem to suggest that the tens value are stored in the AH registers and the ones value are stored the AL registers.
  • And why is CF set here? It's for the carry over from the MSB, but I can't see any the carry-over in this particular addition/conversion.I was expecting this to be set in situations like an overflows.
like image 315
supmethods Avatar asked Jan 03 '23 01:01

supmethods


1 Answers

It would appear the author's understanding of what AAA does is misguided and he may have confused it with DAA (Decimal Adjust AL after Addition), but to make matters worse the Intel documentation over the years has been incorrect2 3 at times, thus adding to the confusion.

I'm not going to answer your question directly, but I want to give you some background that may allow you to figure out the answers to your own question given proper documentation. The documentation that you have quoted in your question only applies to processors earlier than the 80286, and even then it also contains an error regarding the masking of the bottom 4 bits of AL2

The original intent of AAA was to be used after the addition of two valid unpacked BCD numbers (ASCII 0 to 9 or 0x30 to 0x39) to convert the result to a valid 2-digit BCD number. Some people abused AAA outside of what it was designed for. Unfortunately, some of the code that abused AAA broke when the 286 was announced.

The AAA instruction is best defined this way1:

IF ((( AL and 0FH ) > 9 ) or (AF==1) 
    IF CPU<286 THEN 
        AL = AL+6  
    ELSE
        AX = AX+6
    ENDIF 
    AH = AH+1 
    CF = 1 
    AF = 1 
ELSE 
    CF = 0 
    AF = 0 
ENDIF 
AL = AL and 0Fh 

When adding two single digit BCD numbers you will want to clear out AH before the AAA. Code that would add two numbers would look like:

xor ah, ah        ; Clear AH
mov al, '6'       ; AL=0x36 (0x36 = ASCII '6')
add al, '9'       ; AL=0x36+0x39 (0x39 = ASCII '9') = 0x6F
aaa               ; AH=0x01, AL=0x05 thus AX=0x0105 . AH has upper digit, AL has the lower.

Because this code uses valid values (0x30 to 0x39) it will work the same way for all processors where the instruction is supported1. More generally though as long as the value in AL is 0x00 through 0xF9 (inclusive) the result of AAA will be the same if run on a processor <286 and a 286 or later processor.

If adding three or more BCD numbers you can use the value in AH to help maintain a properly overflowed result.


Similar Issue with AAS (ASCII adjust after subtraction)

The AAS instruction is best defined this way1:

IF ((( AL and 0FH ) > 9 ) or (AF==1) 
    IF CPU<286 THEN 
        AL = AL-6  
    ELSE
        AX = AX-6
    ENDIF 
    AH = AH-1 
    CF = 1 
    AF = 1 
ELSE 
    CF = 0 
    AF = 0 
ENDIF 
AL = AL and 0Fh 

Notes:

  • 1AAA and AAS are not available in 64-bit code on x86-64 processors.
  • 2 Some 80386 and 80486 documentation doesn't make it clear that the upper 4 bits of AL are always cleared in the result no matter what processor is being used. The documentation you have quoted from the article is also wrong in that regard. Related issues with AAS instruction.
  • 3 Some 80286+ documentation improperly state that AH is incremented by 6 by AAA when in fact it is AX that is incremented by 6. Apart from that, in both cases AH is also incremented by 1. More recent Intel manuals get this correct, but don't mention that processors before the 286 worked differently in this regard. Related issues with AAS instruction.
like image 71
Michael Petch Avatar answered Jan 04 '23 13:01

Michael Petch