"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