Search code examples
assemblyx86-16qemubootloaderreal-mode

How can I create a power function in assembly?


I'm trying to write an assembly function to convert ASCII decimal digits into hexadecimal values which can be easily used by the computer. Before showing my code, I have to point out that I'm working in 16-bit real mode (using AT&T syntax), therefore I have some limitations.

To achieve my goal, I wrote a "power" function which takes two arguments (BX as the base and CX as the exponent) and returns BX^CX into BX, or at least that's what I expected it to do. In reality, it works fine only once. For example:

    mov     $2, %cx
    mov     $10, %bx
    call    power

    mov     %bx, %dx
    call    printh       # Prints the value in dx

    dec     %cx
    mov     $10, %bx
    call    power

    mov     %bx, %dx
    call    printh       # Prints the value in dx

Here the first three lines work just fine, as they put 100 (0x64) into BX. Conversely, the other three lines put 10 (0xA) into the last 4-bit of BX, leaving the old (0x60) from 0x64 and producing a bad 0x6A as a result. I've done many checks, so I'm quite sure this is the nature of the problem. Despite this, I couldn't come up with a solution. Moreover, the code of my "power" function is really redundant, and I would like to make it simpler. Here it is:

power:
        push    %ax
        push    %cx
        mov     %bx, %ax
        or      %cx, %cx
        jz      power_zero
power_loop:
        cmp     $1, %cx
        je      power_loop_end
        mul     %bx
        dec     %cx
        jmp     power_loop
power_loop_end:
        mov     %ax, %bx
        pop     %cx
        pop     %ax
        ret

power_zero:
        mov     $1, %bx
        jmp     power_loop_end

Recently, after assembling and trying my code in qemu, I got this strange error:

Error in qemu

I don't know whether it's somehow linked with this problem in my power function or not, since I had this problem also before this error appeared. In the end, I firmly exclude the error to be in my print function, which I tried successfully on many occasions.

I would like to know if someone of you knows how to fix this problem, and most of all, if someone can tell me what's going on, and how can my computer remember a value (such as the 0x60 from the first power call) I erased through a mov or a xor operation.

In case you want to try the code by yourself, here I put a fully executable code which I tried: https://github.com/LoZack19/bare_metal/blob/main/power/power.S

EDIT: After reading your answers I think it is better if I include in my question also the code of the printh function, because, as you correctly stated, that the error was here, and that the power function had no problem:

        # printh(%dx)
        # dx: hex value to print
        # ==> converts the value stored in dx to ascii and prints it
printh:
        pusha
        mov     $0x05, %cx
loop_printh:
        cmp     $0x00, %dx
        je      end_loop_printh
        call    convert             # Converts the lower nibble (4-bit) of dx into an ascii character and stores it in HEX_OUT
        dec     %cx
        shr     $0x04, %dx
        jmp     loop_printh
end_loop_printh:
        mov     $HEX_OUT, %si
        call    printf
        popa
            ret
# [...]

HEX_OUT:        .asciz "0x0000"

Solution

  • The power function is just fine in this case (except for the unrelated bugs mentioned in comments). When called with bx = 10 and cx = 1 it correctly leaves 10 in bx. Single-stepping in a debugger is a good way to test this, and I found Bochs is a more convenient emulator than qemu for debugging 16-bit code.

    The bug is in your "firmly excluded" print function, specifically at printh. Consider what happens when dx contains a value less than or equal to 0xf. After the first iteration of loop_printh, the low nibble has been shifted out of dx, and so dx is now 0. Thus the test at loop_printh will exit the loop instead of iterating a second time. So the high 3 nibbles will never be written to HEX_OUT, which will therefore still contain whatever characters were left behind as the high nibble from the previous print, here 006.

    Your printh needs to iterate the loop four times no matter the input, to ensure that 0s are written for all the high nibbles. Your test for the loop thus probably wants to be based on the value of the counter cx instead.

    how can my computer remember a value (such as the 0x60 from the first power call) I erased through a mov or a xor operation.

    Because you never actually erased it :-)