3
\$\begingroup\$

As this will eventually become part of an operating system I'm developing, there is no specific adherence to any particular ABI, even though this is being developed and tested on Ubuntu 16.04. My OS will however adopt a SYSTEM V paradigm insomuch as passing values by register, but they will be passed in the one most suitable to the algorithm.

One of my methodologies is to utilize as many bits of the register as possible, especially when 16 or less are significant. I have found too, that in a lot of cases this can simplify calling process. It does however complicate things a little ie:

    mov     rdi, Pointer_to_String
    mov     ecx, 784 << 8 | '.'
    call    strlen

This would traverse the buffer pointed to by RDI for the first occurence of period for a maximum of 784 characters. This snippet had evolved from just scanning for NULL for a maximum of FFFFFFFFH bytes, to scanning for any of the values (0 - FF), to traversing buffer for specified number of bytes.

Question

Do you think I've achived the maximum utility with the least amount of code.

 ; Determine length, including terminating character EOS. Result may include
 ; VT100 escape sequences.

 ;        ENTER: RDI = Pointer to ASCII string.
 ;               RCX   Bits 31 - 08 = Max chars to scan (1 - 1.67e7)
 ;                          07 - 00 = Terminating character (0 - FF)

 ;        LEAVE: RAX = Pointer to next string (optional).

 ;        FLAGS:  ZF = Terminating character found, NZ otherwise (overrun).
 ;                DF = Unmodified incase it was already set.

strlen:  pushf                       ; Preserve DF (Direction flag)
         push    rcx                 ; Preserve registers used by proc so
         push    rdi                 ; its non-destructive except for RAX.

         mov      al, cl             ; Byte to scan for in AL.
         shr     ecx, 8              ; Shift max count into bits 23 - 00

         std                         ; Auto decrement.
         repnz   scasb               ; Scan for AL or until ECX = 0
         mov     rax, rdi            ; Return pointer to EOS + 1

         pop     rdi                 ; Original pointer from proglogue
         jz      .exit               ; ZF indicates EOS was found
         mov     rax, rdi            ; RAX = RDI, NULL string

    .exit:        
         pop     rcx
         popf                        ; Restore direction flag.
         ret
\$\endgroup\$
  • \$\begingroup\$ Do you ever plan on calling this from C? Or is your entire OS going to be in assembly? \$\endgroup\$ – JS1 Oct 21 '16 at 9:26
  • \$\begingroup\$ Will you also write compilers for your own calling convention or everything will be in assembly? BTW what's the ultimate goal of this kind of packaging? You may save few registers (maybe) but unless all parameters are literals then calling code is MUCH more convoluted (then possibly slower...) \$\endgroup\$ – Adriano Repetti Oct 21 '16 at 9:30
  • \$\begingroup\$ @AdrianoRepetti: I may port or implement some sort of NASM type assembler, but no intention of writing a compiler. Ultimate goal is to have what I'll call a more intuitive ABI where RBX RCX RSI & RDI are utilized as per X86 specification. Complexity dependent upon design intent is unavoidable. I've just resolved to make what I'll call lower level (for lack of a better term) as simple as possible. That position is mailable though and yours/others perspectives are essential in the development process. \$\endgroup\$ – Shift_Left Oct 21 '16 at 16:02
  • \$\begingroup\$ @JS1 I don't foresee anything other than assembly, but the system I have in mind could possibly become a collaborative work, so it may be something I should consider. At this point, if that should become the case, an additional tier between an established calling convention and this code is an alternative. \$\endgroup\$ – Shift_Left Oct 21 '16 at 16:09
6
\$\begingroup\$

Bug #1

You set the DF flag, so your string operation is going to run backwards. You should clear the DF flag to scan your string in the forwards direction.

Bug #2

If you reach the byte limit, your function will return the original string pointer in RAX. The comment says it should return NULL. Perhaps you meant xor rax, rax instead of mov rax, rdi?

Bug #3

According to the comments, your function should return with ZF set according to the result of the scan. However, since you do pushf and then popf, the ZF result is wiped out by the popf.

Your own ABI

I would suggest following the standard X64 ABI rather than inventing a new ABI per function. For one thing, you will be able to use high level languages to interface with your assembly routines. Another thing is that it would be hard for you to remember details such as "when I call strlen, ZF set means that the terminating character was found".

Don't call it strlen

Your function is closer to strchr or strnchr than strlen. Any reasonable person would expect a function called strlen to return a length rather than a pointer.

\$\endgroup\$
  • \$\begingroup\$ There is irrefutable logic in all your points, so I know what I'll be doing this afternoon. \$\endgroup\$ – Shift_Left Oct 21 '16 at 16:29
1
\$\begingroup\$

Based on comments I've modified code to correct error(s) and

  • Conform to System V ABI
  • Make no assumption about DF and always restore to original state
  • When using a name similar to that in glibc, adhere to that functions definition.

Return pointer to first occurence of Match character

   ENTER: RDI = Pointer to ASCII string.
          RSI   Bits 31 - 08 = Max chars to scan (1 - 1.67e7)
                     07 - 00 = Match character (0 - FF)

   LEAVE: RAX = Pointer to first match or NULL if not found.
                All others unchanged

   FLAGS:  ZF = not found, NZ otherwise.
           DF = Unmodified.
    strchr: push    rcx                 ; Preserve registers used by proc so
            mov     ecx, esi
            push    rdi                 ; Its non-destructive except for RAX.
            pushf                       ; Preserve direction flag

            mov      al, cl             ; Match char into AL.
            shr     ecx, 8              ; Shift max count into bits 23 - 00
            cld                         ; Guarantee auto increment
            repnz   scasb               ; Scan for AL or until ECX = 0
            jz      .found

            mov     eax, ecx            ; Return NULL in RAX
            jmp     .done

    .found: mov     rax, rdi
            dec     rax                 ; Bump back to match char

     .done: popf                        ; DF reset to original state
            or      rax, rax            ; Set ZF accordingly
            pop     rdi                 ; Original pointer from proglogue    
            pop     rcx
            ret
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.