0

I want to change flag UP to one, now it is UP = 0. I am using Visual Studio 64-bit assembly.
(Intel calls this the "direction flag", DF).

I tried this:

.data
source DB 'Hello, world!', 0
byte_to_find DB 'o'
.code
MyProc1 proc
    std                     ; Set Direction Flag to 1 (backward direction)
    lea rdi, source         ; Load address of source string

    mov al, byte_to_find    ; Load byte to find into AL
    mov rcx, 13             ; Length of the source string

SearchLoop:
    scasb                   ; Compare AL with byte at [RSI]
    jne NotFound            ; Jump if not equal (byte not found)
    jmp ByteFound           ; Jump if equal (byte found)

NotFound:
    mov rax, 0              ; Set RAX to 0 (byte not found)
    jmp Done

ByteFound:
    mov rax, rdi            ; Store the address of the found byte in RAX
    dec rax                 ; Adjust RAX to point to the byte before the found byte
    jmp Done

Done:
    ret
MyProc1 endp
end

but when I was debugging nothing changed and there still was UP = 1. I also tried using only std and mov after that and there was the same issue.

3
  • 5
    If you want to search backwards you should set your pointer to the end of the string. Also as per calling convention, you should reset the direction flag before returning. Commented Mar 28, 2024 at 13:54
  • 4
    scasb scans for one element only, it should be prefixed by repne. Commented Mar 28, 2024 at 15:42
  • 3
    The comment on scasb is wrong, it doesn't use rsi. It uses di/edi/rdi depending on mode. Commented Mar 28, 2024 at 16:39

1 Answer 1

1

Searching forward using scasb requires setting the direction flag DF to 0. The cld instruction does that. What it further requires is that you load the AL register with the character to be found, and the RDI register with the address of the first character of the string.

Searching backward using scasb requires setting the direction flag DF to 1. The std instruction does that. What it also requires is that you load the AL register with the character to be found, and the RDI register with the address of the last character of the string. Then the formula to use is:

RDI = AddressFirstChar + LengthString - 1

And if we plan to use the repne prefix, we also need to load the RCX register with the length of the string. You can write:

std                          ; Set DF to 1 (backward direction)
mov  al, byte_to_find        ; Load byte to find in AL
mov  ecx, 13                 ; Load length of the string in RCX
lea  rdi, [source + rcx - 1] ; Load address of last character in RDI
  jne NotFound            ; Jump if not equal (byte not found)
  jmp ByteFound           ; Jump if equal (byte found)
NotFound:

You don't need both these jumps. Because the zero flag ZF has but two states, either it's 0 or 1, you can jump to ByteFound if ZF=1, and just fall-through in NotFound if ZF=0.

SearchLoop:
 scasb                   ; Compare AL with byte at [RSI]
 jne NotFound            ; Jump if not equal (byte not found)
 jmp ByteFound           ; Jump if equal (byte found)

Although you named this 'SearchLoop', there's no actual loop here! After scasb has finished comparing the single byte in AL to the byte in memory at es:[rdi] you immediately jump to one of the two possible exits on the procedure. You need to repeat the scasb instruction either through prefixing this instruction with the repne prefix (REPeat while Not Equal):

  repne scasb
  je   ByteFound
NotFound:

or else by writing a normal loop:

SearchLoop:
  scasb
  je   ByteFound
  dec  rcx
  jnz  SearchLoop
NotFound:
NotFound:
  mov rax, 0   ; Set RAX to 0 (byte not found)
  jmp Done
ByteFound:
  mov rax, rdi ; Store the address of the found byte in RAX
  dec rax      ; Adjust RAX to point to the byte before the found byte
  jmp Done
Done:
  ret

Some cleaning is required here, but especially your comment 'Adjust RAX to point to the byte before the found byte' is noteworthy.
I don't know why you would want to return the address before the find, but if that is indeed what you need then know that RDI is already pointing to before the found byte, so you wouldn't need that dec rax! And should you need to point at the find itself, you would rather have to increment the value:

NotFound:
  xor  eax, eax       ; Set RAX to 0 (byte not found)
  ret
ByteFound:
  lea  rax, [rdi + 1] ; Set RAX to point to the found byte
  ret

Summary

Searching backward

  std                          ; Set DF to 1 (backward direction)
  mov  al, byte_to_find        ; Byte to find in AL
  mov  ecx, 13                 ; Length of the string in RCX
  lea  rdi, [source + rcx - 1] ; Address of last character in RDI
  repne scasb
  cld                          ; (*)
  je   ByteFound
NotFound:
  xor  eax, eax                ; Set RAX to 0 (byte not found)
  ret
ByteFound:
  lea  rax, [rdi + 1]          ; Set RAX to point to the find
  ret

(*) Whenever you need to use DF=1 locally, you should always return the direction flag to the clear state. Everybody expects to find DF=0, so we shouldn't have to bother using cld all the time! See The probability of selected EFLAGS bits.

Searching forward

  ; Save to assume DF=0 (forward direction)
  mov  al, byte_to_find  ; Byte to find in AL
  mov  ecx, 13           ; Length of the string in RCX
  lea  rdi, source       ; Address of first character in RDI
  repne scasb
  lea  rax, [rdi - 1]    ; Optimistically set RAX to point to the find
  je   Done              ; Byte found
NotFound:
  xor  eax, eax          ; Set RAX to 0 (byte not found)
Done:
  ret
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.