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