Quantcast
Channel: TSAdminEx – Remko Weijnen's Blog (Remko's Blog)
Viewing all articles
Browse latest Browse all 14

Reading physical memory size from the registry

$
0
0

I’m working on a new build of TSAdminEx for which I need to query the total amount of physical memory. Locally we can use the GlobalMemoryStatusEx API but there’s no API to do this remotely. It would be possible using WMI but I decided not to use that because I dislike it because of it’s slowness and I need support for older OS versions which might not have WMI.

So I found in the registry the following key:

HKLM\HARDWARE\RESOURCEMAP\System Resources\Physical Memory

It has a value .Translated of type RES_RESOURCE_LIST which seems undocumented besides stating that it exists. Regedit knows how to handle it though. If you doubleclick on the key you will see something like this:

ResourceLists

If we click on the list and choose display we see the following:

Resources

If we sum the Length of all memory blocks we get the total amount of Physical Memory. I wondered how to do this programmatically so I Googled on it but found mostly pages where the values are extracted at fixed offsets rather than knowing the correct structure. I found however that the structure is present in the DDK (wdm.h) and is called CM_RESOURCE_LIST. I was also lucky because Jack van Nuenen had already done a conversion of those headers to Delphi which saved me some work. I did some small modifications to the headers and included them in a new unit into the Jedi Apilib (JwaWdm.pas).

Let’s take a look at the structure:

CM_RESOURCE_LIST = packed record
    Count: ULONG;
    List: array[0..ANYSIZE_ARRAY-1] of CM_FULL_RESOURCE_DESCRIPTOR;
  end;
  PCM_RESOURCE_LIST = ^CM_RESOURCE_LIST;

ANYSIZE_ARRAY is a constant from the Windows PSDK which is meant to make a dummy array, so here we make actually an array[0..0]. However the compiler permits us to do array[1], array[2] if we turn off range checking so this makes it easier to loop through the array.

The CM_FULL_RESOURCE_DESCRIPTOR describes the resource:

CM_FULL_RESOURCE_DESCRIPTOR = packed record
    InterfaceType: ULONG;//INTERFACE_TYPE;
    BusNumber: ULONG;
    PartialResourceList: CM_PARTIAL_RESOURCE_LIST;
  end;
  PCM_FULL_RESOURCE_DESCRIPTOR = ^CM_FULL_RESOURCE_DESCRIPTOR;

And finally points to a CM_PARTIAL_RESOURCE_LIST structure:

CM_PARTIAL_RESOURCE_LIST = packed record
    Version: USHORT;
    Revision: USHORT;
    Count: ULONG;
    PartialDescriptors: array[0..ANYSIZE_ARRAY-1] of CM_PARTIAL_RESOURCE_DESCRIPTOR;
  end;
  PCM_PARTIAL_RESOURCE_LIST = ^CM_PARTIAL_RESOURCE_LIST;

Which in turn points to an array of CM_PARTIAL_RESOURCE_DESCRIPTOR structures:

CM_PARTIAL_RESOURCE_DESCRIPTOR = packed record
    ResType: UCHAR;
    ShareDisposition: CM_SHARE_DISPOSITION; // has UCHAR = Byte size
    Flags: USHORT;
    case Byte of
      0: (Generic: RDD_Generic);
      1: (Port: RDD_Port);
      2: (Interrupt: RDD_Interrupt);
      3: (Memory: RDD_Memory);
      4: (Dma: RDD_DMA);
      5: (DevicePrivate: RDD_DevicePrivate);
      6: (BusNumber: RDD_BusNumber);
      7: (DeviceSpecificData: RDD_DeviceSpecificData);
  end;
  PCM_PARTIAL_RESOURCE_DESCRIPTOR = ^CM_PARTIAL_RESOURCE_DESCRIPTOR;

We are interested in the RDD_MEMORY structure which finally gives us the requested information (Length):

RDD_MEMORY = packed record
    Start: PHYSICAL_ADDRESS;
    Length: ULONG;
  end;
  PRDD_MEMORY = ^RDD_MEMORY;

So it’s just a matter of looping through the Partial Descriptor array and summing up the Lengths. However on a x64 (64 bit) Windows the memory length is a 8 bytes instead of 4 byte, so we need to account for that. If we use a 64 bit compiler it will handle this automatically because the ULONG type is 8 bytes then. That will make it impossible though to query remote 32 bit systems from 64 bit or vice versa. So I wondered how Taskmanager handled that, the answer is: IT DOESN’T! See the following example:

Querying locally on 32 bit system:

x86Local

Now I query the same machine remotely from a 64 bit system:

x86Remote

I think it can only be solved by using two different structures, one for x86 and one for x64. So I introduced the following structures:

// This is for x64
  CM_PARTIAL_RESOURCE_DESCRIPTOR_EX = packed record
    ResType: UCHAR;
    ShareDisposition: CM_SHARE_DISPOSITION; // has UCHAR = Byte size
    Flags: USHORT;
    case Byte of
      0: (Generic: RDD_GENERIC_EX);
      1: (Port: RDD_PORT_EX);
      2: (Interrupt: RDD_INTERRUPT_EX);
      3: (Memory: RDD_MEMORY_EX);
      4: (Dma: RDD_DMA);
      5: (DevicePrivate: RDD_DevicePrivate);
      6: (BusNumber: RDD_BusNumber);
      7: (DeviceSpecificData: RDD_DeviceSpecificData);
  end;
  PCM_PARTIAL_RESOURCE_DESCRIPTOR_EX = ^CM_PARTIAL_RESOURCE_DESCRIPTOR_EX;

  // This is for x64
  RDD_MEMORY_EX = packed record
    Start: PHYSICAL_ADDRESS;
    Length: ULONGLONG;
  end;
  PRDD_MEMORY_EX = ^RDD_MEMORY_EX;

