Display RDTSC opcode result to stdout
[page last modified 2016-03-21]
This 32-bit Windows executable displays the result of the RDTSC opcode as a decimal number to stdout. The caller can select the number of lower order bits to display -- 0 to the full 64.
The counter is 64-bit and incremented once per clock tick on Pentium class (586) and later processors. Even on recent "slower" processors the clock frequency is a couple billion ticks or so per second.
To display the full count (all 64 bits) to stdout:
C>phrdtsc 64
The source is included in the .zip distribution and is shown at
the bottom of this page. It's assembly language only because I was
in an old-school mood when I wrote it.
A benefit of assembly is the .exe is only 1k.
The code was compiled with Microsoft's circa 2003 ml.exe assembler version
7.10.3077 and associated link.exe (same version). More recent versions
of these tools (google visual studio express) are available free from
Microsoft and will work fine, though the result .exe will be larger because
newer versions of the linker are less forgiving about merging sections.
Precise build instructions are at the top of the single source file.
The source can also be used as an example of how to write a tiny Win32 executable -- a useless endeavor except to those such a thing brings joy to.
phrdtsc output can be piped to another program or redirected to a file, or processed further in a .BAT file -- which is why I wrote the tool. I wanted a truly random number in a .BAT file. There is DOS %random%, but that's only 15 bits of resolution and not very random, especially if you run your .BAT at a daily scheduled time, since %random% is seeded by the time-of-day second.
With phrdtsc you can get a very random number in a .BAT file easily. The below sets environment var r60k to some number 0..59999:
for /f %%r in ('phrdtsc 31') do set /a r60k=%%r %% 60000
The above demonstrates why phrdtsc allows selection of the number of lower order bits. Unsigned .BAT "set arithmetic" numerics are limited to 31 bits.
If you want a random number in the range 0..15 use phrdtsc 4. If you can handle a 20 digit number (or just want to watch that enormous counter spin) use phrdtsc 64.
Change Log
Initial version released 3/21/2016
Click here to download phrdtsc160321.zip
You are visitor 10243 Go to Home Page
;----------------------------------------------------------------------------- ; ; Win32 console program to display specified number of lower bits of the ; 64-bit X86 RDTSC opcode result to stdout. paulhoule.com 3/21/2016 ; ; Built with Microsoft ml.exe ver 7.1 via: ; ; ml phrdtsc.asm /link /incremental:no /entry:main /filealign:512 ; /merge:.data=.text /merge:.rdata=.text /safeseh:no ; ; The lib environment variable must point to the folder holding kernel32.lib, ; eg set lib=d:\msvc71\sdk\lib ; ; More recent compiler versions should work; however, their linkers may ; produce a larger executable by refusing to merge some sections. ; ;----------------------------------------------------------------------------- .586 ;need .586 for rdtsc opcode .model flat, stdcall includelib kernel32.lib GetCommandLineA proto ;Win32 definitions GetStdHandle proto :dword STD_OUTPUT_HANDLE equ -11 WriteFile proto hFile:ptr, :ptr char, :dword, :ptr dword, :ptr SkipToArgs proto ;local function prototypes GetNumBits proto FmtDec64 proto .data NL textequ <0dh,0ah> errmsg db '*** bad argument',NL helpmsg db NL db 'Displays lower n (0..64) bits of CPU timer. paulhoule.com 3/21/2016',NL db 'eg: phrdtsc 31',NL endmsg db 0 dup(?) .code main proc local buf[32]:byte ;scratch buffer invoke GetStdHandle,STD_OUTPUT_HANDLE xchg edi,eax ;edi= standard output handle invoke GetCommandLineA xchg esi,eax ;esi= ptr to Win32 command line call SkipToArgs ;advance esi to first arg call GetNumBits ;eax= parsed first arg (0..64) .if eax > 64 ;if invalid (or missing) argument, mov ebx,offset errmsg ;ebx= invalid arg + help msg or eax,eax .if sign? ;if no argument present add ebx,sizeof errmsg ;ebx= help only msg .endif mov esi,offset endmsg ;esi= length of msg sub esi,ebx .else xchg ecx,eax ;ecx= bits to return (0..64) rdtsc cmp ecx,64 ;edx:eax= isolated lower bits sbb esi,esi shl esi,cl not esi .if ecx < 32 and eax,esi cdq .endif and edx,esi lea ebx,buf[sizeof buf-2] ;ebx= ptr to formatted result + crlf mov word ptr [ebx],0a0dh call FmtDec64 lea esi,[eax+2] .endif invoke WriteFile,edi,ebx,esi,addr buf,0 ret main endp ;----------------------------------------------------------------------------- ; Skips over program name to first argument of Win32 command line. This ; simple logic depends on the observed behavior that Win32 insures at least ; one space exists between the program name and first argument. ; ; In: esi= ptr to ANSI command line string (from GetCommandLineA) ; Out: esi= advanced to first argument, or terminating 0 if no args present ;----------------------------------------------------------------------------- SkipToArgs proc xor eax,eax .repeat lodsb .break .if !al .if al == '"' lodsb .break .if !al .continue .if al == '"' ;adjacent double quotes are ignored not ah .endif or al,ah ;ignore if within double quotes .until al == ' ' .while al == ' ' || al == 9 ;skip all space/tab after prog name lodsb .endw dec esi ret SkipToArgs endp ;----------------------------------------------------------------------------- ; Parses number of bits argument. Stops if value exceeds max valid (64) or ; a non-digit is encountered. Argument must end with 0/space/tab. ; ; In: esi= ptr within 0-terminated string to start of argument ; Out: esi= advanced beyond processed digit(s) -- this may be no advance ; eax= 0..64 - valid result, 65 - any overflow or bad terminator, ; eax = 80000000h if valid terminator but no digits found ;----------------------------------------------------------------------------- GetNumBits proc xor eax,eax ;set all digit register bits zero mov edx,80000000h ;init result to "no digits seen" .repeat lodsb sub al,'0' .break .if carry? || al > 9 imul edx,10 add edx,eax .until edx > 64 add al,'0' .if !zero? && al != ' ' && al != 9 mov edx,65 ;overflow or bad terminator - error .endif dec esi ;esi= ptr to terminator char xchg eax,edx ret GetNumBits endp ;----------------------------------------------------------------------------- ; Formats a 64-bit unsigned to decimal. ; ; In: edx:eax= 64-bit value (treated as unsigned) ; ebx= 1+ ptr for LOWEST ORDER digit -- ie last output buffer byte + 1 ; Out: ebx= ptr to highest order digit... at least 1 digit is always stored ; eax= output digit count (1..20) ;----------------------------------------------------------------------------- FmtDec64 proc uses edi push ebx push 10 ;ecx= a ten to use as divisor pop ecx mov edi,edx ;loop requires dividend in edi:edx .repeat xor edx,edx xchg eax,edi div ecx xchg eax,edi div ecx dec ebx add dl,'0' mov [ebx],dl mov edx,edi or edx,eax .until zero? pop eax ;eax= number of digits output sub eax,ebx ret FmtDec64 endp end