1 : // Copyright 2012 Google Inc.
2 : //
3 : // Licensed under the Apache License, Version 2.0 (the "License");
4 : // you may not use this file except in compliance with the License.
5 : // You may obtain a copy of the License at
6 : //
7 : // http://www.apache.org/licenses/LICENSE-2.0
8 : //
9 : // Unless required by applicable law or agreed to in writing, software
10 : // distributed under the License is distributed on an "AS IS" BASIS,
11 : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 : // See the License for the specific language governing permissions and
13 : // limitations under the License.
14 : //
15 : // This file declares the trace::service::ProcessInfo class which
16 : // retrieves and encapsulates the process related information captured
17 : // within a trace file.
18 :
19 : #include "syzygy/trace/service/process_info.h"
20 :
21 : #include <psapi.h>
22 : #include <winternl.h>
23 :
24 : #include "base/logging.h"
25 : #include "base/string_util.h"
26 : #include "sawbuck/common/com_utils.h"
27 :
28 : // From advapi32.dll, but including ntsecapi.h causes conflicting declarations.
29 : extern "C" ULONG NTAPI LsaNtStatusToWinError(__in NTSTATUS status);
30 :
31 : namespace trace {
32 : namespace service {
33 :
34 : namespace {
35 :
36 : typedef NTSTATUS (NTAPI *FuncPtrNtQueryInformationProcess)(
37 : HANDLE ProcessHandle,
38 : DWORD ProcessInformationClass,
39 : PVOID ProcessInformation,
40 : DWORD ProcessInformationLength,
41 : PDWORD ReturnLength);
42 :
43 : // Helper function to get the basic process info for pid/handle.
44 E : bool GetPBI(uint32 pid, HANDLE handle, PROCESS_BASIC_INFORMATION* pbi) {
45 E : DCHECK(pbi != NULL);
46 :
47 E : HMODULE ntdll = GetModuleHandle(L"ntdll.dll");
48 E : if (ntdll == NULL) {
49 i : DWORD error = ::GetLastError();
50 i : LOG(ERROR) << "Failed to get ntdll.dll module handle: " << com::LogWe(error)
51 : << ".";
52 i : return false;
53 : }
54 :
55 : FuncPtrNtQueryInformationProcess query_func =
56 : reinterpret_cast<FuncPtrNtQueryInformationProcess>(
57 E : ::GetProcAddress(ntdll, "NtQueryInformationProcess"));
58 E : if (query_func == NULL) {
59 i : DWORD error = ::GetLastError();
60 i : LOG(ERROR) << "Failed to get NtQueryInformationProcess proc address: "
61 : << com::LogWe(error) << ".";
62 i : return false;
63 : }
64 :
65 E : NTSTATUS status = query_func(handle, 0, pbi, sizeof(*pbi), NULL);
66 E : if (status != 0) {
67 i : LOG(ERROR) << "Failed to query process information for PID=" << pid
68 : << ": " << com::LogWe(::LsaNtStatusToWinError(status)) << ".";
69 i : return false;
70 : }
71 :
72 E : return true;
73 E : }
74 :
75 : // Given a process and an address in its internal memory, returns the maximum
76 : // number of bytes owned by the process starting at that address. This is done
77 : // by looking up how many consecutive pages containing the given address are
78 : // allocated by the given process. Returns true on success (with the number of
79 : // bytes that can be safely read in @p size), false otherwise (@p size set to
80 : // zero).
81 E : bool GetMaximumMemorySize(HANDLE process, void* remote_address, size_t* size) {
82 E : DCHECK(remote_address != NULL);
83 E : DCHECK(size != NULL);
84 :
85 E : *size = 0;
86 :
87 E : MEMORY_BASIC_INFORMATION mem_info = {};
88 : if (VirtualQueryEx(process, remote_address, &mem_info,
89 E : sizeof(mem_info)) == 0) {
90 i : DWORD error = ::GetLastError();
91 i : LOG(ERROR) << "VirtualQueryEx failed: " << com::LogWe(error) << ".";
92 i : return false;
93 : }
94 :
95 : // If the memory contains code or is not readable return an error.
96 E : if (mem_info.Protect == PAGE_NOACCESS || mem_info.Protect == PAGE_EXECUTE) {
97 i : LOG(ERROR) << "Address being dereferenced does not contain readable data.";
98 i : return false;
99 : }
100 :
101 : // Get the size that may be read after the provided address.
102 : size_t region_offset = reinterpret_cast<const uint8*>(remote_address) -
103 E : reinterpret_cast<const uint8*>(mem_info.BaseAddress);
104 E : *size = mem_info.RegionSize - region_offset;
105 :
106 E : return true;
107 E : }
108 :
109 : bool ReadEnvironmentString(HANDLE handle,
110 : const wchar_t* remote_env_string,
111 : size_t max_size,
112 E : std::vector<wchar_t>* environment) {
113 E : DCHECK(environment != NULL);
114 :
115 E : environment->clear();
116 :
117 : const uint8* remote_read_cursor =
118 E : reinterpret_cast<const uint8*>(remote_env_string);
119 E : std::vector<wchar_t> buffer;
120 :
121 E : size_t max_elems = max_size / sizeof(buffer[0]);
122 :
123 : // We use a large buffer to minimize calls to ReadProcessMemory.
124 E : size_t buffer_elems = 128 * 1024;
125 E : if (buffer_elems < max_elems)
126 E : buffer_elems = max_elems;
127 E : buffer.resize(buffer_elems);
128 E : size_t elems_left = max_elems;
129 :
130 E : size_t nulls_in_a_row = 0;
131 E : while (elems_left > 0) {
132 : // Figure out how much data to read in this call.
133 E : size_t elems_to_read = buffer.size();
134 E : if (elems_to_read > elems_left)
135 E : elems_to_read = elems_left;
136 E : size_t bytes_to_read = elems_to_read * sizeof(buffer[0]);
137 :
138 : // Read the next chunk of data.
139 E : SIZE_T bytes_read = 0;
140 : if (!::ReadProcessMemory(handle, remote_read_cursor, &buffer[0],
141 E : bytes_to_read, &bytes_read)) {
142 i : DWORD error = ::GetLastError();
143 :
144 : // It's possible for us to get a failure with ERROR_PARTIAL_COPY if we're
145 : // trying to read pages that are not currently mapped to memory or are
146 : // dirty. Since we do get the number of bytes that were successfully read
147 : // we can silently ignore this. We'll only bail if we're unable to
148 : // advance the read cursor at all.
149 i : if (error != ERROR_PARTIAL_COPY) {
150 i : LOG(ERROR) << "Unable to read environment string: " << com::LogWe(error)
151 : << ".";
152 i : return false;
153 : }
154 : }
155 E : size_t elems_read = bytes_read / sizeof(buffer[0]);
156 E : bytes_read = elems_read * sizeof(buffer[0]);
157 :
158 : // If we got a partial read of zero bytes, we're stuck.
159 E : if (elems_read == 0) {
160 i : LOG(ERROR) << "Unable to read environment string.";
161 i : return false;
162 : }
163 :
164 E : remote_read_cursor += bytes_read;
165 :
166 : // Scan through the buffer looking for the terminating NULLs.
167 E : size_t i = 0;
168 E : for (; i < elems_to_read && nulls_in_a_row < 2; ++i) {
169 E : if (buffer[i] == 0)
170 E : ++nulls_in_a_row;
171 E : else
172 E : nulls_in_a_row = 0;
173 E : }
174 :
175 E : environment->insert(environment->end(), buffer.begin(), buffer.begin() + i);
176 :
177 E : if (nulls_in_a_row == 2)
178 E : return true;
179 i : }
180 :
181 i : LOG(ERROR) << "The environment appears to be malformed.";
182 :
183 i : return false;
184 E : }
185 :
186 : // Extract the exe path and command line for the process given by pid/handle.
187 : // Note that there are other ways to retrieve the exe path, but since this
188 : // function will already be spelunking in the same area (to get the command
189 : // line) we just get the exe path while we're there.
190 : bool GetProcessStrings(uint32 pid,
191 : HANDLE handle,
192 : FilePath* exe_path,
193 : std::wstring* cmd_line,
194 E : std::vector<wchar_t>* environment) {
195 E : DCHECK(exe_path != NULL);
196 E : DCHECK(cmd_line != NULL);
197 E : DCHECK(environment != NULL);
198 :
199 : // Fetch the basic process information.
200 E : PROCESS_BASIC_INFORMATION pbi = {};
201 E : if (!GetPBI(pid, handle, &pbi)) {
202 i : return false;
203 : }
204 :
205 : // TODO(rogerm): Validate that the target process has the same bitness as
206 : // the querying process; otherwise, the following won't work.
207 :
208 : // Setup the variables that we'll use later.
209 E : uint8* peb_base_address = reinterpret_cast<uint8*>(pbi.PebBaseAddress);
210 E : uint8* user_proc_params = NULL;
211 E : UNICODE_STRING string_value[2] = {};
212 :
213 : // Get the address of the process paramters.
214 E : const size_t kProcessParamOffset = FIELD_OFFSET(PEB, ProcessParameters);
215 : if (!::ReadProcessMemory(handle, peb_base_address + kProcessParamOffset,
216 : &user_proc_params, sizeof(user_proc_params),
217 E : NULL)) {
218 i : DWORD error = ::GetLastError();
219 i : LOG(ERROR) << "Failed to read process parameter pointer for PID=" << pid
220 : << " " << com::LogWe(error) << ".";
221 i : return false;
222 : }
223 :
224 : // Get the image path name and command line UNICODE_STRING structures.
225 : // string_value[0] will be the image path name, and string_value[1] will
226 : // be the command line.
227 : const size_t kImagePathNameOffset =
228 E : FIELD_OFFSET(RTL_USER_PROCESS_PARAMETERS, ImagePathName);
229 : if (!::ReadProcessMemory(handle, user_proc_params + kImagePathNameOffset,
230 E : &string_value[0], sizeof(string_value), NULL)) {
231 i : DWORD error = ::GetLastError();
232 i : LOG(ERROR) << "Failed to read the process parameters for PID=" << pid
233 : << ": " << com::LogWe(error) << ".";
234 i : return false;
235 : }
236 :
237 : // Read the image path name.
238 E : std::wstring temp_exe_path;
239 E : size_t num_chars_in_path = string_value[0].Length / sizeof(wchar_t);
240 : if (!::ReadProcessMemory(handle, string_value[0].Buffer,
241 : WriteInto(&temp_exe_path, num_chars_in_path + 1),
242 E : string_value[0].Length, NULL)) {
243 i : DWORD error = ::GetLastError();
244 i : LOG(ERROR) << "Failed to read the exe path for PID=" << pid
245 : << ": " << com::LogWe(error) << ".";
246 i : return false;
247 : }
248 E : *exe_path = FilePath(temp_exe_path);
249 :
250 : // Read the command line.
251 E : size_t num_chars_in_cmd_line = string_value[1].Length / sizeof(wchar_t);
252 : if (!::ReadProcessMemory(handle, string_value[1].Buffer,
253 : WriteInto(cmd_line, num_chars_in_cmd_line + 1),
254 E : string_value[1].Length, NULL)) {
255 i : DWORD error = ::GetLastError();
256 i : LOG(ERROR) << "Failed to read the command line for PID=" << pid
257 : << ": " << com::LogWe(error) << ".";
258 i : return false;
259 : }
260 :
261 : // Get the environment string. Note that this a pointer into a remote process
262 : // so we can't directly dereference it. This is not documented directly in
263 : // winternl.h, but it is documented here: http://goto.google.com/win-proc-env
264 E : const size_t kEnvironmentStringOffset = 0x48;
265 E : wchar_t* remote_env_string = NULL;
266 : if (!::ReadProcessMemory(handle, user_proc_params + kEnvironmentStringOffset,
267 : &remote_env_string, sizeof(remote_env_string),
268 E : NULL)) {
269 i : DWORD error = ::GetLastError();
270 i : LOG(ERROR) << "Failed to read environment variable string for PID=" << pid
271 : << ": " << com::LogWe(error) << ".";
272 i : return false;
273 : }
274 :
275 : // Get an upper bound on the size of the environment string. It doesn't have
276 : // the size encoded within it directly, and this gives us an upper bound by
277 : // determining how much data the remote process owns starting at the given
278 : // location.
279 E : size_t max_size = 0;
280 E : if (!GetMaximumMemorySize(handle, remote_env_string, &max_size))
281 i : return false;
282 :
283 : // Finally, read the environment string.
284 E : if (!ReadEnvironmentString(handle, remote_env_string, max_size, environment))
285 i : return false;
286 :
287 E : return true;
288 E : }
289 :
290 : // Gets the NT headers of the running process.
291 : bool GetProcessNtHeaders(
292 E : uint32 pid, HANDLE handle, IMAGE_NT_HEADERS* nt_headers) {
293 E : DCHECK(nt_headers != NULL);
294 E : HMODULE module = 0;
295 E : DWORD dummy = 0;
296 :
297 : // The first module returned by the enumeration will be the executable. So
298 : // we only need to ask for one HMODULE.
299 E : if (!::EnumProcessModules(handle, &module, sizeof(module), &dummy)) {
300 i : DWORD error = ::GetLastError();
301 i : LOG(ERROR) << "Failed to get module handle for PID=" << pid
302 : << ": " << com::LogWe(error) << ".";
303 i : return false;
304 : }
305 :
306 : // We now have enough information get the module info for the executable.
307 E : MODULEINFO info = {};
308 E : if (!::GetModuleInformation(handle, module, &info, sizeof(info))) {
309 i : DWORD error = ::GetLastError();
310 i : LOG(ERROR) << "Failed to get module info for PID=" << pid
311 : << ": " << com::LogWe(error) << ".";
312 i : return false;
313 : }
314 :
315 E : uint8* base_addr = reinterpret_cast<uint8*>(info.lpBaseOfDll);
316 :
317 : // Get the DOS header.
318 : IMAGE_DOS_HEADER dos_header;
319 E : uint8* addr_to_read = base_addr;
320 E : SIZE_T bytes_to_read = sizeof(IMAGE_DOS_HEADER);
321 E : SIZE_T bytes_read = 0;
322 : if (!::ReadProcessMemory(handle, addr_to_read, &dos_header,
323 : bytes_to_read, &bytes_read) ||
324 E : bytes_read != bytes_to_read) {
325 i : DWORD error = ::GetLastError();
326 i : LOG(ERROR) << "Failed to read DOS header for PID=" << pid
327 : << " " << com::LogWe(error) << ".";
328 i : return false;
329 : }
330 :
331 : // Get the NT headers.
332 E : addr_to_read = base_addr + dos_header.e_lfanew;
333 E : bytes_to_read = sizeof(IMAGE_NT_HEADERS);
334 E : bytes_read = 0;
335 : if (!::ReadProcessMemory(handle, addr_to_read, nt_headers,
336 : bytes_to_read, &bytes_read) ||
337 E : bytes_read != bytes_to_read) {
338 i : DWORD error = ::GetLastError();
339 i : LOG(ERROR) << "Failed to read NT headers for PID=" << pid
340 : << " " << com::LogWe(error) << ".";
341 i : return false;
342 : }
343 :
344 E : return true;
345 E : }
346 :
347 : // Gets the executable module information for the process given by pid/handle.
348 : bool GetMemoryRange(uint32 pid, HANDLE handle, uint32* base_addr,
349 E : uint32* module_size) {
350 E : DCHECK(base_addr != NULL);
351 E : DCHECK(module_size != NULL);
352 :
353 E : HMODULE module = 0;
354 E : DWORD dummy = 0;
355 :
356 : // The first module returned by the enumeration will be the executable. So
357 : // we only need to ask for one HMODULE.
358 E : if (!::EnumProcessModules(handle, &module, sizeof(module), &dummy)) {
359 i : DWORD error = ::GetLastError();
360 i : LOG(ERROR) << "Failed to get module handle for PID=" << pid
361 : << ": " << com::LogWe(error) << ".";
362 i : return false;
363 : }
364 :
365 : // We now have enough information get the module info for the executable.
366 E : MODULEINFO info = {};
367 E : if (!::GetModuleInformation(handle, module, &info, sizeof(info))) {
368 i : DWORD error = ::GetLastError();
369 i : LOG(ERROR) << "Failed to get module info for PID=" << pid
370 : << ": " << com::LogWe(error) << ".";
371 i : return false;
372 : }
373 :
374 E : *base_addr = reinterpret_cast<uint32>(info.lpBaseOfDll);
375 E : *module_size = info.SizeOfImage;
376 :
377 E : return true;
378 E : }
379 :
380 : } // namespace
381 :
382 : ProcessInfo::ProcessInfo()
383 : : process_id(0),
384 : exe_base_address(0),
385 : exe_image_size(0),
386 : exe_checksum(0),
387 E : exe_time_date_stamp(0) {
388 E : ::memset(&os_version_info, 0, sizeof(os_version_info));
389 E : ::memset(&system_info, 0, sizeof(system_info));
390 E : ::memset(&memory_status, 0, sizeof(memory_status));
391 E : }
392 :
393 E : ProcessInfo::~ProcessInfo() {
394 E : }
395 :
396 E : void ProcessInfo::Reset() {
397 E : process_handle.Close();
398 E : process_id = 0;
399 E : executable_path.clear();
400 E : command_line.clear();
401 E : environment.clear();
402 E : ::memset(&os_version_info, 0, sizeof(os_version_info));
403 E : ::memset(&system_info, 0, sizeof(system_info));
404 E : ::memset(&memory_status, 0, sizeof(memory_status));
405 E : exe_base_address = 0;
406 E : exe_image_size = 0;
407 E : exe_checksum = 0;
408 E : exe_time_date_stamp = 0;
409 E : }
410 :
411 E : bool ProcessInfo::Initialize(uint32 pid) {
412 : // Open the process given by pid. We need a process handle that (1) remains
413 : // valid over time (2) lets us query for info about the process, and (3)
414 : // allows us to read the command line from the process memory.
415 : const DWORD kFlags =
416 E : PROCESS_DUP_HANDLE | PROCESS_QUERY_INFORMATION | PROCESS_VM_READ;
417 E : process_handle.Set(::OpenProcess(kFlags, FALSE, pid));
418 E : if (!process_handle.IsValid()) {
419 i : DWORD error = ::GetLastError();
420 i : LOG(ERROR) << "Failed to open PID=" << pid << " " << com::LogWe(error)
421 : << ".";
422 i : Reset();
423 i : return false;
424 : }
425 :
426 E : process_id = pid;
427 :
428 : // Get the executable path, command line and environment string.
429 : if (!GetProcessStrings(process_id, process_handle,
430 E : &executable_path, &command_line, &environment)) {
431 i : Reset();
432 i : return false;
433 : }
434 :
435 : // Get the operating system and hardware information.
436 E : os_version_info.dwOSVersionInfoSize = sizeof(os_version_info);
437 : if (!::GetVersionEx(
438 E : reinterpret_cast<OSVERSIONINFO*>(&os_version_info))) {
439 i : DWORD error = ::GetLastError();
440 i : LOG(ERROR) << "Failed to get OS version information: "
441 : << com::LogWe(error) << ".";
442 i : Reset();
443 i : return false;
444 : }
445 :
446 E : ::GetSystemInfo(&system_info);
447 :
448 E : memory_status.dwLength = sizeof(memory_status);
449 E : if (!::GlobalMemoryStatusEx(&memory_status)) {
450 i : DWORD error = ::GetLastError();
451 i : LOG(ERROR) << "Failed to get global memory status: "
452 : << com::LogWe(error) << ".";
453 i : Reset();
454 i : return false;
455 : }
456 :
457 : // Get the base address and module size.
458 : if (!GetMemoryRange(process_id, process_handle,
459 E : &exe_base_address, &exe_image_size)) {
460 i : Reset();
461 i : return false;
462 : }
463 :
464 : // Get the headers for the running image and use these to populate the
465 : // checksum and time-date stamp.
466 : IMAGE_NT_HEADERS nt_headers;
467 E : if (!GetProcessNtHeaders(process_id, process_handle, &nt_headers)) {
468 i : Reset();
469 i : return false;
470 : }
471 E : exe_checksum = nt_headers.OptionalHeader.CheckSum;
472 E : exe_time_date_stamp = nt_headers.FileHeader.TimeDateStamp;
473 :
474 E : return true;
475 E : }
476 :
477 : } // namespace trace::service
478 : } // namespace trace
|