lundi 1 juin 2020

Strange behaviour writing random number generator in assembly

For a project recently I've been working on translating an RNG from Numerical Recipes in C to assembly language. I have tried many different methods of compiling and disassembling the C code but all lead to the same output, 7 good random numbers followed by a string of 2,147,483,648.0:

Output:
0.214112
0.581177
0.848338
0.028559
0.996681
0.228658
0.067130
2147483648.0
2147483648.0
2147483648.0
2147483648.0
2147483648.0
2147483648.0
2147483648.0
2147483648.0
2147483648.0
2147483648.0
2147483648.0
2147483648.0
2147483648.0

This is interesting because its IM + 1 and 2^31 but I don't know where its coming from or why. If someone can see whats wrong with my code or even better fix it for me ;) That would be much appreciated.

This is the C code that I'm trying to translate:

// This is the C Random Number Generator
float ran0(long *idum) {

      long k;
      float ans;

      *idum ^= MASK;
      k= (*idum) / IQ;
      *idum= IA * (*idum - k * IQ) - IR * k;
      if (*idum < 0) *idum += IM;
      ans= AM * (*idum);
      *idum ^= MASK;

      return ans;
}

This is the assembly code (I know its a lot but its pretty modular and I think its fairly well commented):

[global _start]

section .data
      counter dd 0            ; This is the counter for our loop in _start
      seed dd 0               ; This holds the seed for ran0
      ranNum dd 0             ; This holds the return from ran0 (after its pulled off the FPU stack)

      ; Constants for ran0
      IA dd 16807
      IM dd 2147483647
      AM dq 0x3E00000000200000
      IQ dd 127773
      IR dd 2836
      MASK dd 123459876

      ; Lengths of the output strings for itoa and ftoa
      intLen equ 12                 ; This tells it to only print itoaOut
      floatLen equ 24               ; This includes both itoaOut and ftoaOut



section .bss
      ; Output strings for itoa and ftoa
      ; Both functions use itoaOut for their outputs, but ftoa stores the float part in ftoaOut
      itoaOut resb 12
      ftoaOut resb 12



section .text

_start:
      push ebp
      mov ebp, esp

      ; This gets the system time, which we use as a "random" seed
      sub esp, 0x04
        mov eax, 13
        mov ebx, esp
        int 0x80
        mov eax, [esp]
      add esp, 0x04


      ; Init ran0 (running it once seems to make it more random)
      sub esp, 0x04
        push eax
        call ran0
        pop eax
        fstp dword [esp]            ; The random number is returned on the top of the FPU stack
      add esp, 0x04


      mov [counter], dword 0
      mov [seed], eax
      .startLoop:


            ; Uses the value in seed to call ran0
            mov eax, dword [seed]
            push eax
            call ran0
            pop eax
            mov [seed], eax

            fstp dword [ranNum]     ; Puts the value returned from ran0 into ranNum


            ; Uses the random number to call ftoa which converts it to a string
            mov eax, dword [ranNum]
            push eax
            call ftoa
            pop eax


            ;;;;;;;;
            ; Print the result
            ; To print a float, point it to itoaOut and tell it to print a longer length
            mov eax, 4
            mov ebx, 1
            mov ecx, itoaOut        ; This points to the string we want to print
            mov edx, floatLen       ; This tells it the length of the string
            int 0x80


      add [counter], dword 1
      cmp [counter], dword 20
      jl .startLoop


      leave
      mov eax, 1
      mov ebx, 0
      int 0x80



ran0:       ; ran0(int seed): returns st0
      push ebp
      mov ebp, esp
      sub esp, 0x08

      ; *idum ^= MASK
      mov eax, dword [ebp+0x08]
      xor eax, dword [MASK]
      mov [ebp+0x08], eax

      ; k= (*idum) / IQ
      mov eax, dword [ebp+0x08]
      mov ebx, dword [IQ]
      mov edx, 0
      div ebx
      mov [esp+0x04], eax

      ; *idum= IA * (*idum - k * IQ) - IR * k
      mov eax, dword [esp+0x04]
      mov ebx, dword [IQ]
      imul ebx
      mov edx, [ebp+0x08]
      sub edx, eax
      mov eax, dword [IA]
      imul edx
      mov ecx, dword [IR]
      mov edx, [esp+0x04]
      imul edx, ecx
      sub eax, edx
      mov [ebp+0x08], eax

      ; if (*idum < 0) *idum += IM
      mov eax, [ebp+0x08]
      cmp eax, 0
      jge .endIfRAN
            mov eax, [ebp+0x08]
            add eax, dword [IM]
            mov [ebp+0x08], eax
      .endIfRAN:

      ; ans= AM * (*idum)
      fild dword [ebp+0x08]
      fld qword [AM]
      fmulp st1, st0
      fstp dword [esp]

      ; *idum ^= MASK
      mov eax, dword [ebp+0x08]
      xor eax, dword [MASK]
      mov [ebp+0x08], eax

      fld dword [esp]

      add esp, 0x08
      leave
      ret