Now we need a generic function to query both x86 and x64 (I use the Environment variable PROCESSOR_ARCHITECTURE to determine if a system is 64bit):

{ This functions reads out the total amount of Physical Memory in Bytes for
  local or remote systems using the registry. Locally user rights are needed
  but for Remote Systems admin righs are needed. If reading the memory fails
  (eg because of access denied) the returned result is 0, any exceptions are
  surpressed }
function GetPhysicalMemoryFromRegistry(const Machine: String = ''): Int64;
const
  EnvKey: String = '\SYSTEM\CurrentControlSet\Control\Session Manager\Environment';
  EnvVal: String = 'PROCESSOR_ARCHITECTURE';
  PhysMemKey: String = '\HARDWARE\RESOURCEMAP\System Resources\Physical Memory';
  PhysMemVal: String = '.Translated';
var
  Is64Bit: Boolean;
  Reg: TRegistry;
  RootKey: HKEY;
  cbSize: Integer;
  ResourceListPtr: PCM_RESOURCE_LIST;
  PartDescript: PCM_PARTIAL_RESOURCE_DESCRIPTOR;
  PartDescriptEx: PCM_PARTIAL_RESOURCE_DESCRIPTOR_EX;
  Index: Integer;
  Counter: Integer;
  s: string;
begin
  // Default result = 0
  Result := 0;
  ResourceListPtr := nil;

  // Prepend \\ to machinename if needed
  s := Machine;
  if Pos('\\', s) <> 0 then
  begin
    s := '\\' + s;
  end;

  { Connect to (remote) Registry. Please note there is a bug in Windows 2003 SP1
    from http://support.microsoft.com/kb/906570: After you apply Microsoft
    Windows Server 2003 Service Pack 1 (SP1) or install an x64-based version of
    Windows Server 2003, a custom program that uses the RegConnectRegistry
    function can no longer access the registry of a remote computer.
    I was not able to test if this bug prevents us from reading the value
    remotely }
  if RegConnectRegistry(PChar(Machine), HKEY_LOCAL_MACHINE, RootKey) <> ERROR_SUCCESS then
  begin
    Exit;
  end;

  Reg := TRegistry.Create;
  try
    Reg.RootKey := RootKey;
    { First we read out the Environment variable PROCESSOR_ARCHITECTURE to
      determine is the system is x86 or x64 }
    if Reg.OpenKeyReadOnly(EnvKey) then
    begin
      try
        { Both intel and Amd 64 bit systems have the value AMD64 }
        Is64Bit := CompareText(Reg.ReadString(EnvVal), 'AMD64') = 0;

        // Now open the Physical Memory key
        if Reg.OpenKeyReadOnly(PhysMemKey) then
        begin

          { Get Value Size in bytes }
          cbSize := Reg.GetDataSize(PhysMemVal);

          { Check is Size > 0 }
          if cbSize > 0 then begin

            { Allocate and zero Memory }
            ResourceListPtr := AllocMem(cbSize);

            { Read data into ResourceListPtr var }
            Reg.ReadBinaryData(PhysMemVal, ResourceListPtr^, cbSize);

            { Loop throught the ResourceList(s), usually it's only 1 }
            for Index := 0 to ResourceListPtr^.Count-1 do
            begin
              { x64 systems have a slightly different structure because the
                memory size parameter is not ULONG but ULONGLONG. To solve this we
                assign the PartialDescriptors array to 2 pointers. }

              PartDescript := @ResourceListPtr^.List[Index].PartialResourceList.PartialDescriptors;   // x86
              PartDescriptEx := @ResourceListPtr^.List[Index].PartialResourceList.PartialDescriptors; // x64
              for Counter := 0 to ResourceListPtr^.List[Index].PartialResourceList.Count - 1 do
              begin
                begin
                  if Is64Bit then { for x64 we use PartDescriptEx }

                  begin
                    { Check if the Resourcetype = Memory and that the memory is
                      read/write }
                    if (PartDescriptEx.ResType = CmResourceTypeMemory) and
                      (PartDescriptEx.Flags = CM_RESOURCE_MEMORY_READ_WRITE) then
                    begin
                      Result := Result + PartDescriptEx.Memory.Length;
                    end;
                    { The Inc operator increases the pointer by
                      SizeOf(PartDescriptEx^) which jumps to the next item in
                      the array }
                    Inc(PartDescriptEx);
                  end
                  else begin { for x86 we use PartDescript }
                    { Check if the Resourcetype = Memory and that the memory is
                      read/write }
                    if (PartDescript.ResType = CmResourceTypeMemory) and
                      (PartDescript.Flags = CM_RESOURCE_MEMORY_READ_WRITE) then
                    begin
                      Result := Result + PartDescript.Memory.Length;
                    end;
                    { The Inc operator increases the pointer by
                      SizeOf(PartDescriptEx^) which jumps to the next item in
                      the array }
                    Inc(PartDescript);
                  end;
                end;
              end;
            end;
          end;
        end;
      except
        { Return 0 on any exception }
        Result := 0;
      end;

      { Free the memory }
      if ResourceListPtr <> nil then FreeMem(ResourceListPtr);
      PartDescript := nil;
      PartDescriptEx := nil;
    end;

  finally
    { Close the key }
    RegCloseKey(RootKey);
    FreeAndNil(Reg);
  end;
end;


Viewing all articles
Browse latest Browse all 14

Latest Images

Trending Articles





Latest Images