Author Topic: Division  (Read 24873 times)

Offline darius195

  • New Member
  • Posts: 1
Division
« on: March 24, 2011, 11:58:30 PM »
I'm simply trying to divide in NASM and output the result. The code I have:

Code: [Select]
segment .data

segment .bss

segment .text

global _start

_start:

        mov ax, 250
        mov dl, 100
        div dl              ;Divide ax (250) by dl (100), and store the result in al (right?)

        mov eax, 4      ;Output the result
        mov ebx, 1
        mov ecx, al
        mov edx, 1
        int 0x80


mov eax, 1
mov ebx, 0
int 0x80

Gives me this error on line 17 (mov ecx, al):
invalid combination of opcode and operands

Can anyone help please?
« Last Edit: March 25, 2011, 12:00:44 AM by darius195 »

Offline Bryant Keller

  • Forum Moderator
  • Full Member
  • *****
  • Posts: 360
  • Country: us
    • About Bryant Keller
Re: Division
« Reply #1 on: March 25, 2011, 12:36:24 AM »
I think you're getting that error because... well you have an invalid combination of opcodes and operands.... you can't directly assign the 8-bit register AL to the 32-bit register ECX, you need to move EAX into ECX (operands need to be the correct size).

Another "small" problem is that I believe you've failed to understand what the __NR_write syscall does, it won't convert an integer to a string for ya, so you'll need to do that yourself. If you are just wanting to practice playing around with the arithmetic opcodes, then I suggest using printf, but your next logical step should be to read up some of the conversion examples available on the net (and on this forum, I posted an examples kit at asmcommunity that had a conversion example in it, you should look into that)

Code: (printf version) [Select]
segment .data
strFormat: db "Result is %d with a remainder of %d", 10, 0

segment .bss

segment .text

global _start

_start:

        xor edx, edx
        mov eax, 250
        mov ebx, 100     ; We need EDX for the DIV operation, so use EBX to store 100
        div ebx             ; Divide EAX (250) by EBX (100), and store the result in EDX:EAX

        push edx
        push eax
        push dword strFormat
        extern printf
        call printf
        add esp, 12 ; << Thanks Frank! lol


mov eax, 1
mov ebx, 0
int 0x80

I'm not at my Linux machine atm, but this should work in theory. At least give you an idea of what's going on. Also, don't be afraid to use the standard C library. Yes a lot of "purists" want to do everything through syscalls, and there is some merit to that, but when nearly EVERY linux system has the standard C library on it anyway, it's just foolish not to use it. (yeah, I said that) :D

Regards,
Bryant Keller
« Last Edit: March 25, 2011, 05:49:22 AM by Bryant Keller »

About Bryant Keller
bkeller@about.me

Offline Frank Kotler

  • NASM Developer
  • Hero Member
  • *****
  • Posts: 2500
  • Country: us
Re: Division
« Reply #2 on: March 25, 2011, 04:22:38 AM »
"add esp, 12", but yeah, the C library is just sitting there in memory, waiting to be called. No matter how good our conversion routine is, it's redundant. If the goal is to get a number on the screen, "just call printf" is fine. If the goal is to understand things on a lower level - how printf does its magic, for example - then there may be an advantage to working out the conversion method...

Besides the lack of a "conversion" there are some "logic errors" that might be pointed out...

Code: [Select]

_start:

        mov ax, 250
        mov dl, 100
        div dl              ;Divide ax (250) by dl (100), and store the result in al (right?)

This is okay, but "just barely". One of the "tricky bits" about "div" is that the implied operands depend on the size of the operand you supply. "div" by an 8-bit register (or memory location) divides ax by the operand you provide. "div" by a 16-bit or 32-bit operand divides dx:ax or edx:eax by your operand. If the result won't fit in the destination register, an exception occurs. "div dl" is okay, but "div dx" or "div edx" are a guaranteed exception! Failing to take (e)dx into account is a common error. Note that Bryant's code explicitly zeros edx (xor reg, samereg zeros it) before the "div". (and divides by something else, not edx!)

But with an 8-bit register specified, you're okay. The quotient (2) is in al, and the remainder (50) is in ah...

Code: [Select]
        mov eax, 4      ;Output the result

... not anymore! We've just trashed the result, and it's gone for good. If you don't understand how the registers are composed - al and ah are parts of ax, which is a part of eax... that's even more fundamental than "div"...

Code: [Select]
        mov ebx, 1
        mov ecx, al

"div", doing division of a 16-bit operand by an 8-bit operand (or 32 by 16, or 64 by 32) is an exception to the general rule that operands should be the same size. "movzx ecx, al" would be legal, but doesn't do what we want. ecx wants to be the address of the buffer to write. Your correct result, 2, or the 4 you've replaced it with won't be an address that exists within our process, and will segfault! Maybe we should have had a buffer...

Code: [Select]
section .bss
        buffer resb 1

section .text

global _start

_start:

        mov ax, 250
        mov dl, 100
        div dl              ;Divide ax (250) by dl (100), and store the result in al (right?)

; result - quotient in al, remainder in ah

; "convert" from number to character, and store it
        add al, '0'
        mov [buffer], al

        mov eax, 4      ;Output the result
        mov ebx, 1
        mov ecx, buffer
        mov edx, 1
        int 0x80


mov eax, 1
mov ebx, 0
int 0x80

That won't be pretty - we could really use a linefeed after the '2' - but it does a "one digit conversion". To display more than one digit, we're going to need more (and a bigger buffer!)... Dividing by ten will isolate a digit as the remainder. Doing it repeatedly will give us the digits we want in our buffer... in the "wrong" order, and we'll still need to add '0' (or 48 decimal or 30h... but calling it '0' is perhaps more "self-documenting"...).

If you want to go with "just call printf", assemble Bryant's code with "nasm -f elf32 myfile.asm"... and link it with "ld -o myfile -I/lib/ld-linux.so.2 -lc myfile.o". The "-I/lib/ld-linux.so.2" specifies the "interpreter" or "dynamic loader" that finds "printf" for us. By default, ld looks for "...so.1", which does not exist on my system. This results in "no such file or directory" when I run "myfile" - even though I can see "myfile" right in front of my face. Highly confusing! I don't know where you'd read that in the Friendly Manual - it's a tip we have to "pass along".

Alternatively, you can use gcc to invoke ld (there's nothing to actually "compile"). By default, gcc links in some "startup code" which calls "main" and contains the "_start" entrypoint label - ld will complain about finding two! We can add "--nostartfiles" to the command line to prevent this. You might expect that you'd need that "startup code" if the library code is going to work right. Seems reasonable to me, but my experience has been that everything I've tried works without it. Of course, I haven't tried everything yet. :)

Or... we could replace "_start" with "main" (both the "global" declaration and the label). This would probably be the most "normal" way to do it. (short of just doing it in C :) )

Best,
Frank