SMBIOS Library
Memory_Windows.cpp
Go to the documentation of this file.
1 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2  * vim:expandtab:autoindent:tabstop=4:shiftwidth=4:filetype=c:cindent:textwidth=0:
3  *
4  * Copyright (C) 2005 Dell Inc.
5  * by Michael Brown <Michael_E_Brown@dell.com>
6  * Licensed under the Open Software License version 2.1
7  *
8  * Alternatively, you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published
10  * by the Free Software Foundation; either version 2 of the License,
11  * or (at your option) any later version.
12 
13  * This program is distributed in the hope that it will be useful, but
14  * WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
16  * See the GNU General Public License for more details.
17  */
18 
19 #define LIBSMBIOS_SOURCE
20 #include "MemoryImpl.h"
21 #include "miniddk.h"
22 
23 // include this last
24 #include "smbios/message.h"
25 
26 using namespace std;
27 
28 namespace memory
29 {
30  MemoryFactoryImpl::MemoryFactoryImpl()
31  {
32  setParameter("memFile", "\\Device\\PhysicalMemory");
33  }
34 
35 
36  // Non Member Functions
37  NtOpenSectionPtr NtOpenSection = NULL;
38  NtClosePtr NtClose = NULL;
39  NtMapViewOfSectionPtr NtMapViewOfSection = NULL;
40  NtUnmapViewOfSectionPtr NtUnmapViewOfSection = NULL;
41  RtlInitUnicodeStringPtr RtlInitUnicodeString = NULL;
42  ZwSystemDebugControlPtr ZwSystemDebugControl = NULL;
43 
44  EnumSystemFirmwareTablesPtr EnumSystemFirmwareTables = NULL;
45  GetSystemFirmwareTablePtr GetSystemFirmwareTable = NULL;
46  u8 * CBlockBuffer = NULL;
47  u8 * EBlockBuffer = NULL;
48  int reopenHint = 1;
49 
50  int LoadNtdllFuncs(void)
51  {
52  HMODULE hNtdll;
53  HMODULE hKerneldll;
54 
55  hNtdll = GetModuleHandle(L"ntdll.dll");
56  hKerneldll = GetModuleHandle( L"kernel32.dll" );
57  if (!(hNtdll && hKerneldll))
58  return FALSE;
59 
60  // should be available across all systems no matter our priv level
61  NtOpenSection = (NtOpenSectionPtr) GetProcAddress(hNtdll, "NtOpenSection");
62  NtClose = (NtClosePtr) GetProcAddress(hNtdll, "NtClose");
63  NtMapViewOfSection = (NtMapViewOfSectionPtr) GetProcAddress(hNtdll, "NtMapViewOfSection");
64  NtUnmapViewOfSection = (NtUnmapViewOfSectionPtr) GetProcAddress(hNtdll, "NtUnmapViewOfSection");
65  RtlInitUnicodeString = (RtlInitUnicodeStringPtr) GetProcAddress(hNtdll, "RtlInitUnicodeString");
66  ZwSystemDebugControl = (ZwSystemDebugControlPtr) GetProcAddress(hNtdll, "ZwSystemDebugControl");
67 
68  // Donot return false since these APIs are only available on W2K3 SP1 and higher.
69  // returning FALSE will break libsmbios on W2K and W2K3( no SP )
70  EnumSystemFirmwareTables = (EnumSystemFirmwareTablesPtr) GetProcAddress(hKerneldll, "EnumSystemFirmwareTables");
71  GetSystemFirmwareTable = (GetSystemFirmwareTablePtr) GetProcAddress(hKerneldll, "GetSystemFirmwareTable");
72 
73  return TRUE;
74  }
75 
76  // MEB: fatal issue
77  // MEB: pass in filename. use getParameterString("memfile") as the
78  // parameter. makes it possible to unit test against file.
79  HANDLE OpenMemAccess(void)
80  {
81  UNICODE_STRING usDevmem;
82  OBJECT_ATTRIBUTES oaAttrs;
83  NTSTATUS status;
84  HANDLE hPhysMem = NULL;
85 
86  RtlInitUnicodeString(&usDevmem, L"\\device\\physicalmemory");
87  InitializeObjectAttributes(&oaAttrs, &usDevmem, OBJ_CASE_INSENSITIVE, NULL, NULL);
88  status = NtOpenSection(&hPhysMem, SECTION_MAP_READ, &oaAttrs);
89 
90  if (!NT_SUCCESS(status))
91  {
92  hPhysMem = NULL;
93  }
94 
95  return hPhysMem;
96  }
97 
98  int CloseMemAccess(HANDLE hPhysMem)
99  {
100  NTSTATUS status;
101 
102  status = NtClose(hPhysMem);
103 
104  if (!NT_SUCCESS(status))
105  {
106  return FALSE;
107  }
108 
109  return TRUE;
110  }
111 
112  int MapMem(HANDLE hPhysMem, PVOID pBaseAddr, PDWORD pPhysAddr, PDWORD pSize)
113  {
114  NTSTATUS status;
115  PHYSICAL_ADDRESS paAddr;
116 
117  * (DWORD *) pBaseAddr = (DWORD) NULL;
118  paAddr.HighPart = 0;
119  paAddr.LowPart = *pPhysAddr;
120  status = NtMapViewOfSection(hPhysMem, NtCurrentProcess(), (PVOID *) pBaseAddr, 0L,
121  *pSize, &paAddr, pSize, ViewShare, 0, PAGE_READONLY);
122 
123  if (!NT_SUCCESS(status))
124  {
125  hPhysMem = NULL;
126  return FALSE;
127  }
128 
129  *pPhysAddr = paAddr.LowPart;
130  return TRUE;
131  }
132 
133  int UnMapMem(PVOID pBaseAddr)
134  {
135  NTSTATUS status;
136 
137  status = NtUnmapViewOfSection(NtCurrentProcess(), pBaseAddr);
138 
139  if (!NT_SUCCESS(status))
140  {
141  return FALSE;
142  }
143 
144  return TRUE;
145  }
146 
147  static BOOL setPrivilege(LPCTSTR privilegeName, BOOL enable)
148  {
149  HANDLE hToken;
150  HANDLE hCurrentProcess;
151  DWORD err;
152  TOKEN_PRIVILEGES tkprivs;
153 
154  // Get a token for this process.
155  hCurrentProcess = GetCurrentProcess();
156  if(OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
157  {
158  // Get the LUID for the security privilege.
159  LookupPrivilegeValue(NULL, privilegeName, &tkprivs.Privileges[0].Luid);
160  tkprivs.PrivilegeCount = 1; // one privilege to set
161  tkprivs.Privileges[0].Attributes = enable ? SE_PRIVILEGE_ENABLED : 0;
162 
163  // Get the security privilege for this process.
164  AdjustTokenPrivileges(hToken, FALSE, &tkprivs, 0, (PTOKEN_PRIVILEGES)NULL, NULL);
165  }
166  // get the error returned by AdjustTokenPrivileges() or OpenProcessToken()
167  err = GetLastError();
168 
169  return err == ERROR_SUCCESS;
170  }
171 
172  void readPhysicalMemoryMap( HANDLE hPhysMem, u8 *buffer, u64 offset, unsigned int length)
173  {
174  unsigned int totalBytes = 0;
175  unsigned int originalSize = length;
176  unsigned int pageSize = length;
177 
178  if(!hPhysMem)
179  {
180  throw AccessErrorImpl( _("Handle to physical memory was not set or could not be opened.") );
181  }
182 
183  if(0 == buffer)
184  {
185  throw AccessErrorImpl( _("Error accessing buffer.") );
186  }
187 
188  while(totalBytes != originalSize )
189  {
190  DWORD BaseAddr;
191  DWORD PhysAddr = static_cast<DWORD>(offset);
192 
193  // OK, first get the page that has the requested offset. This
194  // function will round-down physaddr to the nearest page boundary,
195  // and the pageSize will change to the size of the page
196  if (!MapMem(hPhysMem, (PVOID) &BaseAddr, &PhysAddr, reinterpret_cast<PDWORD>(&pageSize)))
197  {
198  throw AccessErrorImpl( _("Error mapping physical memory.") );
199  }
200 
201  //Find the index into the page based on requested offset
202  unsigned int index = static_cast<DWORD>(offset) - PhysAddr;
203 
204  // Only continue to copy if the index is within the pageSize and
205  // we still need bytes
206  while( index < pageSize && totalBytes != originalSize)
207  {
208  u64 tmp = BaseAddr + index; // extra tmp to satisfy vc++ /w4
209  buffer[totalBytes] = *reinterpret_cast<u8 *>(tmp);
210  index++;
211  totalBytes++;
212  }
213 
214  u64 tmp = BaseAddr; // extra tmp to satisfy vc++ /w4
215  if (!UnMapMem(reinterpret_cast<PVOID>(tmp)))
216  {
217  throw AccessErrorImpl( _("Error unmapping physical memory."));
218  }
219 
220  // If the inner while loop exited due to the end of the page, we
221  // need to update the offset and the remaining size and map
222  // another page
223  offset = PhysAddr + index;
224  pageSize = originalSize-totalBytes;
225  }
226  }
227 
228  void readPhysicalMemoryDebugSysctl( u8 *buffer, u64 offset, unsigned int length)
229  {
230  MEM_STRUCT mem;
231  NTSTATUS status;
232  ULONG bytesReturned;
233 
234  memset(&mem, 0, sizeof(mem));
235  mem.Addr = (DWORD)offset;
236  mem.pBuf = buffer;
237  mem.NumBytes = (DWORD)length;
238 
240  &mem,
241  sizeof(mem),
242  0,
243  0,
244  &bytesReturned);
245  if (! NT_SUCCESS(status) )
246  {
247  throw AccessErrorImpl( _("Could not use Debug Sysctl to read physical memory."));
248  }
249  }
250 
251 
252  // breaks for requests that span boundaries
253  // breaks for requests of mem at offset > 1M
254  void enumSystemFirmwareTables( u8 *buffer, u64 offset, unsigned int length)
255  {
256  // The following code is added for faster access. Otherewise, it takes libsmbios
257  // approx 3-5minutes to get Service tag etc.
258  if( offset >= 0xC0000 && offset < 0xDFFFF && CBlockBuffer != NULL )
259  {
260  memset( buffer, 0, length );
261  memcpy( buffer, &CBlockBuffer[(DWORD)offset - 0xC0000], length );
262  return;
263  }
264 
265  // The following code is added for faster access. Otherewise, it takes libsmbios
266  // approx 3-5minutes to get Service tag etc.
267  if( offset >= 0xE0000 && offset < 0xFFFFF && EBlockBuffer != NULL ) // optimization
268  {
269  memset( buffer, 0, length );
270  memcpy( buffer, &EBlockBuffer[(DWORD)offset - 0xE0000], length );
271  return;
272  }
273 
274  DWORD iSignature = 0x46000000 | 0x00490000 | 0x00005200 | 0x0000004D ; //FIRM
275 
276  // pass NULL to EnumSystemFirmwareTables. This will return the size needed.
277  unsigned int iBufferSizeNeeded = EnumSystemFirmwareTables( iSignature, NULL, 0 );
278  if( iBufferSizeNeeded > 0 )
279  {
280  DWORD * FirmwareTableEnumBuffer = new DWORD[iBufferSizeNeeded];
281  ULONG i=0;
282  for( i = 0; i < iBufferSizeNeeded; i++ ) // zero out the buffer. Otherwise, the function sometimes fails.
283  {
284  FirmwareTableEnumBuffer[i] = 0;
285  }
286 
287  EnumSystemFirmwareTables( iSignature, FirmwareTableEnumBuffer, iBufferSizeNeeded );
288  DWORD FirmwareTableNameToUse = 0;
289  for( i = 0; i < iBufferSizeNeeded; i++ )
290  {
291  if( FirmwareTableEnumBuffer[i] > 0 && FirmwareTableEnumBuffer[i] <= offset && offset <= FirmwareTableEnumBuffer[i] + 128 * 1024 )
292  {
293  FirmwareTableNameToUse = FirmwareTableEnumBuffer[i];
294  }
295  }
296  delete [] FirmwareTableEnumBuffer;
297 
298  if( FirmwareTableNameToUse == 0 )
299  {
300  throw AccessErrorImpl( _("Could not locate a table which can be used.") );
301  }
302 
303 
304  iBufferSizeNeeded = GetSystemFirmwareTable( iSignature, FirmwareTableNameToUse, NULL, 0 );
305  if( iBufferSizeNeeded > 0 )
306  {
307  u8 * MemoryAtRequestedOffSet = NULL;
308  MemoryAtRequestedOffSet = new u8[iBufferSizeNeeded];
309  if( MemoryAtRequestedOffSet != NULL )
310  {
311  memset( MemoryAtRequestedOffSet, 0, iBufferSizeNeeded );
312  memset( buffer, 0, length );
313  GetSystemFirmwareTable( iSignature, FirmwareTableNameToUse, MemoryAtRequestedOffSet, iBufferSizeNeeded );
314  memcpy( buffer, &MemoryAtRequestedOffSet[(DWORD)offset - FirmwareTableNameToUse], length );
315  if( FirmwareTableNameToUse == 0xC0000 )
316  {
317  CBlockBuffer = MemoryAtRequestedOffSet; // save for later use
318  }
319  else
320  if( FirmwareTableNameToUse == 0xE0000 )
321  {
322  EBlockBuffer = MemoryAtRequestedOffSet; // save for later use
323  }
324  else
325  {
326  delete MemoryAtRequestedOffSet;
327  }
328  }
329  else
330  {
331  throw AccessErrorImpl( _("Failed to allocate memory for Firmware table.") );
332  }
333  }
334  else
335  {
336  throw AccessErrorImpl( _("GetSystemFirmwareTable returned 0 for table length.") );
337  }
338  }
339  else
340  {
341  throw AccessErrorImpl( _("EnumSystemFirmwareTables returned 0 for table size.") );
342  }
343  }
344 
345 
346  MemoryOsSpecific::MemoryOsSpecific( const string )
347  : IMemory()
348  {
349  HANDLE hPhysMem = NULL;
350 
351  if (!LoadNtdllFuncs())
352  {
353  //printf("Could not find functions in dll!\n");
354  //TODO change exception
355  throw AccessErrorImpl( _("Could not load ntdll functions!") );
356  }
357 
358  // DEBUG privs necessary to call systemdebugcontrol
359  // set here to avoid big delays in mem read loops
360  setPrivilege(SE_DEBUG_NAME, 1);
361 
362  hPhysMem = OpenMemAccess();
363 
364  osData = static_cast<HANDLE *>(hPhysMem);
365  }
366 
367  MemoryOsSpecific::~MemoryOsSpecific()
368  {
369  HANDLE hPhysMem = static_cast<HANDLE>(osData);
370 
371  // NEVER THROW FROM DESTRUCTOR!
372  if(hPhysMem)
373  {
374  CloseMemAccess(hPhysMem); // if this fails, something bad happened,
375  } // but there isn't anything we can do about it.
376 
377  delete CBlockBuffer; // safe to delete NULL
378  CBlockBuffer = NULL;
379 
380  delete EBlockBuffer; // safe to delete NULL
381  EBlockBuffer = NULL;
382  }
383 
384  void MemoryOsSpecific::fillBuffer( u8 *buffer, u64 offset, unsigned int length) const
385  {
386 
388  {
389  // Use the newer W2K3 SP1 APIs if they are available.
390  // If we first call readPhysicalMemoryMap and readPhysicalMemoryDebugSysctl
391  // and then call enumSystemFirmwareTables, the new GetSystemFirmwareTable API
392  // of W2K3 SP1 returns invalid data.
393  enumSystemFirmwareTables( buffer, offset, length );
394  }
395  else
396  {
397  HANDLE hPhysMem = static_cast<HANDLE>(osData);
398  if(hPhysMem)
399  {
400  readPhysicalMemoryMap( hPhysMem, buffer, offset, length );
401  }
402  else
403  {
404  readPhysicalMemoryDebugSysctl( buffer, offset, length );
405  }
406  }
407  }
408 
409  u8 MemoryOsSpecific::getByte( u64 offset ) const
410  {
411  u8 value = 0;
412  fillBuffer(&value, offset, 1);
413  return value;
414  }
415 
416  void MemoryOsSpecific::putByte( u64 , u8 ) const
417  {
418  throw smbios::NotImplementedImpl( _("writing to physical memory is not implemented on Windows yet.") );
419  }
420 
421 
422  // yes, I know these do absolutely nothing right now.
423  int MemoryOsSpecific::incReopenHint()
424  {
425  return ++reopenHint;
426  }
427  int MemoryOsSpecific::decReopenHint()
428  {
429  return --reopenHint;
430  }
431 }