Search code examples
assemblyx86nasmcpu-registers

Why do the AL, BL, and CL registers behave weird?


section .data
    format db '%d', 0x0a, 0

section .text
    global ft_strlen

ft_strlen:
    push ebp
    mov ebp, esp

    mov ecx, [ebp + 8]
    mov eax, 0
    
    loop:
    mov cl, [ecx + eax]
    inc eax
    cmp cl, 0
    jne loop

    mov esp, ebp 
    pop ebp
    ret

When I use the CL register the program works properly but when I use AL it gives me an infinite loop. And when I use BL it gives me a SEGV.
Actually it's so weird because three of them are 8-bit registers.


Solution

  • When I use cl register the program works properly but when I use al it gives me an infinite loop

    Both options are equally wrong but the effect they have is mostly coincidential!

    In mov cl, [ecx + eax], ECX is the base address of the string and EAX is the offset into the string. You should not be loading the byte from the string into CL since CL is the lowest 8 bits of the larger ECX register, and similarly you should not be loading the byte into AL since AL is the lowest 8 bits of the larger EAX register.

    and when I use bl it gives me a SEGV.

    Using BL only becomes an option, once you preserve its pre-existing value (on the stack). That's because EBX is "call-preserved" in the standard calling conventions. Compiler-generated code calling your function will assume EBX still has the same value after their call. Your options are to save/restore EBX somewhere, or not modify it or any parts of it.

    If you use a debugger to see where the segfault happens, for this case you should see it happening somewhere after your function returns. This is typically a sign you've violated the calling convention somehow, stepping on your caller's toes. The other two bugs, AL or CL, are likely producing crashes in your own function where you can see the bad address.

    actually it's so weird because three of them are 8bit registers.

    The x86 architecture has no separate 8-bit registers. What we perceive as 8-bit registers is always part of a larger register. See How do AX, AH, AL map onto EAX? for the details, which apply equally to AL, BL, CL, and DL.

    Solution: Use another call-clobbered register like EDX

    ft_strlen:
      mov   ecx, [esp+4]          ; Base
      xor   eax, eax              ; Offset
    .loop:
      movzx edx, byte [ecx + eax]
      inc   eax
      test  dl, dl
      jnz   .loop
      dec   eax                   ; Don't include zero-terminator
      ret
    

    Your code was including the zero-terminator in the returned count. Usually we don't do that.

    If you want to know how much space to allocate for a copy, that would save you the trouble of adding 1 to the return value. But give the function a different name to avoid confusion with the C standard library strlen function which doesn't include the terminator. And be sure to document in comments any ways your function is different from standard functions that do similar things, when you're doing it intentionally. If this was unintentional, just change the code to make it an implementation of C's strlen.