A while ago I set out to write a little tool that would show the time a system had been running since the last reboot. It seemed like something that should be fairly easy to do, but as it turns out, it isn’t entirely straightforward.

The first thing that came to my mind was the GetTickCount function. It returns the number of milliseconds since the system was started, which fits nicely. There is one problem of course, the value returned is a DWORD, and that limits the time it can handle to about 50 days.

Microsoft realized this as well, and added GetTickCount64, which returns a 64-bit value instead. Unfortunately it only works on Vista+.

“To obtain the time elapsed since the computer was started, retrieve the System Up Time counter in the performance data in the registry key HKEY_PERFORMANCE_DATA. The value returned is an 8-byte value. For more information, see Performance Counters.”

The performance counters are a nice idea. Basically they provide a homogeneous interface to a multitude of counters that give information about how well the operating system or an application, service, or driver is performing.

The recommended way to access the data is through the PDH interface. You access the counters by specifying a counter path; a string that describes the computer, object, counter, and instance you are interested in.

Looking through the list of counters by objects for the ‘System’ object, we find the ‘System Up Time’ counter, which is exactly what we need.

So I wrote a test application to open a query, add the counter “\System\System Up Time”, collect the data, and display it. It failed. Apparently my computer did not have a ‘System Up Time’ counter.

Reading over the documentation for the PDH interface again did not help, but after some searching I ended up at this help article:

“Performance Data Helper (PDH) APIs use object and counter names that are in the localized language. Therefore, applications that use PDH APIs should always use the localized string for the object or counter name specification.”

Since I was running a Danish version of Windows, that explained the problem!

Following the steps outlined there, I found that the ‘System’ object has index 2, and the ‘System Up Time’ counter has index 674. With these indices in hand, you can then call the PdhLookupPerfNameByIndex function to get the localized names. Using the localized path “\System\Computerperiode uden afbrydelser” gave the desired result.

The choice to make the paths use localized names makes it somewhat more involved to use these functions. Also, this should have been described much more clearly in the PDH documentation, since it is quite possible for a developer using English Windows to read over the documentation like I did, and use a hardcoded path for a counter. This will work nicely while testing, and then fail if someone with a localized Windows uses it.

As an example, let’s take a look at the PsInfo tool. It is written by Mark Russinovich, one of the people behind Sysinternals.com, a site that specializes in advanced system utilities and technical information. He is also a coauthor of the Windows Internals book, describing the inner workings of Windows operation systems.

Running PsInfo on my system I get:

1

2

3

4

5

6

7

PsInfo v1.75-Local andremote system information viewer

Copyright(C)2001-2007Mark Russinovich

Sysinternals-www.sysinternals.com

System information for\\removed:

Uptime:Error reading uptime

...

Could it be? let’s have a little peek inside PsInfo.exe:

Assembly (x86)

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

perform_query:

pushesi

leaecx,[esp+834h+szCounterPath]

pushoffsetaSUT; "\\\\%s\\System\\System Up Time"

pushecx

callsprintf?

movecx,[esp+83Ch+hQuery]

addesp,0Ch

leaedx,[esp+830h+phCounter]

pushedx

push0

leaeax,[esp+838h+szCounterPath]

pusheax

pushecx

callPdhAddCounterW

Indeed, a hardcoded path to the counter using the English names.

Microsoft must have realized it could be a problem as well, because I found the function PdhAddEnglishCounter, added in Vista, which made me smile.

When Intel expanded the 8086 architecture to 32-bit in 1985, they extended the 16-bit registers present to 32-bit registers. ax became eax, but it was still possible to use the low 16 bits of eax as ax just like before. Their choice was that performing operations on the low 16 bits did not change the high 16 bits of the register.

AMD expanded the 32-bit architecture to 64-bit in 2003. This was again a superset of the original, making it backwards compatible. They extended the 32-bit registers to 64-bit, and eax became rax. Again it was possible to to perform operations on the low 32 bits, but doing so clears the high 32 bits of the register.

“Operations that output to a 32-bit subregister are automatically zero-extended to the entire 64-bit register. Operations that output to 8-bit or 16-bit subregisters are not zero-extended (this is compatible x86 behavior).”

Now both choices work as far as backwards compatibility goes, and as long as we as programmers are aware of what happens, neither is a problem.

When building the aPLib compression library, I use Visual C++ to generate assembly listings, which I then perform some changes on with a perl script, before assembling the object files. While working on the recently released 64-bit version, I ran into a problem — the debug build of the library worked fine, but the release build did not.

Bugs like this are often caused by some improper memory usage, so I spent a day trying to track down the problem without much luck. Somehow the contents of a register was corrupted.

Looking through the code in HIEW I finally found the cause; a seemingly random instruction that wrote to the 32-bit part of a register, thereby clearing the high 32 bits. Then it dawned on me.

Visual C++ emits padding macros into assembly listings to align code and improve performance. These macros, npad, are defined in a file called listing.inc which resides in the Visual C++ include folder. But there is no 64-bit version of this file!

Let’s have a look then:

Assembly (x86)

1

2

3

4

5

6

7

8

9

10

11

;; LISTING.INC

;; non destructive nops

npadmacrosize

ifsizeeq1

nop

else

ifsizeeq2

movedi,edi

else

...

And there we have it. An instruction like mov edi, edi is safe to use as padding in 32-bit code, because moving the register to itself has no effect. But if you insert it in 64-bit code, it all of a sudden has an effect — the high 32 bits of rdi are cleared.

I have reported the problem to Microsoft and they say it will be addressed in a future release.