A fully featured cross-platform port of rdmsr/wrmsr from intel/msr-tools. Windows support added via the wMSR kernel driver.
The upstream source files are unchanged, compatibility headers (include/compat) bridge the Linux kernel device (/dev/cpu/N/msr) interface to the wMSR driver interface at compile time.
Windows: the msr.sys kernel driver from wMSR must be loaded before running either tool.
Linux: the msr kernel module must be loaded (modprobe msr).
Usage: rdmsr [options] regno
--help -h Print this help
--version -V Print current version
--hexadecimal -x Hexadecimal output (lower case)
--capital-hex -X Hexadecimal output (upper case)
--decimal -d Signed decimal output
--unsigned -u Unsigned decimal output
--octal -o Octal output
--c-language -c Format output as a C language constant
--zero-pad -0 Output leading zeroes
--raw -r Raw binary output
--all -a all processors
--processor # -p Select processor number (default 0)
--bitfield h:l -f Output bits [h:l] only
Usage: wrmsr [options] regno value...
--help -h Print this help
--version -V Print current version
--all -a all processors
--processor # -p Select processor number (default 0)
Output at build/win64/rdmsr.exe and build/win64/wrmsr.exe.
Requires MSVC. Run from a Developer Command Prompt.
scripts\buildRequires mingw-w64. On macOS:
brew install mingw-w64
Run from any terminal:
scripts/build.sh win64Output at build/native/rdmsr and build/native/wrmsr.
scripts/build.sh nativeBoth rdmsr.c and wrmsr.c are unmodified from the upstream intel/msr-tools source.
On Linux they directly use the kernel's /dev/cpu/N/msr devices via standard POSIX calls. On Windows, the include/compat/ headers provide a fake posix API to rdmsr.c/wrmsr.c and redirect the syscalls to the wMSR kernel driver.
This works because the compat headers are included before the system headers, on Linux they simply passthrough to system headers while on windows they implement the fake posix API.
#ifndef _WIN32
#include_next <posix_header.h>
#else
// fake windows-only API
#endifOn Linux, each CPU is a separate device file. open returns a file descriptor and the CPU index is baked into the path:
sprintf(msr_file_name, "/dev/cpu/%d/msr", cpu);
fd = open(msr_file_name, O_RDONLY);On Windows, there is one device (\\.\msr) shared for all CPUs. The device handle is stored in a static variable in win32.h, shared across all compat headers:
static HANDLE msr_device = INVALID_HANDLE_VALUE;The compat open() function parses the CPU index out of the path, opens the wMSR device once (lazily, on first call), and returns the CPU index instead of the fd.
static int open(const char *name, int _) {
if (msr_device == INVALID_HANDLE_VALUE)
msr_device = msr_open();
int cpu;
sscanf(name, "/dev/cpu/%d/msr", &cpu);
return cpu;
}On Linux, pread uses the file offset as the MSR register number:
p[read | write](fd, &data, sizeof data, reg);On Windows, the compat p[read | write]() functions reinterpret the fake fd (returned by the compat open() function) as the CPU index and the offset as the register number, then call into the wMSR kernel driver:
static size_t p[read | write](int fd, void *data, size_t size, off_t offset) {
uint32_t cpu = (uint32_t)fd;
uint32_t reg = (uint32_t)offset;
if ()
return 0;
return size;
}On Linux, CPUs are enumerated by scanning /dev/cpu/:
scandir("/dev/cpu", &namelist, dir_filter, 0);On Windows, GetActiveProcessorCount() gives the count and the compat scandir() function fakes the directory entries:
static int scandir(..., struct dirent ***namelist, ...) {
DWORD count = GetActiveProcessorCount(ALL_PROCESSOR_GROUPS);
for (DWORD p = 0; p < count; ++p) {
int len = snprintf(NULL, 0, "%u", (unsigned)p);
struct dirent *entry = malloc(sizeof(struct dirent) + len + 1);
sprintf(entry->d_name, "%u", (unsigned)p);
...
}
}On Linux, the upstream code checks specific posix errno values (ENXIO, EIO) to distinguish error types before falling through to generic perror().
On Windows, these error paths are not possible, so we define the POSIX signals as ULONG_MAX (which will never match any errno value), forcing all error paths to the compat perror() function:
#ifndef ENXIO
#define ENXIO ULONG_MAX
#endif
#ifndef EIO
#define EIO ULONG_MAX
#endifAnd the compat perror() function prints the human-readable error message returned by the windows kernel API and immediately exits, with a special case for ERROR_FILE_NOT_FOUND to point the user at the wMSR kernel driver:
static void compat_perror(const char *s) {
DWORD err = GetLastError();
char *buf = NULL;
if (err == ERROR_FILE_NOT_FOUND) {
buf = "Could not find the wMSR driver. See https://github.com/nullpytr/wMSR";
} else {
FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | ..., NULL, err, 0, &buf, 0, NULL);
}
fprintf(stderr, "%s: %s", s, buf);
ExitProcess(err);
}On Linux, close(fd) releases the file descriptor after each read or write.
On Windows, the static device handle msr_device lives for the lifetime of the process. So the compat close() function is a no-op and the handle is only closed by the OS when the process exits:
static void close(int _) { }Because that's not nearly as fun, and the compat layer approach avoids adding complexity to the rdmsr.c / wrmsr.c source files.