0

Im a beginner at assembly and Im making a program that is going to take the value in a variable num and seeing if that number is in an array, it will print "That number is in the array" if not it will print something else. But it always prints, "The number is not in the array". Why?

section .text
    global _start
    
_start:
    mov eax, [numArray]
    mov ecx, 3 ;How many times Num will loop
    
Num:
    cmp [num], eax ;compare num to eax
    je inArray ;If equal go to inArray message
    dec ecx ;decrement ecx
    inc eax ;move to next element
    jz notIn ;If counter = 0 and a match still has not been found goto notIn
    jnz Num ;Else go back to Num
    
inArray: ;Print msg1
    mov eax, 4
    mov ebx, 1
    mov ecx, msg1
    mov edx, len1
    int 0x80
    
    jmp _exit
    
notIn: ;Print msg2
    mov eax, 4
    mov ebx, 1
    mov ecx, msg2
    mov edx, len2
    int 0x80
    
    jmp _exit
    
_exit: ;exit program
    mov eax, 1
    int 0x80
    
section .data
    num db 1
    numArray db 1, 2, 3
    
    msg1 db 'That is in the array!'
    len1 equ $-msg1
    
    msg2 db 'That is not in the array'
    len2 equ $-msg2

dareesome
  • 133
  • 1
  • 7
  • 2
    You have multiple problems. For starters, `mov eax, [numArray]` tries to load the first element but fails even at that due to size mismatch. You should load the address so drop the brackets. Then you probably want to load `num` once before the loop e.g. by doing `mov dl, [num]`. The comparison should then be `cmp [eax], dl`. The `inc eax` will overwrite the flags from the `dec ecx` which you need for the exit condition so move the `jz notIn` directly after the `dec ecx` and change the `jnz Num` to an unconditional `jmp`. – Jester Jun 01 '21 at 00:48
  • Oh wow, thanks. I have a question, why does eax fail to load the array? Is it because eax is a 32 bit register? – dareesome Jun 01 '21 at 01:12
  • 1
    Yes. But you want to use `eax` as a pointer since you use `inc eax` to iterate the array. – Jester Jun 01 '21 at 01:15
  • Ah okay, thanks. But why does eax being a 32 bit register make it fail to load in the array, sorry if this is a stupid question – dareesome Jun 01 '21 at 01:36
  • 1
    It doesn't "fail" to load the array element, it loads the first 4 elements (of a 3-element array). That's not exactly what you want because it loads "garbage" into the high bytes of EAX, but AL is still `1`. The top byte of EAX will be `'T'`, the first byte of `msg1`. Use a debugger to look at register values, especially in hex so it's easy to see the separate bytes separately. – Peter Cordes Jun 01 '21 at 01:57

1 Answers1

1

Some basic things:

mov eax, [data]

means, retrieve the contents indicated by the address data and store them in eax. This instruction retrieves a 32-bit quantity, because you have specified a 32-bit destination. If you want to retrieve just one Byte, you will need to specify an 8-bit destination:

mov al, [data]

Furthermore, if you want to load the address of a variable (a label), you simply write

mov rax, data

Confer point 5: Confusing value and address of a variable. For an address, a pointer, you almost always use a 64‑bit register (in 64‑bit mode), confer 10.5 Addresses and pointers in 64-bit mode.

lea rax, [data]

does effectively the same, but as you can see different notation, different instruction, thus different code.


You usually use string instructions for that kind of task. Unlike in high-level programming languages, “string” means any kind of continuous data, not just character strings.

global _start
bits 64
default rel

; ### constants ################################################
sys_exit:            equ            1

; ### intialized data ##########################################
section .data

data:
    db 42, -6, 13, -7, 99, 2
data_size:           equ     $ - data

; ### executable code section ##################################
section .text

_start:
    xor ebx, ebx                        ; ebx ≔ 0
    
    mov ecx, data_size                  ; exc ≔ count
    mov rdi, data                       ; rdi ≔ @data
    mov eax, 99                         ; the number to find
    
    repne scasb                         ; scan string Byte
    
    ; as it customary for program exit status: 0 means
    ; successful, and any non-zero value indicates an error
    setnz bl                            ; bl ≔ ¬ZF
exit:
    mov eax, sys_exit                   ; request number
    int 0x80

; vim: set ft=nasm:

cld (“forward” direction) has been omitted because the used ABI says it must be cleared upon process initialization (and function entry and return).

scasb retrieves a Byte from the location indicated by the value in rdi, compares it to the contents in al, sets the ZF according to the result, increments rdi (depends on DF) and decrements rcx. All of that in one instruction. The repne prefix simply means, while the ZF is clear and ecx has not hit 0 yet.

