RDRAND instructionを使う

# RDRAND instructionを使う

## はじめに
RDRANDおよびRDSEED instructionの使い方を説明します。
乱数および乱数SEEDを取得できます。Intel 64 and IA-32 instruction setで利用できます。

## RDRAND / RDSEED instruction

指定したregisterに乱数および乱数SEEDをloadします。ビット長は16, 32, 64が指定できます。
有効な値を取得できない場合があります。CF(Carry Flag)で有効かどうかを確認できます。

## Sample Code
rdrand / rdseedを指定した回数実行し、結果を表示する例を示します。

makefileは次の通りです。

```makefile
CC=gcc
CFLAGS=-I. -Wall -Werror -O2
INCS=

ifeq ($(shell uname),Linux)
  OBJS=test.o test_linux.o
else
  OBJS=test.o test_win.o
endif
LIBS=
TARGET=test

all: $(TARGET)

%.o: %.s $(INCS)
	$(CC) $(CFLAGS) -c -o $@ $<

$(TARGET): $(OBJS)
	$(CC) $(CFLAGS) -o $@ $^ $(LIBS)

clean:
	rm -rf $(TARGET) *.o
```

```c
// test.c
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <inttypes.h>

#define TYPE_RAND 0
#define TYPE_SEED 1

#define SIZE_16BIT 16
#define SIZE_32BIT 32
#define SIZE_64BIT 64

#ifdef __linux__
  #define EXE_NAME "test"
#else
  #define EXE_NAME "test.exe"
#endif

#define STRCMP(value, label) strncmp(value, label, strlen(label)+1)

extern uint16_t rdrand64(uint64_t* value);
extern uint16_t rdrand32(uint32_t* value);
extern uint16_t rdrand16(uint16_t* value);
extern uint16_t rdseed64(uint64_t* value);
extern uint16_t rdseed32(uint32_t* value);
extern uint16_t rdseed16(uint16_t* value);

void usage() {
  printf("usage:\n");
  printf("  ./"EXE_NAME" type size num\n");
  printf("    type : rand or seed\n");
  printf("    size : 16 or 32 or 64\n");
  printf("ex:\n");
  printf("  ./"EXE_NAME" rand 16 1000\n");
}

int main(int argc, char* argv[])
{
  int num = 0, type = TYPE_RAND, size = SIZE_32BIT, i;
  
  if (argc != 4) {
    usage();
    return -1;
  }
  
  if (STRCMP(argv[1], "rand") == 0) {
    type = TYPE_RAND;
  } else if (STRCMP(argv[1], "seed") == 0) {
    type = TYPE_SEED;
  } else {
    usage();
    return -1;
  }
  
  size = atoi(argv[2]);
  num = atoi(argv[3]);
  
  printf("type=%d ", type);
  printf("size=%d ", size);
  printf("num=%d\n", num);
  
  // c rrrr
  printf("c ");
  for (i=0; i<(size>>2); i++) {
    printf("r");
  }
  printf(" c:CF(1:valid), r:rand or seed value\n");
  
  if (type == TYPE_RAND) {
    if (size == SIZE_16BIT) {
      uint16_t v;
      uint16_t r;
      for (i=0; i<num; i++) {
        r = rdrand16(&v);
        printf("%d %04"PRIx16"\n", (r>>8)&0x01, v);
      }
    } else if (size == SIZE_32BIT) {
      uint32_t v;
      uint16_t r;
      for (i=0; i<num; i++) {
        r = rdrand32(&v);
        printf("%d %08"PRIx32"\n", (r>>8)&0x01, v);
      }
    } else if (size == SIZE_64BIT) {
      uint64_t v;
      uint16_t r;
      for (i=0; i<num; i++) {
        r = rdrand64(&v);
        printf("%d %016"PRIx64"\n", (r>>8)&0x01, v);
      }
    } else {
      usage();
      return -1;
    }
  } else {
    // type = TYPE_SEED
    if (size == SIZE_16BIT) {
      uint16_t v;
      uint16_t r;
      for (i=0; i<num; i++) {
        r = rdseed16(&v);
        printf("%d %04"PRIx16"\n", (r>>8)&0x01, v);
      }
    } else if (size == SIZE_32BIT) {
      uint32_t v;
      uint16_t r;
      for (i=0; i<num; i++) {
        r = rdseed32(&v);
        printf("%d %08"PRIx32"\n", (r>>8)&0x01, v);
      }
    } else if (size == SIZE_64BIT) {
      uint64_t v;
      uint16_t r;
      for (i=0; i<num; i++) {
        r = rdseed64(&v);
        printf("%d %016"PRIx64"\n", (r>>8)&0x01, v);
      }
    } else {
      usage();
      return -1;
    }
  }
  
  return 0;
}
```

