Is there a way that I'm compiling the same assembly code, on the same server, and get different outputs on different runs for a program that should produce deterministic results? If so, what could be possible reasons?
My program receives some input from the users and do simple actions on strings (compare, swap case, etc..)
In order to know which function to run, along with the strings and their lengths, I'm getting an option for a switch case statement and jumping to the relevant function according to the number the user enters. but when running the code with the same inputs I'm getting most of the time the expected result, and on some runs the "default" option.
How could this happen? The result is non-deterministic. I've tried to debug but I couldn't find the RC. Any suggestion would be appreciated.
getting user input code:
.section .rodata
input_int: .string "%d%*c" # take the int number and clear the buffer from \n for the next inputs
input_string: .string "%[^\n]%*c" # get everything until reaching \n
.text
# this function receives from the user 2 strings and their lengths, and an option for the switch statement
.globl run_main
.type run_main, @function
run_main:
pushq %rbp # save the old frame pointer
movq %rsp, %rbp # create the new frame pointer
addq $-528, %rsp # allocating 528 bytes (256 for string1 length, same for 2, and 4 for the int and align it to 16)
movq $input_int, %rdi # get string1 length- passing the scanf format to scanf
leaq -256(%rbp), %rsi # passing the location to put string1 lengh in
movq $0, %rax
call scanf
movq $input_string, %rdi # get string1- passing the scanf format to scanf
leaq -255(%rbp), %rsi # passing the location to put string1 in
movq $0, %rax
call scanf
movq $input_int, %rdi # get string2 length- passing the scanf format to scanf
leaq -512(%rbp), %rsi # passing the location to put string2 length in
movq $0, %rax
call scanf
movq $input_string, %rdi # get string2 length- passing the scanf format to scanf
leaq -511(%rbp), %rsi # passing the location to put string2 in
movq $0, %rax
call scanf
movq $input_int, %rdi # get the option from the user- passing the format to scanf
leaq -528(%rbp), %rsi # passing
movq $0, %rax
call scanf
movq -528(%rbp), %rdi # passing to run_func option as the first parameter (get its value)
leaq -256(%rbp), %rsi # passing to run_func string1 as the second parameter
leaq -512(%rbp), %rdx # passing to run_func the string2 as the third parameter
call run_func
leave
ret
switch case code:
.section .rodata # read only data section
invalid: .string "invalid option!\n"
input_char: .string "%c%*c"
input_int: .string "%d%*c"
print_length: .string "first pstring length: %d, second pstring length: %d\n"
print_replace: .string "old char: %c, new char: %c, first string: %s, second string: %s\n"
print_pstring: .string "length: %d, string: %s\n"
print_compare: .string "compare result: %d\n"
.align 8
.Switch: # start switch case here
.quad .Case31 # Case 31
.quad .Case32 # Case 32
.quad .Case32 # Case 33
.quad .Default # default case (no 34 case)
.quad .Case35 # Case 35
.quad .Case36 # Case 36
.quad .Case37 # Case 37
.text # the beginnig of the code
# this function runs the wanted function accordding to the switch case selection.
.globl run_func
.type run_func, @function
run_func:
pushq %rbp # save the old frame pointer
movq %rsp, %rbp # create the new frame pointer
subq $32, %rsp # allocate 16 bytes in the stack
movq %rsi, -8(%rbp) # put string1 in the stack
movq %rdx, -16(%rbp) # put string2 in the stack
# set the jump table access
addq $-31,%rdi # compute xi = x-31 to start from 0 in the jump table
cmpq $6, %rdi # compare xi : 6 (31<=x<=37)
ja .Default # if >, goto default case (for negative numbers the cmp will be in unsigned (very big number for negatives becaue MSB on))
jmp *.Switch(,%rdi,8) # goto jump table at xi
# Case 36
.Case36:
movq -8(%rbp), %rdi
call swapCase
movq -8(%rbp), %rdi # passing string1 to pstrlen to get the length
call pstrlen
movq $print_pstring, %rdi # passing the print format to printf
movq %rax, %rsi # passing the result of pstrlen to printf
movq -8(%rbp), %rdx # passing the string1 to printf
incq %rdx # add 1 to rdx to get the beggining of the string
movq $0, %rax
call printf
movq -16(%rbp), %rdi
call swapCase
movq -16(%rbp), %rdi # passing string2 to pstrlen to get the length
call pstrlen
movq $print_pstring, %rdi # passing the print format to printf
movq %rax, %rsi # passing the result of pstrlen to printf
movq -16(%rbp), %rdx # passing the string2 to printf
incq %rdx # add 1 to rdx to get the beggining of the string
movq $0, %rax
call printf
jmp .Done # goto done
# Default case
.Default:
movq $invalid, %rdi # passing the invalid input string to printf
movq $0,%rax
call printf
jmp .Done # goto done
# return
.Done: # done
leave
ret
the pstrlen func:
# this function receives a pointer to a string and returns its length
.globl pstrlen
.type pstrlen, @function
pstrlen:
pushq %rbp # save the old frame pointer
movq %rsp, %rbp # create the new frame pointer
movzbq (%rdi), %rax # return the first byte of the string in which the length provided
leave
ret
swap case func:
# this function receives: pointer to string, and replace each lowercase letter with its uppercase compatible and vice versa.
.globl swapCase
.type swapCase, @function
swapCase:
pushq %rbp # save the old frame pointer
movq %rsp, %rbp # create the new frame pointer
.StartSwap:
incq %rdi # increase the received addres by 1 to get the beggining of the string (the first one is length)
cmpb $0, (%rdi) # check if the first char of the string is 0
je .DoneSwap
cmpb $0x41, (%rdi) # comare the first byte in ths string to A (ascii value in hexa is 41)
jl .StartSwap # if the char value is less than A value - its not a letter and we can continue without changing anything
cmpb $0x7A, (%rdi) # comare the first byte in ths string to z (ascii value in hexa is 7A)
jg .StartSwap # if the char value is greater z value - its not a letter and we can continue without changing anything
cmpb $0x61, (%rdi) # compare to a
jge .ToUpper # if the value is greater/equal to a, its a small letter
cmpb $0x5A, (%rdi) # compare to Z
jle .ToLower # if the value is less/equal to Z, its a big letter
jmp .StartSwap
.ToUpper:
subb $32, (%rdi) # the difference between the lower case values and the upper cale values is 32
jmp .StartSwap
.ToLower:
addb $32, (%rdi) # add 32 to get lower case value
jmp .StartSwap
.DoneSwap:
movq %rdi, %rax
leave
ret
Edit: running the code above with the following input:
5
hello
5
world
36
the expected output:
length: 5 string: HELLO
length: 5 string: WORLD
actual result: sometimes the expected result and sometimes the following output (which is default)
Invalid option!
CodePudding user response:
Near the end of run_main
you have:
movq $input_int, %rdi # get the option from the user- passing the format to scanf
leaq -528(%rbp), %rsi # passing
movq $0, %rax
call scanf
movq -528(%rbp), %rdi # passing to run_func option as the first parameter (get its value)
where the input_int
format string is "%d%*c"
. Now %d
asks scanf
to convert an int
, which is a 32-bit type, and so that's what it stores at address -528(%rbp)
. However, movq -528(%rbp), %rdi
is a 64-bit load. You also load the high 32 bits of %rdi
from memory, but those 4 bytes (starting at -524(%rbp)
were not written by scanf
and have not been initialized. If they happen to contain zero, your program will work (assuming the number scanned was nonnegative), but if they happen to contain something else, then %rdi
will end up with a very large value, which your program would treat as an invalid option.
There are a few different ways you could fix it:
Change the format string to
%ld%*c
so thatscanf
will convert and store a 64-bitlong int
. (Or perhaps%lu%*c
would make more sense, since it doesn't look like you want to allow negative values.)Sign-extend or zero-extend the value you read back from memory. So
movslq -528(%rbp), %rdi
if you want to treat the number as signed (consistent with a%d
format string). If you want to treat it as unsigned, you can simply domovl -528(%rbp),