Search code examples
assemblyx86masmirvine32

ASSEMBLY: issue with nested loop


I am working with nested loops in assembly. The desired output should be "ABCD" (new line) "EFGH" (new line) "HIJK" (new line) "LMNO" but currently I am not getting any output. The whole loop will execute but nothing will appear in the console.

INCLUDE Irvine32.inc

.data
ALPHA byte "A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z"
count byte 4 ; inner loop counter


.code
main proc

mov esi, OFFSET ALPHA
mov ecx, 4 ;outer loop counter
mov ebx, 0 ; inner counter
call part1
main endp

;-----------------------------------------------------------------------------
;part1 proc
;print 4x4 letter matric
;Receives EBX: arr address ECX: outer loop counter
;Returns EAX: full array
;-----------------------------------------------------------------------------
part1 PROC USES ebx ecx edx esi

L1:
    cmp ecx, 4 ; test outer loop count
    je next ; yes?
    call Crlf ; print new line
    inc ecx


L2:                                             ;inner loop
    cmp ebx, ecx ; 
    mov eax, [esi] ; mov next letter
    call WriteChar ; write it
    inc esi ; point to the next element
    inc ebx
    loop L2 ; loop

next: 
    call Crlf
    ret

part1 ENDP



end main

Solution

  • Your code does nothing what you describe.. let me step over single instruction with you, how the execution happens (this is not source code, but sequential write-down of instructions as they will be executed by CPU, with some minimal comments of important parts):

    mov esi, OFFSET ALPHA
    mov ecx, 4
    mov ebx, 0
    call part1
    cmp ecx, 4
    je next     ; jump is taken, because ecx == 4
    call Crlf
    ... internal implementation of Crlf by Irvine32 ending with RET
    ret         ; part1 "ret" returning into main
    ; there is no more code in main, so the CPU continues
    ; with next memory interpreting it as machine code
    ; with a bit of luck (no padding between ENDP/PROC, or NOPs)
    ; the "part1" code follows after main, so it will get executed
    cmp ecx, 4
    je next     ; jump is taken, because ecx == 4
    call Crlf
    ... internal implementation of Crlf by Irvine32 ending with RET
    ret         ; part1 "ret" returning out of your code completely
    ; in some environments that may work as clean exit from app
    ; in others that ret will go to whatever address is at stack and crash
    

    You should be capable to verify this in your debugger. I wonder what made you think the "The whole loop will execute" and "nothing will appear in the console", as clearly only minimal part of loop is being executed, and there should be two new lines in console appearing.

    How to fix your code ... first add some kind of exit to the end of the main, if you are building win32 executables, then I think the standard way to exit Irvin32 programs is to use ExitProcess (check working examples of Irvine32 package). I think something like invoke ExitProcess, 0 may work, invoke is some kind of predefined macro which will expand into mov + call instructions, but I don't have windows and/or Irvine32 to check myself.

    Then make your mind, how you want actually to track the loops. For example put outer counter into ecx and inner counter into ebx, and because you know the size if fixed 4x4, do the count-down in do { ..; --counter; } while (counter); style, which fits assembly way of coding naturally (but it doesn't check counter validity at first iteration, so this is good style only for case where counter is always valid value).

    Like for example:

    part1 PROC USES eax ebx ecx esi
        mov  ecx, 4
    outerLoop:
        mov  ebx, 4  ; reset inner counter (for every outer loop)
    innerLoop:
        mov  al, [esi] ; load next letter
        call WriteChar
        inc  esi     ; point to next element
        dec  ebx     ; --innerCounter
        jnz  innerLoop
        call Crlf    ; print new line after 4 chars
        dec  ecx     ; --outerCounter
        jnz  outerLoop
        ret
    part1 ENDP
    

    Now this should produce in console something like this:

    A,B,
    C,D,
    E,F,
    G,H,
    

    And that's because you did define the ALPHA as one long string, including the commas, etc... You probably did want something like

    ALPHA byte 'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P'
    

    or the equivalent

    ALPHA byte "ABCDEFGHIJKLMNOP"
    

    both of them producing 16 bytes with values 65, 66, 67, ... ('A' == 65).

    Also in your original code the:

    mov eax, [esi] ; mov next letter
    

    will load four bytes. The WriteChar does ignore the upper 24 bits of eax and will use only bottom 8 bits (the "al"), and you are incrementing esi only by one, so in the end this is harmless bug, but you should understand how it works. mov al,[esi] will fetch only 8 bits from memory, which is enough in this case, when working with characters.