Idkwhoami
Idkwhoami

Reputation: 145

Printing 0-9 in Assembly

I am a complete newbie to this Language, and i am trying my best to learn it.

This is my first time dealing with low level language.

Here's my incomplete code:

;------------Block 1----------
.386
.model flat,stdcall
option casemap:none


;------------Block 2----------
include windows.inc
include user32.inc
includelib user32.lib
include kernel32.inc
includelib kernel32.lib


;------------block 3----------
.data
first DW 1 ; increment this value


;------------Block 4----------
.data?
retvalue dd ?


;------------Block 5----------
.code
start:
mov ecx,10
mov eax, '1'

l1:
    nop  ;Add code here
loop l1



xor eax,eax
invoke ExitProcess,eax        
end start

Its 64bit Architecture with Intel processor.

I referred to these articles, but none of them seems to fit in my architecture of code.

It's been irritating me for 4 hours.

Any help would be highly appreciated.

Upvotes: 0

Views: 2807

Answers (1)

David C. Rankin
David C. Rankin

Reputation: 84521

Assembly can be a challenge to make friends with. Primarily because you are given very basic building blocks to work with, memory segments, addresses within the segments to use, processor registers to load values into and get results from, the basic system calls, and a way to invoke those system calls to operate on the values currently in the processor registers. Then it's up to you to do the rest. I'll try to give you a rough overview of the thought process you can use in approaching basic assembly calls below.

There are many web-articles that provides a small piece of the puzzle, but very few that provide a reasonably complete overview of the language. That's why I mentioned The Art of Assembly Language Programming in the comments, and I'll mention it here again. If you take time to go through the sections, you will have a good handle on how to use assembly.

In assembly, there are usually a couple ways to approach any problem. Printing 0-9 is no different. In my comment I explained the difference between the numeric values of 0-9 and the printable ASCII characters '0'-'9'. In order to output values to the screen, you must write the ASCII values to stdout (or file-descriptor number 1, where stdin - 0, stdout - 1, stderr - 2).

To write the values to stdout you must make a proper sys_write system call with the proper values in the proper processor registers. (you can find the proper system calls in unistd_32.h (32-bit) or unistd_64.h (64-bit), located in your distro dependent include directory, generally /usr/include/asm or /usr/include/asm-x86) You only need to worry about 2 for you needs, sys_write (number 4) and sys_exit (number 1).

What goes in what processor register? You know the syscall number will go in the first register eax. Thankfully, you can generally figure the rest out from the C man page for the command in question. For writing the man 2 write page will help (you can use the man page for all system functions in a similar manner). Look at the write function declaration. Your register values are generally the parameters required by the function (in order). e.g.

ssize_t write(int fd, const void *buf, size_t count);
                  |               |           |
register:        ebx             ecx         edx

Now you know what goes in each register to write a character to stdout (the file-descriptor number), to execute the write, you generate a kernel interrupt to do it. (for x86, that is int 80h). Look at how you would write one character to stdout:

mov     eax, 4          ; linux (sys_write) in eax
mov     ebx, 1          ; fileno in ebx (stdout)
mov     ecx, achar      ; move achar address to ecx
mov     edx, 1          ; num chars to write in edx
int     0x80            ; kernel interrupt

(where achar is the memory address for the character to write)

As discussed in the comment, you can either start with the digits 0-9 and add '0' to the value to get the ASCII character value (or you can simply or the value with '0' to accomplish the same thing). You can also just start with the ASCII value '0', print it, increment its value by 1 (to get '1', etc...) and do it 10 times in total. (so a loop comes to mind)

I'll let you read further about loops in assembly, but the basic scheme is to load your loop count (10 in your case) into ecx and then jump (jmp) (or loop) to the beginning label for your loop (e.g. next: or looplbl:) and then decrement the value in ecx each time through until you hit 0.

When your looping is done, to finish up, you then load the exit value of your program into ebx and sys_exit into eax and call the kernel interrupt to exit. Now there are a lot of additional sub-issue basics to this process. This is simply an overview of one way to approach a solution. You will simply have to read and investigate the remainder because it is far more than can be crammed into this post.

To help, work through the following example. It simply subtracts the starting ASCII value '0' from the ending ASCII value '9' (then adds 1 for a total of 10-characters and uses that value for your loop count). It then loops 10-times starting by printing '0' and adding 1 to the previous value printed with each pass through the loop until all '0'-'9' have been printed. It then prints a newline so the numbers are not crammed on the same line with your prompt and exits:

section .data
    achar db '0'
    nwln db 0xa
section .text

        global _start               ; must be declared for using gcc
    _start:                         ; tell linker entry point
            xor     eax, eax        ; zero eax register
            mov     al, byte '9'    ; move byte '9` (57) to al
            sub     al, byte '0'    ; subtract byte '0' (48) from al
            inc     al              ; add 1 to al (for total chars)
            xor     ecx, ecx        ; zero exc
            mov     cx, ax          ; move result to cx (loop count)
    next:
            push    ecx             ; save value of ecx on stack
            mov     eax, 4          ; linux (sys_write) in eax
            mov     ebx, 1          ; fileno in ebx (stdout)
            mov     ecx, achar      ; move achar address to ecx
            mov     edx, 1          ; num chars to write in edx
            int     0x80            ; kernel interrupt

            pop     ecx             ; restore loop count
            mov     dx, [achar]     ; move value of achar to dx
            inc     byte [achar]    ; increment value of achar (next char)
            loop    next            ; jump to next:

            mov     eax, 4          ; linux (sys_write) in eax
            mov     ebx, 1          ; fileno in ebx (stdout)
            mov     ecx, nwln       ; move nwln (newline) to ecx
            mov     edx, 1          ; num chars to write in edx
            int     0x80            ; kernel interrupt

            xor     ebx, ebx        ; zero ebx (for exit code 0)
            mov     eax, 1          ; system call number (sys_exit)
            int 0x80                ; kernel interrupt

(note: you can output all printable characters in the ASCII character set by substituting '~' for '9' in the code above)

Compile/Link

This is a compile/link example with the nasm assembler. It will be similar if you are using fasm, or yasm. I write all my object files to an obj subdir and my binaries to a bin subdir just to reduce clutter.

nasm -f elf -o obj/prn_digits_32.o prn_digits_32.asm
ld -m elf_i386 -o bin/prn_digits_32 obj/prn_digits_32.o

Output

$ ./bin/prn_digits_32
0123456789

I hope this helps. Let me know if you have questions. There are probably 20 more ways to do this, some I'm sure much better. But it is really just a matter of taking it one-step-at-a-time and paying attention to what each register and byte in memory is doing.

Upvotes: 2

Related Questions