Home > OS >  compiled assembly code produce non deterministic results
compiled assembly code produce non deterministic results

Time:12-24

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:

  1. Change the format string to %ld%*c so that scanf will convert and store a 64-bit long int. (Or perhaps %lu%*c would make more sense, since it doesn't look like you want to allow negative values.)

  2. 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 do movl -528(%rbp),

  • Related