简体   繁体   中英

linux nasm assembly print all numbers from zero to 100

I am writing a program to print out all the numbers from zero to 100. The only reason I am doing this is to test out printing out multiple digit numbers.

The problem that I am having is that my program is only printing out the numbers 1 and 2. I have no idea why. My compiler compiles fine, without error, as well as no linker errors.

Here is my code:

SECTION .data
len EQU 32
NUL EQU 0
countlen EQU 8

SECTION .bss
counter resb countlen
strlen resb countlen

SECTION .text
GLOBAL _start
_start:
    mov BYTE[counter], 1              ; set counter to 1
    mov BYTE[strlen], 1               ; set string length counter to 1
    mov ecx, counter                  ; move the counter to ecx
    add BYTE[ecx], NUL                ; add null terminator to ecx
    mov esi, 9                        ; move 9 to esi

Length: 
    cmp [counter], esi                ; compare counter to esi
    jle Set                           ; if equal, goto set
    inc BYTE[strlen]                  ; increment the string size
    mov eax, 10                       ; move 10 to eax
    mov ebx, esi                      ; move esi to ebx
    mul ebx                           ; multiply ebx by esi
    add eax, 9                        ; add nine to the result
    mov esi, eax                      ; move the result to esi
    jmp Length                        ; jump to Length 

Set:
    mov esi, 9                        ; reset checker

Check:
    cmp BYTE[strlen], 1               ; is it one digit?
    je Single                         ; if yes, jump to single
    cmp BYTE[strlen], 3               ; is it 100?
    je Exit                           ; if yes, jump to Exit

Print:                                 ; this section deals with multi-digit numbers                                   
    cmp BYTE[ecx], NUL                ; check if end of string
    je Exit                           ; if equal goto exit 
    mov eax, 4
    mov ebx, 1
    mov edx, 1
    int 80h                           ; print number

    inc ecx                           ; point to next digit in number
    jmp Print                         ; jump to Print

Single:                                   ; this section deals with single digit numbers         add BYTE[counter], '0'            ; convert to ASCII
    mov eax, 4                       
    mov ebx, 1
    mov ecx, counter 
    mov edx, countlen 
    int 80h                           ; print the digit
    jmp Length                        ; go back

Exit:                                     ; Exit section
    mov eax, 1                        ; sys_exit
    mov ebx, 0                        ; return 0
    int 80h                           ; syscall

Why does it do this? Also, what do I need to change to get it to work as expected?

Thanks in advance,

RileyH

UPDATE:

Edited to include the 'Print' label

This is my function to print the digits on stdout. It's in AT&T sorry ;)

movl <your decimal here>, %eax
xor %ecx, %ecx  # the counter
movl $10, %ebx  

loop:
xor %edx, %edx
div %ebx        # isolate the last digit, remainder in edx
add $48, %dx    # '0' is 48 in ascii, result is the ascii equivalent
shl $8, %dx     # move the ascii byte to %dh
pushw %dx       # puch ascii code on the stack
inc %esp        # point to the ascii byte! (discard %dl)
inc %ecx        # count the digits
cmp $0, %eax
jnz loop

movl $4, %eax     # write()
movl $1, %ebx     # stdout
movl %ecx, %edx   # now edx holds the number of digits
movl %esp, %ecx   # load the address of string array
int $0x80         # the string array is on top of the stack

Cheers!

EDIT: it's not an "error" per se, but just misdirection for the casual reader to access a counter and strlen as one byte variables and in other places compare the contents to 32-bit variables...

add BYTE[ecx], NUL

this perhaps adds NUL terminator to ecx , but I suppose it should append the terminator. That could happen at place [ecx+1] .

Anyway the handling of the variables and pointers is very unconventional in your code...

First: the kernel functions that 'output' stuff, assume that ecx contains the address of a string. There isn't a string allocated anywhere. If the string would just fit into the eight bytes reserved to counter at counter resb 8 , and the counter would contain characters: '1' , '3' , '\\0' then the approach would work. And this reveals the second thing: printf deals with strings, which encode single digits 0-9 to values 48-57. Space eg in this ASCII system encodes to 32 (decimal) while \\NUL is ascii zero.

So, what is needed:

  • option 1
    Initialize your counter to a string

     counter db '0','0','0','0','0','0','0','1' length dq 1 

    Ascii zero is not needed to terminate the string, because as I understood, it's given to the print function

    Then one can give the real pointer to the string as

     lea ecx, counter // get the address of counter string add ecx, 7 // this is the last character 

    Also one can increase the counter as a string one digit at a time:

     loop: mov al,[ecx] // assuming ecx still points to last character inc al mov [ecx],al cmp al, '9' jle string ok mov al, '0' mov [ecx],al dec ecx jmp loop ok: // here the counter has been increased correctly 
  • option 2

    Increase the counter as a 32-bit integer Convert the integer to string one digit at a time with the following algorithm:

     digits = 0; string_ptr = &my_string[32]; // move barely outside the string do { last_digit = a % 10 + '0'; // calculate the last digit and convert to ASCII a = a / 10; *--string_ptr = last_digit; // write the last digit digits++; // count the number of digits } while (a); // because we predecrement string_ptr, that value also contains the exact // start of the first character in the printable string. And digits contains the length. 

To produce some good looking result, one has to still add line feeds . That can be handled separately or just appended to the original string -- and ensure they are never written over, so they can be used in all cases.

You need to convert the number to an ASCII digit(s) in order to print to terminal. Now I won't give you my dwtoa, that will take the fun out of learning, but you could do something like this:

sys_exit        equ     1
sys_write       equ     4
stdout          equ     1

SECTION .bss
lpBuffer    resb    4

SECTION .text
GLOBAL _start
_start:
    xor     esi, esi

.NextNum:
    call    PrintNum
    inc     esi
    cmp     esi, 100
    jna     .NextNum

.Exit:                                 
    mov     eax, sys_exit                
    xor     ebx, ebx                      
    int     80h                           

;~ #####################################################################
PrintNum:   
    push    lpBuffer
    push    esi 
    call    dwtoa

    mov     edi, lpBuffer
    call    GetStrlen
    inc     edx
    mov     ecx, lpBuffer

    mov     eax, sys_write
    mov     ebx, stdout
    int     80H     
    ret     

;~ #####################################################################    
GetStrlen:
    push    ebx
    xor     ecx, ecx
    not     ecx
    xor     eax, eax
    cld
    repne   scasb
    mov     byte [edi - 1], 10
    not     ecx
    pop     ebx
    lea     edx, [ecx - 1]
    ret

Notice, I use things like sys_exit, sys_write, stdout, instead of hard coded numbers. Makes code a bit more self documenting.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM