Search code examples
assemblyx86i386

How do I count the occurence of a char in a string in i386?


I'm a newbie to 80386 assembly language. Currently struggling on a school assignment that asks to write a function in assembly language that will be called in a c program.

extern int count(char *string, char c);

I think I have a sense of how this should be done, but still struggling with choosing the right instruction(instruction ends with 'b', 'w' or 'l') and perhaps the "right" register, I know there are some that are reserved to certain purposes.

.text
.global count

count:
    pushl   %ebp        # set up stack frame
    movl    %esp,%ebp   # save %esp in %ebp
    subl    $12, %esp   # automatic variables
    movl    $0, %eax    # initialize %eax to 0
    movl    8(%ebp), %esi   # pointer to s
    movb    12(%ebp), %bh   # pointer to c

check:
    movb    (%esi), %bl # move the first char in s to %bl
    cmp     0, %bl      # if the char is \0 
    je      done        # job is done

    cmp     %bh, %bl    # else compare the char to %bh
    je      found1      # if match increase the counter
    incb    %bl         # else move to next char
    jmp     check

found1:
    addl    $1, %eax    # found a match
    incb    %bl
    jmp     check       # go back to the beginning of check
    
done:
    movl    %ebp, %esp  # restore %esp from %ebp
    popl    %ebp        # restore %ebp
    ret

.end

My understanding of this program is that it should store the address of two values(string and char) into two registers. Then access the string char by char and compare it with the char stored in another register. If a match is found increase the return value in %eax, otherwise goes to the next char in the string until the end\0 is reached.

My program seems to be stuck in a loop as it does not crash either output a result. enter image description here

Any help will be appreciated.

enter image description here


Solution

  • I don't think there is a real reason to save %esp to %ebp, or to subtract from %esp. You do need to save %esi. I think the a, b, c, and d registers can be safely lost, but if not (it's been some time since I used assembly), you need to save %ebx as well.
    (Update: as @NateEldredge pointed out, %ebx has to be preserved - and I forgot to update the stack pointer. Yes, it has been too long).

    count:
        pushl   %esi             # save %esi as we use it
        pushl   %ebx
        # "In assembly language, all the labels and numeric constants used 
        #  as immediate operands (i.e. not in an address calculation like 
        #  3(%eax,%ebx,8)) are always prefixed by a dollar sign."
        #  https://flint.cs.yale.edu/cs421/papers/x86-asm/asm.html
        movl    12(%esp), %esi   # pointer to s
        movb    16(%esp), %bh    # char
        # I think it's more common "xor %eax, %eax"
        movl    $0, %eax         # initialize %eax to 0
    
    check:
        movb    (%esi), %bl      # move the current char in s to %bl
        cmp     $0, %bl          # if the char is \0 
        je      done             # job is done
    
        cmp     %bh, %bl         # else compare the char to %bh
        je      found1           # if match increase the counter
        # We must increase the pointer to the character, not %bl
        incl    %esi             # else move to next char
        jmp     check
    found1:
        addl    $1, %eax         # found a match
        # incb    %bl
        incl    %esi             # move to next char
        jmp     check            # go back to the beginning of check
    done:
        popl    %ebx
        popl    %esi             # restore %esi
        ret
    
    .end
    

    You could also invert the test to save some instructions:

        cmp     %bh, %bl         # else compare the char to %bh
        jne     notfound         # if not match, skip incrementing
        addl    $1, %eax         # found a match
    notfound:
        incl    %esi             # move to next char
        jmp     check