NASM - The Netwide Assembler
NASM Forum => Programming with NASM => Topic started by: darius195 on March 24, 2011, 11:58:30 PM
-
I'm simply trying to divide in NASM and output the result. The code I have:
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?
-
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)
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
-
"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...
_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...
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"...
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...
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