Obsah
1. Generátor náhodných čísel založený na instrukcích RDSEED a RDRAND
2. Detekce podpory instrukce RDRAND mikroprocesorem
3. První demonstrační příklad: zjištění, zda mikroprocesor podporuje instrukci RDRAND
4. Výsledky získané pro čip Intel Core i7–1270P
5. Přečtení náhodné hodnoty instrukcí RDRAND
6. Druhý demonstrační příklad – realizace volání instrukce RDRAND v assembleru
7. Otestování, že instrukce RDRAND pokaždé vrátí odlišnou hodnotu
8. Instrukce RDRAND ve vyšších programovacích jazycích
9. Vestavěné funkce v GCC umožňující volání instrukce RDRAND
10. Demonstrační příklad – přečtení a výpis sekvence deseti náhodných 32bitových hodnot
11. Způsob překladu demonstračního příkladu do assembleru
12. Jak kvalitní je sekvence náhodných čísel získaná opakovaným voláním RDRAND?
13. Program pro vygenerování binárního souboru s (pseudo)náhodným obsahem
14. Vygenerování binárního souboru s jedním milionem 32bitových pseudonáhodných hodnot
15. Projekt s implementací metrik pro otestování (pseudo)náhodných hodnot
16. Otestování hodnot vygenerovaných instrukcí RDRAND
17. Porovnání výsledků s výsledky deterministického generátoru pseudonáhodných hodnot
18. Příloha: pomocné soubory s makry a subrutinami použitými v příkladech napsaných v assembleru
19. Repositář s demonstračními příklady
1. Generátor náhodných čísel založený na instrukcích RDSEED a RDRAND
V informatice existuje hned několik oblastí, ve kterých je nutné používat generátory náhodných nebo alespoň pseudonáhodných hodnot. Jedná se například o kryptografii (šifrování, generování klíčů atd.), ale taktéž o online kasina (nebo podobné hry pro velké množství hráčů založené na náhodě – zde kvalitní generátor náhodných čísel pomáhá kasinu, ne hráčům). A právě zda narážíme na poněkud kuriózní situaci: i když se to nemusí při každodenní práci s počítači zdát, tato zařízení jsou deterministická a je tedy relativně složité vypočítat (resp. získat) skutečně náhodné hodnoty. Proto se velmi často používají deterministické generátory pseudonáhodných hodnot, což však nemusí být dostatečně silné řešení.
Samozřejmě je možné i v běžném počítači například přečíst šum z mikrofonního vstupu (apod.), ovšem není úplně snadné dokázat, že výsledkem budou náhodné hodnoty a ne například výsledek deterministické činnosti A/D převodníku (existuje hned několik možností zkreslení). Navíc se jedná o dosti pomalé operace. I z těchto důvodů se do moderních PC přidávají obvody určené pro získání skutečně náhodných hodnot. Buď se jedná o různé přídavné karty (s příslušnými ovladači), nebo je možné využít nové instrukce nazvané RDSEED a RDRAND, kde prefix RD značí „read“.
Instrukce RDSEED slouží k přečtení skutečně náhodné sekvence bitů z nějakého vhodného zdroje náhodnosti, který může být umístěn přímo v mikroprocesoru nebo v čipové sadě. Kvůli přímému čtení ze zdroje náhodnosti (typicky se využívá senzor tepelného šumu) může být tato instrukce relativně pomalá; na druhou stranu se však jedná o skutečně náhodné hodnoty. Vzhledem k tomu, že může být k dispozici pouze jediný senzor tepelného šumu, bude se sdílet mezi všemi jádry mikroprocesoru. To ještě více přispívá k tomu, že je tato instrukce relativně pomalá.
Druhá instrukce se jmenuje RDRAND. Ta přečte – a to obecně velmi rychle – hodnotu vrácenou generátorem pseudonáhodných hodnot. To se může zdát jako cesta zpět k deterministickým algoritmům, ale tento generátor je vždy po maximálně 511 čteních restartován, přičemž je do něj předáno nové semínko (seed) získané podobně jako u instrukce RDSEED ze specializovaného obvodu. Interně použitý generátor pseudonáhodných hodnot je poměrně sofistikovaný, takže (společně s tím, že je periodicky nastavován) splňuje například NIST SP 800–90A.
Dnes se zaměříme na instrukci RDRAND, kterou lze zavolat jak přímo z assembleru, tak i z vyšších programovacích jazyků.
2. Detekce podpory instrukce RDRAND mikroprocesorem
Společně s přidáváním dalších rozšiřujících instrukčních sad pro platformu 80×86 se objevila nutnost zjištění, zda daný mikroprocesor nějakou rozšiřující instrukční sadu podporuje či nikoli. Pro tento účel se používá instrukce CPUID, s níž jsme se již v tomto seriálu několikrát setkali. Tuto instrukci použijeme pro získání informací kategorie číslo 0 a 1. O kterou kategorii se má jednat zadáme v registru EAX před zavoláním CPUID:
mov eax, kategorie
cpuid ; naplneni EDX, ECX a EBX
Pro kategorii 0 se ve trojici registrů EBX, EDX a ECX (v tomto pořadí) vrátí dvanáctiznakový řetězec s identifikací mikroprocesoru. Současně se v registru EAX vrátí číslo nejvyšší dostupné kategorie.
Z pohledu podpory či nepodpory instrukce RDRAND je důležitá první kategorie. V registru ECX se vrátí bitová pole, z nichž je možné vyčíst, která instrukční sada je podporována a která naopak nikoli. Konkrétně podpora instrukce RDRAND je uložena v bitu číslo 30. Následuje příklad použití CPUID pro rozeskok na základě (ne)podpory instrukce RDRAND:
mov eax, 1 ; prvni kategorie
cpuid ; naplneni EDX a ECX
bt ecx, 30 ; test bitu cislo 30: podpora RDRAND
jnc rdrand_not_supported
print_string rdrand_supported, rdrand_supported_length
rdrand_not_supported:
...
...
...
3. První demonstrační příklad: zjištění, zda mikroprocesor podporuje instrukci RDRAND
V dnešním prvním demonstračním příkladu si ověříme, jestli mikroprocesor skutečně podporuje instrukci RDRAND. Použijeme k tomu postup popsaný v předchozí kapitole, tj. analýzu bitových polí vracených instrukcí CPUID. Nejdříve se podívejme na zdrojový kód příkladu a posléze si ukážeme výsledky pro konkrétní mikroprocesor:
[bits 32]
%include "linux_macros.asm"
;-----------------------------------------------------------------------------
section .data
hex_message:
times 8 db '?'
db ' '
hex_message_length equ $ - hex_message
rdrand_supported:
db 10, "RDRAND supported"
rdrand_supported_length equ $ - rdrand_supported
;-----------------------------------------------------------------------------
section .bss
id_string: resb 12
;-----------------------------------------------------------------------------
section .text
global _start ; tento symbol ma byt dostupny i linkeru
_start:
; ziskani indexu nejvyssi volatelne funkce CPUID
xor eax, eax ; nulta kategorie
cpuid
mov edx, eax ; hodnota, ktera se ma vytisknout
mov ebx, hex_message ; buffer, ktery se zaplni hexa cislicemi
call hex2string ; zavolani prislusne subrutiny
print_string hex_message, hex_message_length ; tisk hexadecimalni hodnoty
; test podpory RDRAND
mov eax, 1 ; prvni kategorie
cpuid
mov ebx, hex_message ; buffer, ktery se zaplni hexa cislicemi
call hex2string ; zavolani prislusne subrutiny
print_string hex_message, hex_message_length ; tisk hexadecimalni hodnoty
; vypis CPU ID
xor eax, eax ; nulta kategorie
cpuid
mov [id_string], ebx ; prvni ctyri znaky ID
mov [id_string+4], edx ; dalsi ctyri znaky ID
mov [id_string+8], ecx ; posledni ctyri znaky ID
print_string id_string, 12 ; tisk 12 znaku CPU ID
mov eax, 1 ; prvni kategorie
cpuid ; naplneni EDX a ECX
bt ecx, 30 ; test bitu cislo 30: podpora RDRAND
jnc rdrand_not_supported
print_string rdrand_supported, rdrand_supported_length
rdrand_not_supported:
exit ; ukonceni procesu
%include "hex2string.asm"
$ nasm -felf rdrand_support.asm -o rdrand_support.o $ ld -melf_i386 rdrand_support.o -o rdrand_support
4. Výsledky získané pro čip Intel Core i7–1270P
V mém konkrétním případě (Intel Core i7–1270P) se po překladu a spuštění tohoto demonstračního příkladu vypíšou následující dva řádky:
00000020 BFEBFBFF GenuineIntel RDRAND supported
Na prvním řádku je hexadecimálně zobrazen nejvyšší index volatelné funkce CPU ID (0×20), dále bitové pole s první kategorií podporovaných vlastností a poté již obsah registrů EBX, EDX a ECX s dvanáctiznakovou identifikací výrobce a (zhruba) modelu. Na druhém řádku se zobrazí informace o tom, že instrukce RDRAND je skutečně podporována.
5. Přečtení náhodné hodnoty instrukcí RDRAND
Samotná instrukce RDRAND – pochopitelně pokud je podporována – má tři různé operační kódy, které se odlišují podle toho, jestli se má vracet šestnáctibitová, 32bitová či dokonce 64bitová hodnota do zvoleného pracovního registru r16, r32 nebo r64:
| Operační kód | 64bit režim | 32bit režim | Stručný popis |
|---|---|---|---|
| NFx 0F C7 /6 RDRAND r16 | ano | ano | vrací se šestnáctibitová náhodná hodnota |
| NFx 0F C7 /6 RDRAND r32 | ano | ano | vrací se 32bitová náhodná hodnota |
| NFx REX.W + 0F C7 /6 RDRAND r64 | ano | ne | vrací se 64bitová náhodná hodnota |
6. Druhý demonstrační příklad – realizace volání instrukce RDRAND v assembleru
Základní způsob použití instrukce RDRAND je ukázán v dnešním druhém demonstračním příkladu. Po jeho překladu a spuštění se instrukcí RDRAND vygeneruje 32bitová náhodná hodnota, která se zapíše do pracovního registru EDX. Následně je obsah tohoto registru vypsán v hexadecimální podobě (maximálně se bude jednat o osm hexadecimálních cifer) na standardní výstup. Realizace pro operační systém Linux může vypadat následovně:
[bits 32]
%include "linux_macros.asm"
;-----------------------------------------------------------------------------
section .data
hex_message:
times 8 db '?'
db 0x0a
hex_message_length equ $ - hex_message
;-----------------------------------------------------------------------------
section .bss
;-----------------------------------------------------------------------------
section .text
global _start ; tento symbol ma byt dostupny i linkeru
_start:
rdrand edx ; ziskat pseudonahodnou hodnotu
mov ebx, hex_message ; buffer, ktery se zaplni hexa cislicemi
call hex2string ; zavolani prislusne subrutiny
print_string hex_message, hex_message_length ; tisk hexadecimalni hodnoty
exit ; ukonceni procesu
%include "hex2string.asm"
Příklad výstupu:
E570968D
$ nasm -felf rdrand_read.asm -o rdrand_read.o $ ld -melf_i386 rdrand_read.o -o rdrand_read
7. Otestování, že instrukce RDRAND pokaždé vrátí odlišnou hodnotu
V úvodní kapitole jsme si řekli, že instrukce RDRAND při každém zavolání vrátí nějakou pseudonáhodnou hodnotu. Ověřme si tedy, že skutečně nedostáváme konstantní hodnoty. Pro tento účel zavoláme RDRAND v počítané programové smyčce, kterou založíme na čítači uloženém v registru ECX a instrukci LOOP, která sníží hodnotu v čítači a dokud se nedosáhne nuly, provede se další iterace:
mov ecx, 10 ; pocitadlo smycky
rand:
... volání RDRAND ...
... výpis hexadecimální hodnoty ...
loop rand ; opakovat smycku
V praxi bude nutné hodnotu čítače uložit před výpisem hexadecimální hodnoty, protože tento kód modifikuje obsah registru ECX. Samozřejmě můžeme ECX uložit na zásobník a poté ho obnovit:
mov ecx, 10 ; pocitadlo smycky
rand:
push ecx ; uchovat hodnotu pocitadla
... volání RDRAND ...
... výpis hexadecimální hodnoty ...
pop ecx ; obnovit hodnotu pocitadla
loop rand ; opakovat smycku
Úplný zdrojový kód takto upraveného demonstračního příkladu vypadá následovně:
[bits 32]
%include "linux_macros.asm"
;-----------------------------------------------------------------------------
section .data
hex_message:
times 8 db '?'
db 0x0a
hex_message_length equ $ - hex_message
;-----------------------------------------------------------------------------
section .bss
;-----------------------------------------------------------------------------
section .text
global _start ; tento symbol ma byt dostupny i linkeru
_start:
mov ecx, 10 ; pocitadlo smycky
rand:
push ecx ; uchovat hodnotu pocitadla
rdrand edx ; ziskat pseudonahodnou hodnotu
mov ebx, hex_message ; buffer, ktery se zaplni hexa cislicemi
call hex2string ; zavolani prislusne subrutiny
print_string hex_message, hex_message_length ; tisk hexadecimalni hodnoty
pop ecx ; obnovit hodnotu pocitadla
loop rand ; opakovat smycku
exit ; ukonceni procesu
%include "hex2string.asm"
Příklad výstupu:
B2FF885E C07C2AA4 61B3E170 FA24F24B 57B51A51 9640F86D 180239A6 9B045C83 E039159D E01952DD
8. Instrukce RDRAND ve vyšších programovacích jazycích
Prozatím jsme si ukázali, jakým způsobem je možné instrukci RDRAND volat z programů psaných v assembleru. Jedná se o neprivilegovanou instrukci, takže její volání je vlastně velmi jednoduché. Ovšem v assembleru se v současnosti píše jen relativně malé množství kódu, takže bude zajímavé zjistit, zda a jak lze RDRAND zavolat z vyšších programovacích jazyků, zejména z céčka. Pokud totiž bude volání z jazyka C podporováno, je tím prakticky zajištěna i podpora v dalších vyšších programovacích jazycích, které většinou dokážou s jazykem C poměrně dobře kooperovat (Rust, Python, Go, do jisté míry i Java atd.). Dále uvedené ukázky jsou odladěny pro GNU C Compiler, ovšem zcela stejný postup lze aplikovat i v dalších překladačích, zejména v Clangu.
9. Vestavěné funkce v GCC umožňující volání instrukce RDRAND
Překladač GCC (a ovšem i některé další překladače céčka) podporují zápis programů, které interně volají instrukci RDRAND, a to bez toho, aby bylo nutné používat assembler. Konkrétně v GCC je dostupná trojice vestavěných funkcí, které po svém zavolání naplní obsah paměťového místa, jehož adresa je uvedena v jediné parametru funkci. „Pravou“ návratovou hodnotou je informace o tom, zda se volání podařilo či nikoli:
| # | Typ návratové hodnoty | Jméno vestavěné funkce | Parametr |
|---|---|---|---|
| 1 | unsigned int | __builtin_ia32_rdrand16_step | unsigned short * |
| 2 | unsigned int | __builtin_ia32_rdrand32_step | unsigned int * |
| 3 | unsigned int | __builtin_ia32_rdrand64_step | unsigned long long * |
Pozor si ovšem musíme dát na to, že výše uvedenou trojici funkcí je možné volat pouze v případě, že při překladu (nikoli až při linkinku) použijeme přepínač -mrdrnd nebo specifikujeme takovou architekturu cílového CPU, která tuto instrukci podporuje. V případě, že bude jeden z těchto přepínačů použit, povede volání libovolné z těchto funkcí k vygenerování strojového kódu s instrukcí RDRAND:
$ gcc --target-help |grep mrdrnd -mrdrnd Support RDRND built-in functions and code generation.
10. Demonstrační příklad – přečtení a výpis sekvence deseti náhodných 32bitových hodnot
Zatímco demonstrační příklady z první poloviny článku byly psané v assembleru mikroprocesorů s architekturou x86–64, budou další příklad psány v céčku (konkrétně v ANSI C, ale to je již detail). Následující příklad se po svém překladu a spuštění pokusí přečíst deset náhodných 32bitových hodnot a ty následně vypíše na terminál. Současně vypíše i příznak úspěšnosti či naopak neúspěšnosti přečtení těchto hodnot. Pokud je instrukce RDRAND podporována, mělo by být její volání úspěšné (nevyžaduje žádná privilegia):
#include <stdio.h>
#include <stdint.h>
int main(void) {
int success;
uint32_t int random_value;
int i;
for (i=0; i<10; i++) {
success = __builtin_ia32_rdrand32_step(&random_value);
printf("%d %x\n", success, random_value);
}
return 0;
}
V případě, že při překladu neuvedeme přepínač -mrdrnd, vypíše překladač chybu, protože nebude vestavěnou funkci __builtin_ia32_rdrand32_step podporovat (viz předchozí kapitolu). Toto chování si můžeme velmi snadno ověřit:
$ gcc rnrand_read.c
Výsledek pokusu o překlad našeho demonstračního příkladu dopadne neslavně:
rnrand_read.c: In function ‘main’:
rnrand_read.c:9:19: error: implicit declaration of function ‘__builtin_ia32_rdrand32_step’; did you mean ‘__builtin_ia32_rdtscp’? [-Wimplicit-function-declaration]
9 | success = __builtin_ia32_rdrand32_step(&random_value);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~
| __builtin_ia32_rdtscp
Korektní překlad bude realizován pouze po použití přepínače -mrdrnd. Vestavěné funkce budou dostupné v libovolné verze jazyka C, a to včetně ANSI C/C89:
$ gcc -mrdrnd -ansi -Wall -Werror rnrand_read.c
Výsledek by mohl vypadat následovně:
1 d49a35f7 1 563c3fd0 1 9c3fe577 1 34ee6930 1 68c498ee 1 1d38cc2f 1 244349d6 1 50f16a09 1 1324b913 1 af34d82c
11. Způsob překladu demonstračního příkladu do assembleru
Ověřme si ještě, zda překladač GCC skutečně převedl volání vestavěných funkcí __builtin_ia32_rdrandXX_step na instrukci RDRAND. Toto ověření je vhodné provést v případě, že se náhodné hodnoty budou využívat v kryptografických algoritmech, v online hrách (kasinech) atd. Provedeme překlad do assembleru a pro větší čitelnost si vynutíme vygenerování assemblerovského zdrojového kódu založeného na syntaxi používané firmou Intel:
$ gcc -mrdrnd -S -masm=intel -Og rdrand_read.c
Ve výsledném kódu je volání instrukce RDRAND podtrženo:
.file "rdrand_read.c"
.intel_syntax noprefix
.text
.section .rodata.str1.1,"aMS",@progbits,1
.LC0:
.string "%d %x\n"
.text
.globl main
.type main, @function
main:
.LFB11:
.cfi_startproc
push rbx
.cfi_def_cfa_offset 16
.cfi_offset 3, -16
sub rsp, 16
.cfi_def_cfa_offset 32
mov ebx, 0
jmp .L2
.L3:
rdrand esi
mov DWORD PTR [rsp+12], esi
mov eax, 1
cmovc esi, eax
mov edx, DWORD PTR [rsp+12]
mov edi, OFFSET FLAT:.LC0
mov eax, 0
call printf
add ebx, 1
.L2:
cmp ebx, 9
jle .L3
mov eax, 0
add rsp, 16
.cfi_def_cfa_offset 16
pop rbx
.cfi_def_cfa_offset 8
ret
.cfi_endproc
.LFE11:
.size main, .-main
.ident "GCC: (GNU) 14.2.1 20240912 (Red Hat 14.2.1-3)"
.section .note.GNU-stack,"",@progbits
12. Jak kvalitní je sekvence náhodných čísel získaná opakovaným voláním RDRAND?
Jak jsme si již naznačili v úvodní kapitole, negeneruje instrukce RDRAND skutečné náhodné hodnoty, ale „jen“ hodnoty pseudonáhodné, i když samotné semínko je přečteno z generátoru skutečně náhodných čísel (tato situace ovšem nastane až po vrácení 511 hodnot). Musíme si tedy ověřit, jak náhodná je ve skutečnosti sekvence čísel, která je opakovaným voláním instrukce RDRAND získána. Pro tento účel existuje poměrně velké množství metrik, jejichž podrobný popis přesahuje rámec dnešního článku (ovšem jedná se o velmi zajímavou problematiku). Proto využijeme existující nástroj, který přečte obsah binárního souboru s (pseudo)náhodnými daty a s využitím většího množství metrik ověří, do jaké míry jsou tato data náhodná. Pro zajímavost porovnáme hodnoty vytvářené instrukcí RDRAND s hodnotami získanými klasickým deterministickým generátorem pseudonáhodných hodnot (ovšem nevyužijeme funkci rand ze základní knihovny).
13. Program pro vygenerování binárního souboru s (pseudo)náhodným obsahem
Abychom si ověřili, do jaké míry jsou hodnoty vrácené instrukcí RDRAND náhodné, necháme si vygenerovat binární soubor s (pseudo)náhodným obsahem, který budeme posléze analyzovat. Pro jednoduchost při implementaci zůstaneme u jazyka C, i když naprosto stejný program je pochopitelně možné vytvořit i v assmebleru (i když nebude přenositelný):
#include <stdio.h>
#include <stdint.h>
int main(void) {
FILE *fout;
int success;
uint32_t random_value;
int i;
fout = fopen("random.bin", "wb");
if (fout == NULL) {
perror("fopen");
return 1;
}
for (i=0; i<10; i++) {
success = __builtin_ia32_rdrand32_step(&random_value);
if (success != 1) {
perror("rdrand32");
return 2;
}
fwrite(&random_value, sizeof(uint32_t), 1, fout);
if (ferror(fout)) {
perror("fwrite");
return 1;
}
}
fclose(fout);
return 0;
}
14. Vygenerování binárního souboru s jedním milionem 32bitových pseudonáhodných hodnot
Předchozí příklad vygeneroval pouze 40 náhodných bajtů. Upravme si ho proto do podoby, kdy se vygeneruje 1000000 třiceti dvoubitových hodnot:
...
...
...
for (i=0; i<1000000; i++) {
...
...
...
}
...
...
...
Po překladu a spuštění bychom měli získat soubor random.bin s délkou přesně 4000000 bajtů:
$ ls -l random.bin -rw-r--r--. 1 ptisnovs ptisnovs 4000000 Aug 8 18:47 random.bin
15. Projekt s implementací metrik pro otestování (pseudo)náhodných hodnot
Pro otestování, jak kvalitní jsou hodnoty získané instrukcí RDRAND, použijeme projekt s implementací testovací sady SP800–22 Rev 1a PRNG. Tento projekt je naprogramován v Pythonu, takže testy budou poměrně zdlouhavé.
Projekt s testy naklonujeme do nového adresáře:
$ git clone git@github.com:dj-on-github/sp800_22_tests.git
Zjistíme, zda je testovací sada spustitelná:
$ ./sp800_22_tests.py --help
Výsledek by měl vypadat následovně:
usage: sp800_22_tests.py [-h] [--be] [-t TESTNAME] [--list_tests] [filename]
Test data for distinguishability form random, using NIST SP800-22Rev1a algorithms.
positional arguments:
filename Filename of binary file to test
options:
-h, --help show this help message and exit
--be Treat data as big endian bits within bytes. Defaults to little endian
-t TESTNAME, --testname TESTNAME
Select the test to run. Defaults to running all tests. Use --list_tests to see the list
--list_tests Display the list of tests
Můžeme si vypsat i testy, které jsou implementovány:
$ ./sp800_22_tests.py --list_tests
Tests of Distinguishability from Random 1 : monobit_test 2 : frequency_within_block_test 3 : runs_test 4 : longest_run_ones_in_a_block_test 5 : binary_matrix_rank_test 6 : dft_test 7 : non_overlapping_template_matching_test 8 : overlapping_template_matching_test 9 : maurers_universal_test 10 : linear_complexity_test 11 : serial_test 12 : approximate_entropy_test 13 : cumulative_sums_test 14 : random_excursion_test 15 : random_excursion_variant_test
16. Otestování hodnot vygenerovaných instrukcí RDRAND
Nyní testy spustíme oproti souboru random.bin, který byl vytvořen v rámci čtrnácté kapitoly. Výsledné metriky se pochopitelně mohou nepatrně odlišovat, nicméně celkové výsledky by se měnit neměly:
Tests of Distinguishability from Random TEST: monobit_test Ones count = 16002474 Zeroes count = 15997526 PASS P=0.381742010349456 TEST: frequency_within_block_test n = 32000000 N = 99 M = 323232 PASS P=0.016363453060209376 TEST: runs_test prop 0.5000773125 tau 0.00035355339059327376 vobs 15997442.0 PASS P=0.36586054912558746 TEST: longest_run_ones_in_a_block_test n = 32000000 K = 6 M = 10000 N = 75 chi_sq = 7.062112789716308 PASS P=0.3151412465525687 TEST: binary_matrix_rank_test Number of blocks 31250 Data bits used: 32000000 Data bits discarded: 0 Full Rank Count = 9002 Full Rank -1 Count = 18103 Remainder Count = 4145 Chi-Square = 0.4486112659932162 PASS P=0.7990708746184348 TEST: dft_test N0 = 15200000.000000 N1 = 15200124.000000 PASS P=0.8405777193396355 TEST: non_overlapping_template_matching_test PASS P=0.9099065451579921 TEST: overlapping_template_matching_test B = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1] m = 10 M = 1062 N = 968 K = 5 model = [352, 179, 134, 97, 68, 135] v[j] = [580, 141, 101, 56, 47, 43] chisq = 6.780899397146134 PASS P=0.23745168802495786 TEST: maurers_universal_test sum = 29375620.88424485 fn = 10.16946589683095 PASS P=0.41030644109319797 TEST: linear_complexity_test M = 512 N = 62500 K = 6 chisq = 2.2519091978212007 P = 0.8951344431090033 PASS P=0.8951344431090033 TEST: serial_test psi_sq_m = 17.469030998647213 psi_sq_mm1 = 7.197106998413801 psi_sq_mm2 = 2.348089501261711 delta1 = 10.271924000233412 delta2 = 5.422906503081322 P1 = 0.24645678090722536 P2 = 0.24658963035192114 PASS P=0.24645678090722536 P=0.24658963035192114 TEST: approximate_entropy_test n = 32000000 m = 3 Pattern 1 of 8, count = 3998217 Pattern 2 of 8, count = 4000588 Pattern 3 of 8, count = 3999448 Pattern 4 of 8, count = 3999273 Pattern 5 of 8, count = 4000588 Pattern 6 of 8, count = 3998133 Pattern 7 of 8, count = 3999273 Pattern 8 of 8, count = 4004480 phi(3) = -4.382027 Pattern 1 of 16, count = 2000148 Pattern 2 of 16, count = 1998069 Pattern 3 of 16, count = 2000528 Pattern 4 of 16, count = 2000060 Pattern 5 of 16, count = 2000285 Pattern 6 of 16, count = 1999163 Pattern 7 of 16, count = 1998244 Pattern 8 of 16, count = 2001029 Pattern 9 of 16, count = 1998069 Pattern 10 of 16, count = 2002519 Pattern 11 of 16, count = 1998920 Pattern 12 of 16, count = 1999213 Pattern 13 of 16, count = 2000303 Pattern 14 of 16, count = 1998970 Pattern 15 of 16, count = 2001029 Pattern 16 of 16, count = 2003451 phi(3) = -5.075174 AppEn(3) = 0.693147 ChiSquare = 10.270517456945072 PASS P=0.24655018820637375 TEST: cumulative_sums_test PASS PASS P=0.30009060907640817 P=0.3654675300008945 TEST: random_excursion_test J=3795 x = -4 chisq = 2.277509 p = 0.809564 x = -3 chisq = 21.315877 p = 0.000706 Not Random x = -2 chisq = 8.381990 p = 0.136402 x = -1 chisq = 9.269424 p = 0.098788 x = 1 chisq = 7.581427 p = 0.180863 x = 2 chisq = 17.648646 p = 0.003421 Not Random x = 3 chisq = 3.632979 p = 0.603369 x = 4 chisq = 2.222378 p = 0.817597 FAIL: Data not random FAIL P=0.8095643977803422 P=0.0007059586033140604 P=0.13640211952704734 P=0.0987883878164268 P=0.18086283358054378 P=0.0034205664260353688 P=0.603368571138448 P=0.8175970567482853 TEST: random_excursion_variant_test J= 3795 x = -9 count=4344 p = 0.126422 x = -8 count=4095 p = 0.373945 x = -7 count=3898 p = 0.742985 x = -6 count=3822 p = 0.925552 x = -5 count=3733 p = 0.812487 x = -4 count=3750 p = 0.845214 x = -3 count=3918 p = 0.527784 x = -2 count=4057 p = 0.082514 x = -1 count=4002 p = 0.017501 x = 1 count=3696 p = 0.255808 x = 2 count=3836 p = 0.785847 x = 3 count=3893 p = 0.614922 x = 4 count=3778 p = 0.941207 x = 5 count=3683 p = 0.668269 x = 6 count=3563 p = 0.422023 x = 7 count=3464 p = 0.292000 x = 8 count=3503 p = 0.386820 x = 9 count=3547 p = 0.489937 PASS PASS P=0.12642201640045245 P=0.3739447986092539 P=0.7429854218086411 P=0.925551671800892 P=0.812486864855873 P=0.8452142837803289 P=0.5277841063848452 P=0.08251432838963278 P=0.017500678846047855 P=0.25580771506589667 P=0.7858465900792965 P=0.6149217634548362 P=0.9412070171775442 P=0.6682691399315276 P=0.42202260140624953 P=0.2919997884594155 P=0.38681988986649096 P=0.4899371303964931
Nejdůležitější je závěrečná tabulka:
SUMMARY ------- monobit_test 0.381742010349456 PASS frequency_within_block_test 0.016363453060209376 PASS runs_test 0.36586054912558746 PASS longest_run_ones_in_a_block_test 0.3151412465525687 PASS binary_matrix_rank_test 0.7990708746184348 PASS dft_test 0.8405777193396355 PASS non_overlapping_template_matching_test 0.9099065451579921 PASS overlapping_template_matching_test 0.23745168802495786 PASS maurers_universal_test 0.41030644109319797 PASS linear_complexity_test 0.8951344431090033 PASS serial_test 0.24645678090722536 PASS approximate_entropy_test 0.24655018820637375 PASS cumulative_sums_test 0.30009060907640817 PASS random_excursion_test 0.0007059586033140604 FAIL random_excursion_variant_test 0.017500678846047855 PASS
Z této tabulky je patrné, že „neprošel“ pouze jediný test random_excursion_test. Podrobnosti si řekneme příště.
17. Porovnání výsledků s výsledky deterministického generátoru pseudonáhodných hodnot
Bude zajímavé si porovnat výsledky otestování pseudonáhodných hodnot vygenerovaných instrukcí RDRAND se softwarově implementovaným generátorem pseudonáhodných hodnot (PRNG). Klasickou funkci rand (viz https://github.com/lattera/glibc/blob/master/stdlib/random_r.c#L341) použít přímo nelze, protože ořezává nejvyšší bit. Pokusme se tedy inspirovat některými existujícími PRNG: buď na posuvném registru se zpětnovazebními smyčkami (LFSR) nebo založeným na známém algoritmu XorShift (což je jedna z možných implementací LFSR, která je velmi rychlá a lze ji snadno implementovat i na osmibitových CPU). Konkrétní implementace byla inspirována tímto kódem:
#include <stdio.h>
#include <stdint.h>
uint32_t xorshift32(void) {
const uint32_t start_state = 0xACE1u;
static uint32_t state = start_state;
uint32_t x = state;
x ^= x << 13;
x ^= x >> 17;
x ^= x << 5;
return state = x;
}
int main(void) {
FILE *fout;
uint32_t random_value;
int i;
fout = fopen("random.bin", "wb");
if (fout == NULL) {
perror("fopen");
return 1;
}
for (i=0; i<1000000; i++) {
random_value = xorshift32();
fwrite(&random_value, sizeof(uint32_t), 1, fout);
if (ferror(fout)) {
perror("fwrite");
return 1;
}
}
fclose(fout);
return 0;
}
Výsledky testu pseudonáhodných hodnot nyní budou vypadat následovně:
Tests of Distinguishability from Random TEST: monobit_test Ones count = 16003561 Zeroes count = 15996439 PASS P=0.2080290229323611 TEST: frequency_within_block_test n = 32000000 N = 99 M = 323232 PASS P=0.8614428497587631 TEST: runs_test prop 0.50011128125 tau 0.00035355339059327376 vobs 16002865.0 PASS P=0.3109595205564162 TEST: longest_run_ones_in_a_block_test n = 32000000 K = 6 M = 10000 N = 75 chi_sq = 22.941016541697515 FAIL P=0.0008164762821414863 TEST: binary_matrix_rank_test Number of blocks 31250 Data bits used: 32000000 Data bits discarded: 0 Full Rank Count = 31250 Full Rank -1 Count = 0 Remainder Count = 0 Chi-Square = 76960.83183277596 FAIL P=0.0 TEST: dft_test N0 = 15200000.000000 N1 = 15201888.000000 FAIL P=0.002193202101438405 TEST: non_overlapping_template_matching_test PASS P=0.9993894628376528 TEST: overlapping_template_matching_test B = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1] m = 10 M = 1062 N = 968 K = 5 model = [352, 179, 134, 97, 68, 135] v[j] = [595, 150, 99, 48, 32, 44] chisq = 4.462951491364795 PASS P=0.48485565251215446 TEST: maurers_universal_test sum = 29377040.220677454 fn = 10.169957253030853 PASS P=0.9134294606289255 TEST: linear_complexity_test M = 512 N = 62500 K = 6 chisq = 8.824348198476159 P = 0.18370025599170844 PASS P=0.18370025599170844 TEST: serial_test psi_sq_m = 15.084697999060154 psi_sq_mm1 = 7.5235175006091595 psi_sq_mm2 = 4.195492248982191 delta1 = 7.5611804984509945 delta2 = 4.233155246824026 P1 = 0.47746392082023353 P2 = 0.3753703504323772 PASS P=0.47746392082023353 P=0.3753703504323772 TEST: approximate_entropy_test n = 32000000 m = 3 Pattern 1 of 8, count = 3996493 Pattern 2 of 8, count = 3998514 Pattern 3 of 8, count = 4001144 Pattern 4 of 8, count = 4000288 Pattern 5 of 8, count = 3998514 Pattern 6 of 8, count = 4002918 Pattern 7 of 8, count = 4000288 Pattern 8 of 8, count = 4001841 phi(3) = -4.382027 Pattern 1 of 16, count = 1997631 Pattern 2 of 16, count = 1998862 Pattern 3 of 16, count = 2000811 Pattern 4 of 16, count = 1997703 Pattern 5 of 16, count = 1999985 Pattern 6 of 16, count = 2001159 Pattern 7 of 16, count = 1999967 Pattern 8 of 16, count = 2000321 Pattern 9 of 16, count = 1998862 Pattern 10 of 16, count = 1999652 Pattern 11 of 16, count = 2000333 Pattern 12 of 16, count = 2002585 Pattern 13 of 16, count = 1998529 Pattern 14 of 16, count = 2001759 Pattern 15 of 16, count = 2000321 Pattern 16 of 16, count = 2001520 phi(3) = -5.075174 AppEn(3) = 0.693147 ChiSquare = 7.561090846763818 PASS P=0.4774731288894847 TEST: cumulative_sums_test PASS PASS P=0.33602422186921377 P=0.24415913425280134 TEST: random_excursion_test J=7693 x = -4 chisq = 8.002090 p = 0.156121 x = -3 chisq = 1.777649 p = 0.878976 x = -2 chisq = 1.259272 p = 0.939066 x = -1 chisq = 8.128416 p = 0.149299 x = 1 chisq = 4.735801 p = 0.448966 x = 2 chisq = 1.803213 p = 0.875649 x = 3 chisq = 2.294887 p = 0.807018 x = 4 chisq = 2.816422 p = 0.728263 PASS PASS P=0.15612050457353127 P=0.8789756760269307 P=0.9390662428576048 P=0.14929894014192574 P=0.44896643380466994 P=0.8756486991836885 P=0.8070175988774235 P=0.7282626872703772 TEST: random_excursion_variant_test J= 7693 x = -9 count=8004 p = 0.543123 x = -8 count=7917 p = 0.641021 x = -7 count=7920 p = 0.611759 x = -6 count=7834 p = 0.731797 x = -5 count=7753 p = 0.871906 x = -4 count=7851 p = 0.630201 x = -3 count=7931 p = 0.390847 x = -2 count=7910 p = 0.312479 x = -1 count=7839 p = 0.239181 x = 1 count=7527 p = 0.180807 x = 2 count=7491 p = 0.347107 x = 3 count=7433 p = 0.348552 x = 4 count=7135 p = 0.089077 x = 5 count=6962 p = 0.049482 x = 6 count=6988 p = 0.086587 x = 7 count=7161 p = 0.234229 x = 8 count=7269 p = 0.377460 x = 9 count=7229 p = 0.364271 PASS PASS P=0.5431229529294412 P=0.6410206735453066 P=0.6117587281384658 P=0.7317969690295173 P=0.8719060321940433 P=0.6302014247555671 P=0.39084685974524846 P=0.3124787233136639 P=0.23918087197125792 P=0.18080694993165813 P=0.34710650922851455 P=0.34855224987536226 P=0.08907689521634357 P=0.04948197607007566 P=0.0865873799112578 P=0.23422934975621235 P=0.3774596342564739 P=0.36427054513505974
Hodnoty jsou nepatrně horší (podle očekávání):
SUMMARY ------- monobit_test 0.2080290229323611 PASS frequency_within_block_test 0.8614428497587631 PASS runs_test 0.3109595205564162 PASS longest_run_ones_in_a_block_test 0.0008164762821414863 FAIL binary_matrix_rank_test 0.0 FAIL dft_test 0.002193202101438405 FAIL non_overlapping_template_matching_test 0.9993894628376528 PASS overlapping_template_matching_test 0.48485565251215446 PASS maurers_universal_test 0.9134294606289255 PASS linear_complexity_test 0.18370025599170844 PASS serial_test 0.3753703504323772 PASS approximate_entropy_test 0.4774731288894847 PASS cumulative_sums_test 0.24415913425280134 PASS random_excursion_test 0.14929894014192574 PASS random_excursion_variant_test 0.04948197607007566 PASS
18. Příloha: pomocné soubory s makry a subrutinami použitými v příkladech napsaných v assembleru
Soubor linux_macros.asm obsahuje základní makra používaná v programech, které mají být spouštěny v Linuxu, tj. které mají volat funkce jádra (kernelu):
%ifndef LINUX_MACROS_LIB
%define LINUX_MACROS_LIB
; Linux kernel system call table
sys_exit equ 1
sys_write equ 4
; makro pro tisk retezce na obrazovku
%macro print_string 2
mov eax, sys_write ; cislo syscallu pro funkci "write"
mov ebx, 1 ; standardni vystup
mov ecx, %1 ; adresa retezce, ktery se ma vytisknout
mov edx, %2 ; delka retezce
int 80h ; volani Linuxoveho kernelu
%endmacro
; makro pro tisk 32bitove hexadecimalni hodnoty
; na standardni vystup
%macro print_hex 2
push ebx ; uschovat EBX pro dalsi pouziti
mov edx, %1 ; zapamatovat si hodnotu pro tisk
mov ebx, hex_message ; buffer, ktery se zaplni hexa cislicemi
mov byte [ebx+8], %2 ; oddelovac, konec radku, atd.
call hex2string ; zavolani prislusne subrutiny
print_string hex_message, hex_message_length ; tisk hexadecimalni hodnoty
pop ebx ; obnovit EBX
%endmacro
; makro pro vypis obsahu MMX vektoru
%macro print_mmx_reg_as_hex 1
mov ebx, mmx_tmp ; adresa bufferu
movq [ebx], %1 ; ulozeni do pameti (8 bajtu)
mov eax, [ebx+4] ; nacteni casti MMX vektoru do celociselneho registru
print_hex eax, ' ' ; zobrazeni obsahu tohoto registru v hexadecimalnim tvaru
mov eax, [ebx] ; nacteni casti MMX vektoru do celociselneho registru
print_hex eax, 0x0a ; zobrazeni obsahu tohoto registru v hexadecimalnim tvaru
%endmacro
; makro pro vypis obsahu SSE vektoru
%macro print_sse_reg_as_hex 1
mov ebx, sse_tmp ; adresa bufferu
movups [ebx], %1 ; ulozeni do pameti (16 bajtu)
mov eax, [ebx+12] ; nacteni casti SSE vektoru do celociselneho registru
print_hex eax, ' ' ; zobrazeni obsahu tohoto registru v hexadecimalnim tvaru
mov eax, [ebx+8] ; nacteni casti SSE vektoru do celociselneho registru
print_hex eax, ' ' ; zobrazeni obsahu tohoto registru v hexadecimalnim tvaru
mov eax, [ebx+4] ; nacteni casti SSE vektoru do celociselneho registru
print_hex eax, ' ' ; zobrazeni obsahu tohoto registru v hexadecimalnim tvaru
mov eax, [ebx] ; nacteni casti SSE vektoru do celociselneho registru
print_hex eax, 0x0a ; zobrazeni obsahu tohoto registru v hexadecimalnim tvaru
%endmacro
; makro pro vypis obsahu AVX vektoru
%macro print_avx_reg_as_hex 1
mov ebx, avx_tmp ; adresa bufferu
vmovdqu [ebx], %1 ; ulozeni do pameti (32 bajtu)
mov eax, [ebx+28] ; nacteni casti AVX vektoru do celociselneho registru
print_hex eax, ' ' ; zobrazeni obsahu tohoto registru v hexadecimalnim tvaru
mov eax, [ebx+24] ; nacteni casti AVX vektoru do celociselneho registru
print_hex eax, ' ' ; zobrazeni obsahu tohoto registru v hexadecimalnim tvaru
mov eax, [ebx+20] ; nacteni casti AVX vektoru do celociselneho registru
print_hex eax, ' ' ; zobrazeni obsahu tohoto registru v hexadecimalnim tvaru
mov eax, [ebx+16] ; nacteni casti AVX vektoru do celociselneho registru
print_hex eax, ' ' ; zobrazeni obsahu tohoto registru v hexadecimalnim tvaru
mov eax, [ebx+12] ; nacteni casti AVX vektoru do celociselneho registru
print_hex eax, ' ' ; zobrazeni obsahu tohoto registru v hexadecimalnim tvaru
mov eax, [ebx+8] ; nacteni casti AVX vektoru do celociselneho registru
print_hex eax, ' ' ; zobrazeni obsahu tohoto registru v hexadecimalnim tvaru
mov eax, [ebx+4] ; nacteni casti AVX vektoru do celociselneho registru
print_hex eax, ' ' ; zobrazeni obsahu tohoto registru v hexadecimalnim tvaru
mov eax, [ebx] ; nacteni casti AVX vektoru do celociselneho registru
print_hex eax, 0x0a ; zobrazeni obsahu tohoto registru v hexadecimalnim tvaru
%endmacro
; makro pro ukonceni procesu
%macro exit 0
mov eax,sys_exit ; cislo sycallu pro funkci "exit"
mov ebx,0 ; exit code = 0
int 80h ; volani Linuxoveho kernelu
%endmacro
%endif
Soubor hex2string.asm obsahuje subrutinu nazvanou taktéž hex2string, která převede vstupní 32bitovou hodnotu uloženou v registru EDX do řetězce:
%ifndef HEX_2_STRING_LIB
%define HEX_2_STRING_LIB
; subrutina urcena pro prevod 32bitove hexadecimalni hodnoty na retezec
; Vstup: EDX - hodnota, ktera se ma prevest na retezec
; EBX - adresa jiz drive alokovaneho retezce (resp. osmice bajtu)
hex2string:
mov ecx, 8 ; pocet opakovani smycky
.print_one_digit: rol edx, 4 ; rotace doleva znamena, ze se do spodnich 4 bitu nasune dalsi cifra
mov al, dl ; nechceme porusit obsah vstupni hodnoty v EDX, proto pouzijeme AL
and al, 0x0f ; maskovani, potrebujeme pracovat jen s jednou cifrou
cmp al, 10 ; je cifra vetsi nebo rovna 10?
jl .store_digit ; neni, pouze prevest 0..9 na ASCII hodnotu '0'..'9'
.alpha_digit: add al, 'A'-10-'0' ; prevod hodnoty 10..15 na znaky 'A'..'F'
.store_digit: add al, '0'
mov [ebx], al ; ulozeni cifry do retezce
inc ebx ; dalsi cifra
loop .print_one_digit ; a opakovani smycky, dokud se nedosahlo nuly
ret ; navrat ze subrutiny
%endif
19. Repositář s demonstračními příklady
Demonstrační příklady napsané v jazyku C i v assembleru mikroprocesorů s architekturou x86–64, byly uloženy do Git repositáře, který je dostupný na adrese https://github.com/tisnik/8bit-fame. Jednotlivé demonstrační příklady si můžete v případě potřeby stáhnout i jednotlivě bez nutnosti klonovat celý (dnes již poměrně rozsáhlý) repositář:
| # | Příklad | Stručný popis | Adresa |
|---|---|---|---|
| 1 | rdrand_support.asm | test, jestli je instrukce RDRAND mikroprocesorem podporována | https://github.com/tisnik/8bit-fame/blob/master/pc-linux/rdrand_support.asm |
| 2 | rdrand_read.asm | přečtení jedné 32bitové hodnoty instrukcí RDRAND | https://github.com/tisnik/8bit-fame/blob/master/pc-linux/rdrand_read.asm |
| 3 | rdrand_read_loop.asm | přečtení sekvence 32bitových hodnot instrukcí RDRAND | https://github.com/tisnik/8bit-fame/blob/master/pc-linux/rdrand_read_loop.asm |
| 4 | rdrand_read.c | přečtení náhodné 32bitové hodnoty, realizace s využitím vestavěné funkce GCC | https://github.com/tisnik/8bit-fame/blob/master/gcc-builtins/rdrand_read.c |
| 5 | rdrand_read.asm | výsledek překladu předchozího zdrojového kódu do assembleru | https://github.com/tisnik/8bit-fame/blob/master/gcc-builtins/rdrand_read.asm |
| 6 | rand_gen.c | vygenerování binárního souboru s pseudonáhodnými 32bitovými hodnotami | https://github.com/tisnik/8bit-fame/blob/master/gcc-builtins/rand_gen.c |
| 7 | rdrand_gen.c | vygenerování binárního souboru s hodnotami vrácenými instrukcí RDRAND | https://github.com/tisnik/8bit-fame/blob/master/gcc-builtins/rdrand_gen.c |
20. Odkazy na Internetu
- RDRAND (Wikipedia)
https://en.wikipedia.org/wiki/RDRAND - RDRAND instruction
https://www.felixcloutier.com/x86/rdrand - Random Number Generator
https://wiki.osdev.org/Random_Number_Generator - GCC documentation: Extensions to the C Language Family
https://gcc.gnu.org/onlinedocs/gcc/C-Extensions.html#C-Extensions - GCC documentation: Using Vector Instructions through Built-in Functions
https://gcc.gnu.org/onlinedocs/gcc/Vector-Extensions.html - SSE (Streaming SIMD Extentions)
http://www.songho.ca/misc/sse/sse.html - Timothy A. Chagnon: SSE and SSE2
http://www.cs.drexel.edu/~tc365/mpi-wht/sse.pdf - CPU design (Wikipedia)
http://en.wikipedia.org/wiki/CPU_design - GCC Compiler Intrinsics
https://iq.opengenus.org/gcc-compiler-intrinsics/ - Other Built-in Functions Provided by GCC
https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html - GCC: 6.60 Built-in Functions Specific to Particular Target Machines
https://gcc.gnu.org/onlinedocs/gcc/Target-Builtins.html#Target-Builtins - Stránka projektu Compiler Explorer
https://godbolt.org/ - The LLVM Compiler Infrastructure
https://llvm.org/ - GCC, the GNU Compiler Collection
https://gcc.gnu.org/ - Clang
https://clang.llvm.org/ - Clang: Assembling a Complete Toolchain
https://clang.llvm.org/docs/Toolchain.html - Integer overflow
https://en.wikipedia.org/wiki/Integer_overflow - SETcc — Set Byte on Condition
https://www.felixcloutier.com/x86/setcc - The ARMv8 instruction sets
http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.den0024a/ch05s01.html - A64 Instruction Set
https://developer.arm.com/products/architecture/instruction-sets/a64-instruction-set - Switching between the instruction sets
http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.den0024a/ch05s01.html - The A64 instruction set
http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.den0024a/ch05s01.html - Introduction to ARMv8 64-bit Architecture
https://quequero.org/2014/04/introduction-to-arm-architecture/ - Undefined behavior (Wikipedia)
https://en.wikipedia.org/wiki/Undefined_behavior - Is signed integer overflow still undefined behavior in C++?
https://stackoverflow.com/questions/16188263/is-signed-integer-overflow-still-undefined-behavior-in-c - Allowing signed integer overflows in C/C++
https://stackoverflow.com/questions/4240748/allowing-signed-integer-overflows-in-c-c - SXTB, SXTH, SXTW
https://www.scs.stanford.edu/~zyedidia/arm64/sxtb_z_p_z.html - Is there any legitimate use for Intel's RDRAND?
https://stackoverflow.com/questions/26771329/is-there-any-legitimate-use-for-intels-rdrand - Intel® Digital Random Number Generator (DRNG) Software Implementation Guide
https://www.intel.com/content/www/us/en/developer/articles/guide/intel-digital-random-number-generator-drng-software-implementation-guide.html - Hardware random number generator
https://en.wikipedia.org/wiki/Hardware_random_number_generator - Random number generator attack
https://en.wikipedia.org/wiki/Random_number_generator_attack - random_r.c (Glibc)
https://github.com/lattera/glibc/blob/master/stdlib/random_r.c#L341 - Xorshift
https://en.wikipedia.org/wiki/Xorshift