2

I'm using an Intel Ivy Bridge CPU and want to use the RDRAND opcode (https://software.intel.com/en-us/articles/intel-digital-random-number-generator-drng-software-implementation-guide) in C#.

How can I call this CPU instruction via C#? I've seen an example of executing assembly code from c# here: x86/x64 CPUID in C#

But I'm not sure how to use it for RDRAND. The code doesn't need to check whether the CPU executing the code supports the instruction or not.

I've seen this C++ example of executing assembly byte code coming from drng_samples of Intel:

int rdrand32_step (uint32_t *rand)
{
    unsigned char ok;

    /* rdrand edx */
    asm volatile(".byte 0x0f,0xc7,0xf0; setc %1"
        : "=a" (*rand), "=qm" (ok)
        :
        : "edx"
    );

    return ok;
}

How can the example of executing assembly code in C# be combined with the C++ code coming from the Intel drng sample code?

Kevin
  • 16,549
  • 8
  • 60
  • 74
Mike de Klerk
  • 11,906
  • 8
  • 54
  • 76
  • Consider just using [`RNGCryptoServiceProvider`](https://msdn.microsoft.com/library/system.security.cryptography.rngcryptoserviceprovider), which should be good enough for most purposes that need better-than-pseudorandom numbers. – Jeroen Mostert Aug 02 '17 at 12:09
  • @JeroenMostert Thank you for your comment. But I really need it to be truely random to fit the purpose of scientificly accurate software experiments. I've ordered TrueRNGV3 but may take too long to arrive and I was looking for alternatives in the meanwhile and RDRAND seems promising. – Mike de Klerk Aug 02 '17 at 12:14
  • 2
    You can't do processor-specific assembly in C#. You can write a piece of C++ code for it and wrap that using C++/CLI or P/Invoke, as covered [here](https://stackoverflow.com/questions/13436130/). – Jeroen Mostert Aug 02 '17 at 12:32
  • @JeroenMostert I've edited the question. What do you think? Does this question now fits StackOverflow on-topicness? – Mike de Klerk Aug 02 '17 at 13:55
  • @MikedeKlerk Looks good to me. – fuz Aug 02 '17 at 13:58
  • 3
    Since you're no longer asking for a ready-made library, I'd say it fits. Voted to reopen. You don't want to futz around with `asm`, though -- `include ` and wrap `_rdrand_step32`. The hard work has already been done. – Jeroen Mostert Aug 02 '17 at 13:58
  • 1
    Generally, if a language makes something hard to do, you're not supposed to do it or you're doing it wrongly. If you have to resort to encoding bytes in hex and creating a function pointer to them, that should tell you something. – David Hoelzer Aug 02 '17 at 17:50

1 Answers1

7

There are answers out on SO that will generate (unmanaged) assembly code at runtime for managed code to call back into. That's all very interesting, but I propose that you simply use C++/CLI for this purpose, because it was designed to simplify interop scenarios. Create a new Visual C++ CLR class library and give it a single rdrandwrapper.cpp:

#include <immintrin.h>

using namespace System;

namespace RdRandWrapper {

#pragma managed(push, off)
  bool getRdRand(unsigned int* pv) {
    const int max_rdrand_tries = 10;
    for (int i = 0; i < max_rdrand_tries; ++i) {
      if (_rdrand32_step(pv)) return true;
    }
    return false;
  }
#pragma managed(pop)

  public ref class RandomGeneratorError : Exception
  {
  public:
    RandomGeneratorError() : Exception() {}
    RandomGeneratorError(String^ message) : Exception(message) {}
  };

  public ref class RdRandom
  {
  public:
    int Next() {
      unsigned int v;
      if (!getRdRand(&v)) {
        throw gcnew RandomGeneratorError("Failed to get hardware RNG number.");
      }
      return v & 0x7fffffff;
    }
  };
}

This is a very bare-bones implementation that just tries to mimic Random.Next in getting a single non-negative random integer. Per the question, it does not attempt to verify that RDRAND is actually available on the CPU, but it does handle the case where the instruction is present but fails to work. (This "cannot happen" on current hardware unless it's broken, as detailed here.)

The resulting assembly is a mixed assembly that can be consumed by managed C# code. Make sure to compile your assembly as either x86 or x64, same as your unmanaged code (by default, projects are set to compile as "Any CPU", which will not work correctly since the unmanaged code has only one particular bitness).

using System;
using RdRandWrapper;

class Program {
  static void Main(string[] args) {
    var r = new RdRandom();
    for (int i = 0; i != 10; ++i) {
      Console.WriteLine(r.Next());
    }
  }
}

I make no claims as to performance, but it's probably not great. If you wanted to get many random values this way, you would probably want a Next(int[] values) overload to get many random values in one call, to reduce the overhead of interop.

Jeroen Mostert
  • 27,176
  • 2
  • 52
  • 85
  • I will first need to install C++ development chain for Visual Studio then :) Great answer. I will give it a try. – Mike de Klerk Aug 03 '17 at 07:51
  • 1
    Working and complete answer! Thanks so much for the help! – Mike de Klerk Aug 03 '17 at 09:00
  • With respect to performance, I get ~366mbit/second random data when requesting Int64 per call. And each call resulted in a different value from the previous call. That will suffice :) – Mike de Klerk Aug 03 '17 at 09:17
  • No, the `String ^ message` is not a typo. See [here](https://stackoverflow.com/questions/202463/what-does-the-caret-mean-in-c-cli). – Kevin Jul 15 '19 at 16:57
  • If you're going to retry, I'd suggest at least 100 retries, or infinite so you get an infinite loop on broken hardware. @jww has reported that users of libcryptopp have filed bugs about spurious failures of rdrand with small retry counts (so apparently CPUs after IvyBridge-client can exhaust the RNG), but no reports of problems with retry-forever. Infinite retry count also avoids needing to check a bool return value. – Peter Cordes Jul 16 '19 at 02:24