windowsのassembly codeは次の通りです。

```
# test_win.s
    .globl rdrand64
    .globl rdrand32
    .globl rdrand16
    .globl rdseed64
    .globl rdseed32
    .globl rdseed16

# Microsoft x64 calling convention
# rcx(1st) rdx(2nd) r8(3rd) r9(4th)
# uint16_t rdrand64(uint64_t* value);
rdrand64:
    rdrand %rdx
    lahf
    mov %rdx, (%rcx)
    ret
# uint16_t rdrand32(uint32_t* value);
rdrand32:
    rdrand %edx
    lahf
    mov %edx, (%rcx)
    ret
# uint16_t rdrand16(uint16_t* value);
rdrand16:
    rdrand %dx
    lahf
    mov %dx, (%rcx)
    ret

# uint16_t rdseed64(uint64_t* value);
rdseed64:
    rdseed %rdx
    lahf
    mov %rdx, (%rcx)
    ret
# uint16_t rdseed32(uint32_t* value);
rdseed32:
    rdseed %edx
    lahf
    mov %edx, (%rcx)
    ret
# uint16_t rdseed16(uint16_t* value);
rdseed16:
    rdseed %dx
    lahf
    mov %dx, (%rcx)
    ret
```

linuxのassembly codeは次の通りです。

```
# test_linux.s
    .globl rdrand64
    .globl rdrand32
    .globl rdrand16
    .globl rdseed64
    .globl rdseed32
    .globl rdseed16

# System V AMD64 ABI
# rdi(1st) rsi(2nd) rdx(3rd) rcx(4th)
# uint16_t rdrand64(uint64_t* value);
rdrand64:
    rdrand %rsi
    lahf
    mov %rsi, (%rdi)
    ret
# uint16_t rdrand32(uint32_t* value);
rdrand32:
    rdrand %esi
    lahf
    mov %esi, (%rdi)
    ret
# uint16_t rdrand16(uint16_t* value);
rdrand16:
    rdrand %si
    lahf
    mov %si, (%rdi)
    ret

# uint16_t rdseed64(uint64_t* value);
rdseed64:
    rdseed %rsi
    lahf
    mov %rsi, (%rdi)
    ret
# uint16_t rdseed32(uint32_t* value);
rdseed32:
    rdseed %esi
    lahf
    mov %esi, (%rdi)
    ret
# uint16_t rdseed16(uint16_t* value);
rdseed16:
    rdseed %si
    lahf
    mov %si, (%rdi)
    ret
```

windows(Mingw-w64)でビルドします。

```bash
$ gcc --version
gcc.exe (Rev2, Built by MSYS2 project) 10.3.0
Copyright (C) 2020 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.


$ make
gcc -I. -Wall -Werror -O2   -c -o test.o test.c
gcc -I. -Wall -Werror -O2 -c -o test_win.o test_win.s
gcc -I. -Wall -Werror -O2 -o test test.o test_win.o
```

linuxでビルドします。

```bash
$ cat /etc/lsb-release 
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=18.04
DISTRIB_CODENAME=bionic
DISTRIB_DESCRIPTION="Ubuntu 18.04.5 LTS"
$ gcc --version
gcc (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0
Copyright (C) 2017 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

$ make
gcc -I. -Wall -Werror -O2   -c -o test.o test.c
gcc -I. -Wall -Werror -O2 -c -o test_linux.o test_linux.s
gcc -I. -Wall -Werror -O2 -o test test.o test_linux.o 
```

プログラムを実行します。type, size, numを指定します。
typeにはinstructionを指定します。rand / seedが選択できます。
sizeにはビット長を指定します。16 / 32 / 64を指定します。
numには実行回数を指定します。

```bash
$ ./test.exe
usage:
  ./test.exe type size num
    type : rand or seed
    size : 16 or 32 or 64
ex:
  ./test.exe rand 16 1000

$ ./test.exe rand 16 10
type=0 size=16 num=10
c rrrr c:CF(1:valid), r:rand or seed value
1 6b57
1 0611
1 66d4
1 7efc
1 6e46
1 2257
1 c5c7
1 4a3f
1 84ce
1 f471
```

なお、type=seed, size=16を設定するとCF=0になることがありました。

```bash
$ ./test.exe seed 16 100
type=1 size=16 num=100
c rrrr c:CF(1:valid), r:rand or seed value
1 5212
...
1 d32a
0 0000
0 0000
1 9064
...
1 2e4b
0 0000
1 7a3c
...
1 b2e7
0 0000
1 9760
```

## References

- [1] https://en.wikipedia.org/wiki/RDRAND
- [2] https://www.felixcloutier.com/x86/rdrand
- [3] https://www.felixcloutier.com/x86/rdseed