Kai Burghardt
  • 1,046
  • 1
  • 8
  • 23
  • You forgot to mention `default rel`. Without that, ti's pointless to do `lea rax, [data]`, and you might as well do `mov eax, data` for static symbols in a non-PIE Linux executable. ([How to load address of function or label into register](https://stackoverflow.com/q/57212012)). – Peter Cordes Jul 22 '21 at 18:55
  • Also, you have `int $80` at one point instead of `int 0x80`. NASM doesn't use `$` for hex constants. Also, don't use `int 0x80` in 64-bit code; it only uses 32-bit inputs so it's not useful for system calls that take pointers, except for static data in a non-PIE executable. [What happens if you use the 32-bit int 0x80 Linux ABI in 64-bit code?](https://stackoverflow.com/q/46087730) – Peter Cordes Jul 22 '21 at 18:57
  • It's not really "normal" to use `rep scasb` or `rep cmpsb` in modern x86-64 code, because the microcode for those instructions isn't optimized to compare multiple bytes at once. It's only useful as a code-size optimization, or maybe as an alternative to a naive byte-at-a-time loop if that's what you would have written instead. [Why is this code using strlen heavily 6.5x slower with GCC optimizations enabled?](https://stackoverflow.com/q/55563598) shows a case where GCC -O1 inlines a bad `rep scasb` strlen which is horrible for long strings. (And has startup overhead for short strings) – Peter Cordes Jul 22 '21 at 19:00
  • After another look at the question, it's using purely 32-bit stuff (registers and system calls), so in that case PC-relative addressing is a huge pain, and you should just use `mov eax, data`. Suggesting stuff involving RAX will result in code that doesn't assemble. – Peter Cordes Jul 22 '21 at 22:03
  • Well, I forgot it to mention in my explanations at the top, but otherwise my source code does contain [`default rel`](https://www.nasm.us/xdoc/2.15.05/html/nasmdoc7.html#section-7.2.1). Yes, I know `int 0x80` is disfavored on x86-64, but I wanted to keep things simple for @dareesome, because then I had to explain the different number. Again, I wanted to keep things simple. Agner Fog mentions in [16.9 String instructions](https://www.agner.org/optimize/optimizing_assembly.pdf#page=140) `repne scasb`, but the faster implementation is rather too difficult to grasp for a beginner, you know. – Kai Burghardt Jul 22 '21 at 22:14
  • Sure, you can mention `repne scasb`, but just don't say it's what you'd "usually" use. I'd say "it's a simple low-performance way". re: x86-64: the question appears to be a 32-bit question, so everything about x86-64 is off-topic. That's exactly why so many changes would be required to make proper 64-bit code. – Peter Cordes Jul 22 '21 at 22:24
  • It’s what you “usually” do. If you _just_ consult the _manuals_ for your favorite processor brand, you’ll hardly find any statement saying “By the way, we’ve implemented string instructions so badly, it’s barely worth using them.” Of course that shouldn’t stop us from further digging into it, but as soon as you make use of mathematical theorems, combining all (performance) data available, for carefully crafting an algorithm, we can’t call this the “usual” method anymore. In other words, if it’s not taught in a (college/university) Computer Science program, it’s unusual. – Kai Burghardt Jul 23 '21 at 09:44
  • Most undergrad CS programs don't teach x86 assembly in the first place. One of the main reason for writing asm in the first place is performance, so if you're not reading stuff like https://www.agner.org/optimize/ you're defeating the purpose of asm. Also, Intel's optimization manual *does* have a whole section about memcpy / memset and fast-strings vs. SIMD loops. (rep stos and rep movs *do* have optimized microcode). I assume the optimization manual mentions somewhere how slow the conditional-rep instructions are, but it's a fairly well-known fact. – Peter Cordes Jul 23 '21 at 17:55
  • x86 has legacy instructions like `enter` and `loop` as well, but they're not normally used either because they're slow, so I'm not at all buying your argument that every instruction in the manual should be assumed to be "how you should do X". – Peter Cordes Jul 23 '21 at 17:57
  • Also, ways to go faster at searches will potentially touch bytes of memory that a byte-at-a-time loop (or `repne scasb`) wouldn't have touched. That's unimportant (if you do it right) when memory protection has page granularity, but x86 in protected mode (instead of long mode) can have byte granularity segment limits. So fast-strings microcode for conditional rep instructions might be non-trivial to implement in ways that are correct for every corner case of x86. (And stuff like valgrind can maybe get false positives for reading outside array bounds if you use SIMD strings). – Peter Cordes Jul 23 '21 at 18:01