itoa:       ; itoa(int number): returns itoaOut
      push ebp
      mov ebp, esp



      ; Set itoaOut to 0
      mov edi, itoaOut
      mov ecx, 0
      .ftoaLoopCLEAR:
            mov [edi], byte 0
            inc edi
      inc ecx
      cmp ecx, 12
      jl .ftoaLoopCLEAR



      mov edi, itoaOut
      mov [edi+0x0b], byte 0x0a
      add edi, 0x0a


      mov eax, [ebp+0x08]           ; Puts the argument (int number) into eax
      mov ebx, 10                   ; This is what we divide eax by

      ; This loop writes digits to itoaOut backwards
      .itoaLoop:
            mov edx, 0
            div ebx                 ; eax= eax/ebx  edx= remainder

            or edx, 0x30            ; or the remainder with 30 to make a valid ascii char
            mov [edi], dl           ; Put the char in the output string (itoaOut)
            dec edi

      cmp edi, itoaOut              ; If edi is not at the beginning of itoaOut, write another digit
      jge .itoaLoop                 ;   otherwise quit

      leave
      ret





ftoa:       ; ftoa(flt number): returns itoaOut
      push ebp
      mov ebp, esp
      sub esp, 12



      ; Set both itoaOut and ftoaOut to 0
      mov edi, itoaOut
      mov esi, ftoaOut
      mov ecx, 0
      .ftoaLoopCLEAR:
            mov [edi], byte 0
            mov [esi], byte 0

            inc edi
            inc esi
      inc ecx
      cmp ecx, 12
      jl .ftoaLoopCLEAR



      fld dword [ebp+0x08]          ; Load 'number' into st0
      fisttp dword [esp+0x08]       ; Set [esp+0x08] to the int part of 'number'

      fld dword [ebp+0x08]          ; Load 'number' into st0
      fisub dword [esp+0x08]        ; st0= st0 - [esp+0x08]
      fstp dword [esp+0x04]         ; Set [esp+0x04] to the float part of 'number'



      ; Digits are written to itoaOut backwards because it's easy to
      ;  repetitively isolate the least significant digit of a number
      mov edi, itoaOut              ; Set the destination to itoaOut
      add edi, 0x0b                 ; Set edi to point to the end of itoaOut

      mov eax, [esp+0x08]           ; Set eax to the integer part of the float
      mov ebx, 0x0a                 ; Set ebx to 10, this will be the divisor

      mov ecx, 0                    ; ecx functions as a character counter throughout the code

      .ftoaLoopINT:
            mov edx, 0x00           ; Set edx to 0, it will collect the remainder
            div ebx                 ; eax= eax/ebx, edx= remainder

            or edx, 0x30            ; Tag the remainder as an ascii number character
            mov [edi], dl           ; Put the ascii number character into the string (itoaOut)
            dec edi                 ; Set edi to point to the previous character

            inc ecx                 ; Add one to ecx to count ont more character
      cmp eax, 0x00                 ; If eax > 0, jump back to the beginning to write the next number
      jg .ftoaLoopINT



      ; Digits are written to ftoaOut forwards by
      ;  multiplying by 10 and repetitively truncating to get the next digit
      mov edi, ftoaOut              ; Set the destination to ftoaOut
      fld dword [esp+0x04]          ; Load the float part of 'number' into st0

      mov [edi], byte "."           ; Place the decimal at the first byte of ftoaOut
      inc edi                       ; Set edi to the second byte of ftoaOut

      mov [esp], dword 10           ; Set variable to 10

      .ftoaLoopFLT:
            fimul dword [esp]       ; st0= st0 * [esp]
            fld st0                 ; Copy st0 to st1
            fisttp dword [esp+0x08] ; Truncate and send st0 to [esp+0x08], now st0=st1
            fisub dword [esp+0x08]  ; st0= st0 - [esp+0x08]

            mov edx, [esp+0x08]     ; mov [esp+0x08] into edx
            or edx, 0x30            ; Translate the digit in edx into an ascii number character
            mov [edi], dl           ; Put the ascii number character into [edi] (ftoaOut)

            inc edi                 ; Point edi to the next character in ftoaOut
            inc ecx                 ; Add one to ecx to count ont more character
      cmp ecx, 7                    ; A 32 bit float can generally hold no more than 7 digits
      jl .ftoaLoopFLT

      mov [edi], byte 0x0a          ; append '\n' to the end


      ; In the end, itoaOut holds the int part and ftoaOut holds the float part

      add esp, 12
      leave
      ret

The code in ran0 is based off of the dissasembled gcc code. Thank you. :)




Aucun commentaire:

Enregistrer un commentaire