如何在汇编中生成随机数种子?

5
我试图在汇编中创建完全随机的数字,但是每次启动程序它都会给我相同的数字,按相同的顺序。 如果数字是12、132、4113或其他什么,每次启动代码时都会重复它们。
我试图制作一个猜数游戏的程序。
    IDEAL
MODEL small
STACK 100h
DATASEG
;vars here
RNG_Seed dw ? 

CODESEG
; Generates a pseudo-random 15-bit number.
; Parameters: <none>
; Clobbers:   AX, DX
; Returns:    AX contains the random number
proc GenerateRandNum
   push bx
   push cx
   push si
   push di


   ; 32-bit multiplication in 16-bit mode (DX:AX * CX:BX == SI:DI)
   mov  ax, [RNG_Seed]
   xor  dx, dx
   mov  cx, 041C6h
   mov  bx, 04E6Dh
   xor  di, di
   push ax
   mul  bx
   mov  si, dx
   xchg di, ax
   mul  bx
   add  si, ax
   pop  ax
   mul  cx
   add  si, ax


   ; Do addition
   add  di, 3039h
   adc  si, 0


   ; Save seed
   mov [RNG_Seed], di


   ; Get result and mask bits
   mov  ax, si
   and  ah, 07Fh


   pop  di
   pop  si
   pop  cx
   pop  bx
   ret
endp GenerateRandNum

我该怎么做才能在每次运行时获得不同的随机数?


你的程序是否在没有操作系统的情况下运行? - James
@James,你在操作系统中是什么意思? - S. josh
操作系统 - Ped7g
对于 PRNG,您可能希望使用类似 xorshift+ 的东西,以避免需要 32 位乘法。另一方面,找到适用于 32 位版本的常数并不是易事。参考版本 是针对 64 位整数的。(您可以实现 64 位版本,以获得相当高质量的 PRNG,特别是如果您可以使用 386 SHRD / SHLD 使扩展精度移位更有效率。) - Peter Cordes
1
@PeterCordes 这篇论文也有32位的常量。例如,可以在这里找到。 - fuz
啊,只是xorshift,不是xorshift+。是的,这对于游戏来说仍然足够好,并且甚至可能比三个16位的“mul”指令更快,特别是在具有高效SHRD(而非AMD)的CPU上。它比mul/add线性同余生成器具有更高的质量,甚至可能足以用于蒙特卡罗模拟。(即使LCG对于游戏来说也足够好,只要有一个随机种子。) - Peter Cordes
1个回答

4
您需要使用一些“随机”的东西来初始化 RNG_Seed。但是,在确定性机器(如计算机)上,这实际上有点棘手。
特别是如果您希望该随机数足够强以用于加密,那么您只需要花费几个小时研究当前解决方案,一些工业解决方案甚至涉及使用带有白噪声发生器的特殊硬件芯片作为随机值生成器。
由于您只需要用于游戏,所以情况并不那么糟糕,但仍然有点棘手。
我猜您正在实模式下(因为您使用了16位寄存器),因此可以将BIOS自午夜以来的滴答声作为一种熵源进行读取:
xor ah,ah  ; ah = 0
int 1Ah    ; returns in cx:dx ticks since midnight (18.2Hz ticks)
; let's mix the cx:dx a bit together to get a bit more entropy out of it
rol cx,8
xor dx,cx
xor [RNG_Seed],dx ; "add" that entropy to the original seed

这将稍微改善一下,但仍然是一个不太好的解决方案(同时运行游戏可能会轻松产生相同的随机值),所以这里有另一个“廉价”的熵源建议:
例如,要求玩家输入名称(至少3个字符),并通过在等待键时始终执行“增加计数器”来计时每个击键,每次保留较低的4位计数器(3个字符+回车= 4 * 4位 = 总共16位)。然后再次将其“添加”(异或)到RNG_Seed中。
这两件事应该为游戏RNG产生足够的熵(但对于像加密之类的安全目的来说不够)。
编辑:如评论中所述,当混合不同的种子值时,请确保它们要么不相关,要么使用add而不是xor。我的原始想法是使用自午夜起的BIOS滴答声,xor-ed针对您的变量的某些随机内存(可能为零,BTW,在exe加载期间由操作系统清除),然后测量用户的击键时间,那些由4:4:4:4位组成的4个击键(没有异或,只是追加在一起,直到准备好完整的16b),并将最终值与主种子进行xor。由于按键与BIOS滴答声没有任何关系,因此这不应该干扰,并且在这种特定情况下xor应该很好地工作。
另外为什么是来自击键的4:4:4:4,而不是每次异或的单个16b值。我希望您通过调用int 16h,ah = 1来实现击键延迟计数器,因此如果您无限循环这个计数器并增加一些计数器,则很可能会非常快地运行超过16个值(我猜测在千分之一秒内)。然后使用这样的计数器低4位可能很难与用户击键的方式相关联。虽然在非常慢的计算机上,使用等待用户的完整16b实际上可能会产生类似的位模式(即用户每次敲击键都在10000-13000的计数器值之间 - >顶部2位始终为零,整个顶部8位每次都非常相似)。因此,这就是我所说的使用低4位,将它们组合在一起形成16b值。如果用户输入更长的名称,我甚至不会使用那些进一步的击键来进行种子调整,只使用前四个。而且我可能会进行一些调试,以查看它在现实中的工作方式,也许我对结果过于乐观了,有一些隐藏的同步式陷阱。但是我太懒惰了,无法实际编写并尝试(这就像~20行代码,但需要dosbox和一些DOS调试器)。

4
创造熵是我最喜欢的任务之一——非常有创造力!如果能写一个大列表,列出各种熵源及其大小(这可能很难证明),那会很有趣。例如,在“rdtsc”、“rdseed”和“rdrand”之外,下一次垂直回扫所需的时间(以任何单位表示)也是一个不错的熵源 :) - Margaret Bloom
1
@S.josh:RDRAND r16 应该可以在16位模式下工作,因为它不使用VEX编码(这就是为什么一些BMI/BMI2指令在16位模式下不可用)。但当然,只有在支持它的CPU上才能运行,并且即使如此,特定CPU中的RNG硬件可能会出现故障,导致它返回CF=0的非随机数据。 - Peter Cordes
2
@Ped7g:xor是一种危险的混合函数,适用于可能相关的事物。正如@Yakk所说:(https://dev59.com/ZVcO5IYBdhLWcg3w7lgm#arKiEYcBWogLw_1bWM-b):`+`通常更好:`x+x`只会烧掉`x`中的1位熵,而`x^x`会在你两次得到相同值的情况下烧掉所有熵。 - Peter Cordes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接