1 : // Copyright 2012 Google Inc. All Rights Reserved.
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 : #include "syzygy/common/align.h"
28 :
29 : // From advapi32.dll, but including ntsecapi.h causes conflicting declarations.
30 : extern "C" ULONG NTAPI LsaNtStatusToWinError(__in NTSTATUS status);
31 :
32 : namespace trace {
33 : namespace service {
34 :
35 : namespace {
36 :
37 : typedef NTSTATUS (NTAPI *FuncPtrNtQueryInformationProcess)(
38 : HANDLE ProcessHandle,
39 : DWORD ProcessInformationClass,
40 : PVOID ProcessInformation,
41 : DWORD ProcessInformationLength,
42 : PDWORD ReturnLength);
43 :
44 : // Helper function to get the basic process info for pid/handle.
45 E : bool GetPBI(uint32 pid, HANDLE handle, PROCESS_BASIC_INFORMATION* pbi) {
46 E : DCHECK(pbi != NULL);
47 :
48 E : HMODULE ntdll = GetModuleHandle(L"ntdll.dll");
49 E : if (ntdll == NULL) {
50 i : DWORD error = ::GetLastError();
51 i : LOG(ERROR) << "Failed to get ntdll.dll module handle: " << com::LogWe(error)
52 : << ".";
53 i : return false;
54 : }
55 :
56 : FuncPtrNtQueryInformationProcess query_func =
57 : reinterpret_cast<FuncPtrNtQueryInformationProcess>(
58 E : ::GetProcAddress(ntdll, "NtQueryInformationProcess"));
59 E : if (query_func == NULL) {
60 i : DWORD error = ::GetLastError();
61 i : LOG(ERROR) << "Failed to get NtQueryInformationProcess proc address: "
62 : << com::LogWe(error) << ".";
63 i : return false;
64 : }
65 :
66 E : NTSTATUS status = query_func(handle, 0, pbi, sizeof(*pbi), NULL);
67 E : if (status != 0) {
68 i : LOG(ERROR) << "Failed to query process information for PID=" << pid
69 : << ": " << com::LogWe(::LsaNtStatusToWinError(status)) << ".";
70 i : return false;
71 : }
72 :
73 E : return true;
74 E : }
75 :
76 : bool ReadEnvironmentString(HANDLE handle,
77 : size_t page_size,
78 : const wchar_t* remote_env_string,
79 E : std::vector<wchar_t>* environment) {
80 E : DCHECK_LT(0u, page_size);
81 E : DCHECK(common::IsPowerOfTwo(page_size));
82 E : DCHECK(remote_env_string != NULL);
83 E : DCHECK(environment != NULL);
84 :
85 E : environment->clear();
86 :
87 E : std::vector<uint8> vector(page_size);
88 E : uint8* buffer = &vector.at(0);
89 E : const wchar_t* wbuffer = reinterpret_cast<const wchar_t*>(buffer);
90 : const uint8* remote_cursor =
91 E : reinterpret_cast<const uint8*>(remote_env_string);
92 : const uint8* next_page = reinterpret_cast<const uint8*>(
93 : common::AlignUp(reinterpret_cast<size_t>(remote_cursor),
94 E : page_size));
95 :
96 E : size_t nulls_in_a_row = 0;
97 E : while (true) {
98 E : DCHECK_GE(next_page, remote_cursor);
99 E : if (remote_cursor == next_page)
100 E : next_page += page_size;
101 :
102 : // Determine the maximum amount of data to read. We read a page at a
103 : // time so as to avoid going off the end of addressable memory, something
104 : // that ReadProcessMemory really hates (it will return zero bytes read and
105 : // ERROR_PARTIAL_COPY).
106 E : size_t bytes_to_read = next_page - remote_cursor;
107 E : DCHECK_EQ(0u, bytes_to_read % sizeof(wbuffer[0]));
108 :
109 E : SIZE_T bytes_read = 0;
110 : if (!::ReadProcessMemory(handle, remote_cursor, buffer, bytes_to_read,
111 E : &bytes_read)) {
112 i : DWORD error = ::GetLastError();
113 i : LOG(ERROR) << "Failed to read environment string: " << com::LogWe(error)
114 : << ".";
115 i : return false;
116 : }
117 E : DCHECK_LT(0u, bytes_read);
118 E : size_t elems_read = bytes_read / sizeof(wbuffer[0]);
119 E : size_t bytes_used = elems_read * sizeof(wbuffer[0]);
120 E : remote_cursor += bytes_used;
121 :
122 : // Look for the terminating double NULL.
123 E : for (size_t i = 0; i < elems_read; ++i) {
124 E : if (wbuffer[i] == 0) {
125 E : if (++nulls_in_a_row == 2) {
126 : // We found the terminating double NULL. Append the end of the
127 : // string and we're done.
128 E : environment->insert(environment->end(), wbuffer, wbuffer + i + 1);
129 E : return true;
130 : }
131 E : } else {
132 E : nulls_in_a_row = 0;
133 : }
134 E : }
135 :
136 : // If we get here then the entire buffer we just read needs to be appended
137 : // to the environment string.
138 E : environment->insert(environment->end(), wbuffer, wbuffer + elems_read);
139 E : }
140 :
141 i : NOTREACHED();
142 i : return false;
143 E : }
144 :
145 : // Extract the exe path and command line for the process given by pid/handle.
146 : // Note that there are other ways to retrieve the exe path, but since this
147 : // function will already be spelunking in the same area (to get the command
148 : // line) we just get the exe path while we're there.
149 : bool GetProcessStrings(uint32 pid,
150 : HANDLE handle,
151 : size_t page_size,
152 : base::FilePath* exe_path,
153 : std::wstring* cmd_line,
154 E : std::vector<wchar_t>* environment) {
155 E : DCHECK(exe_path != NULL);
156 E : DCHECK(cmd_line != NULL);
157 E : DCHECK(environment != NULL);
158 :
159 : // Fetch the basic process information.
160 E : PROCESS_BASIC_INFORMATION pbi = {};
161 E : if (!GetPBI(pid, handle, &pbi)) {
162 i : return false;
163 : }
164 :
165 : // TODO(rogerm): Validate that the target process has the same bitness as
166 : // the querying process; otherwise, the following won't work.
167 :
168 : // Setup the variables that we'll use later.
169 E : uint8* peb_base_address = reinterpret_cast<uint8*>(pbi.PebBaseAddress);
170 E : uint8* user_proc_params = NULL;
171 E : UNICODE_STRING string_value[2] = {};
172 :
173 : // Get the address of the process parameters.
174 E : const size_t kProcessParamOffset = FIELD_OFFSET(PEB, ProcessParameters);
175 : if (!::ReadProcessMemory(handle, peb_base_address + kProcessParamOffset,
176 E : &user_proc_params, sizeof(user_proc_params), NULL)) {
177 i : DWORD error = ::GetLastError();
178 i : LOG(ERROR) << "Failed to read process parameter pointer for PID=" << pid
179 : << " " << com::LogWe(error) << ".";
180 i : return false;
181 : }
182 :
183 : // Get the image path name and command line UNICODE_STRING structures.
184 : // string_value[0] will be the image path name, and string_value[1] will
185 : // be the command line.
186 : const size_t kImagePathNameOffset =
187 E : FIELD_OFFSET(RTL_USER_PROCESS_PARAMETERS, ImagePathName);
188 : if (!::ReadProcessMemory(handle, user_proc_params + kImagePathNameOffset,
189 E : &string_value[0], sizeof(string_value), NULL)) {
190 i : DWORD error = ::GetLastError();
191 i : LOG(ERROR) << "Failed to read the process parameters for PID=" << pid
192 : << ": " << com::LogWe(error) << ".";
193 i : return false;
194 : }
195 :
196 : // Read the image path name.
197 E : std::wstring temp_exe_path;
198 E : size_t num_chars_in_path = string_value[0].Length / sizeof(wchar_t);
199 : if (!::ReadProcessMemory(handle, string_value[0].Buffer,
200 : WriteInto(&temp_exe_path, num_chars_in_path + 1),
201 E : string_value[0].Length, NULL)) {
202 i : DWORD error = ::GetLastError();
203 i : LOG(ERROR) << "Failed to read the exe path for PID=" << pid
204 : << ": " << com::LogWe(error) << ".";
205 i : return false;
206 : }
207 E : *exe_path = base::FilePath(temp_exe_path);
208 :
209 : // Read the command line.
210 E : size_t num_chars_in_cmd_line = string_value[1].Length / sizeof(wchar_t);
211 : if (!::ReadProcessMemory(handle, string_value[1].Buffer,
212 : WriteInto(cmd_line, num_chars_in_cmd_line + 1),
213 E : string_value[1].Length, NULL)) {
214 i : DWORD error = ::GetLastError();
215 i : LOG(ERROR) << "Failed to read the command line for PID=" << pid
216 : << ": " << com::LogWe(error) << ".";
217 i : return false;
218 : }
219 :
220 : // Get the environment string. Note that this a pointer into a remote process
221 : // so we can't directly dereference it. This is not documented directly in
222 : // winternl.h, but it is documented here: http://goto.google.com/win-proc-env
223 E : const size_t kEnvironmentStringOffset = 0x48;
224 E : wchar_t* remote_env_string = NULL;
225 : if (!::ReadProcessMemory(handle, user_proc_params + kEnvironmentStringOffset,
226 : &remote_env_string, sizeof(remote_env_string),
227 E : NULL)) {
228 i : DWORD error = ::GetLastError();
229 i : LOG(ERROR) << "Failed to read environment variable string for PID=" << pid
230 : << ": " << com::LogWe(error) << ".";
231 i : return false;
232 : }
233 :
234 : // Finally, read the environment string.
235 : if (!ReadEnvironmentString(handle, page_size, remote_env_string,
236 E : environment)) {
237 i : return false;
238 : }
239 :
240 E : return true;
241 E : }
242 :
243 : // Gets the NT headers of the running process.
244 : bool GetProcessNtHeaders(
245 E : uint32 pid, HANDLE handle, IMAGE_NT_HEADERS* nt_headers) {
246 E : DCHECK(nt_headers != NULL);
247 E : HMODULE module = 0;
248 E : DWORD dummy = 0;
249 :
250 : // The first module returned by the enumeration will be the executable. So
251 : // we only need to ask for one HMODULE.
252 E : if (!::EnumProcessModules(handle, &module, sizeof(module), &dummy)) {
253 i : DWORD error = ::GetLastError();
254 i : LOG(ERROR) << "Failed to get module handle for PID=" << pid
255 : << ": " << com::LogWe(error) << ".";
256 i : return false;
257 : }
258 :
259 : // We now have enough information get the module info for the executable.
260 E : MODULEINFO info = {};
261 E : if (!::GetModuleInformation(handle, module, &info, sizeof(info))) {
262 i : DWORD error = ::GetLastError();
263 i : LOG(ERROR) << "Failed to get module info for PID=" << pid
264 : << ": " << com::LogWe(error) << ".";
265 i : return false;
266 : }
267 :
268 E : uint8* base_addr = reinterpret_cast<uint8*>(info.lpBaseOfDll);
269 :
270 : // Get the DOS header.
271 : IMAGE_DOS_HEADER dos_header;
272 E : uint8* addr_to_read = base_addr;
273 E : SIZE_T bytes_to_read = sizeof(IMAGE_DOS_HEADER);
274 E : SIZE_T bytes_read = 0;
275 : if (!::ReadProcessMemory(handle, addr_to_read, &dos_header, bytes_to_read,
276 : &bytes_read) ||
277 E : bytes_read != bytes_to_read) {
278 i : DWORD error = ::GetLastError();
279 i : LOG(ERROR) << "Failed to read DOS header for PID=" << pid
280 : << " " << com::LogWe(error) << ".";
281 i : return false;
282 : }
283 :
284 : // Get the NT headers.
285 E : addr_to_read = base_addr + dos_header.e_lfanew;
286 E : bytes_to_read = sizeof(IMAGE_NT_HEADERS);
287 E : bytes_read = 0;
288 : if (!::ReadProcessMemory(handle, addr_to_read, nt_headers, bytes_to_read,
289 : &bytes_read) ||
290 E : bytes_read != bytes_to_read) {
291 i : DWORD error = ::GetLastError();
292 i : LOG(ERROR) << "Failed to read NT headers for PID=" << pid
293 : << " " << com::LogWe(error) << ".";
294 i : return false;
295 : }
296 :
297 E : return true;
298 E : }
299 :
300 : } // namespace
301 :
302 : ProcessInfo::ProcessInfo()
303 : : process_id(0),
304 : exe_base_address(0),
305 : exe_image_size(0),
306 : exe_checksum(0),
307 E : exe_time_date_stamp(0) {
308 E : ::memset(&os_version_info, 0, sizeof(os_version_info));
309 E : ::memset(&system_info, 0, sizeof(system_info));
310 E : ::memset(&memory_status, 0, sizeof(memory_status));
311 E : }
312 :
313 E : ProcessInfo::~ProcessInfo() {
314 E : }
315 :
316 E : void ProcessInfo::Reset() {
317 E : process_handle.Close();
318 E : process_id = 0;
319 E : executable_path.clear();
320 E : command_line.clear();
321 E : environment.clear();
322 E : ::memset(&os_version_info, 0, sizeof(os_version_info));
323 E : ::memset(&system_info, 0, sizeof(system_info));
324 E : ::memset(&memory_status, 0, sizeof(memory_status));
325 E : exe_base_address = 0;
326 E : exe_image_size = 0;
327 E : exe_checksum = 0;
328 E : exe_time_date_stamp = 0;
329 E : }
330 :
331 E : bool ProcessInfo::Initialize(uint32 pid) {
332 : // TODO(chrisha): This whole mechanism is racy by its very nature, as it
333 : // reads memory from a remote process that is running, and which may be
334 : // changing the things being read. In practice this has not proved to be
335 : // a problem as we are typically running under the loader lock, but this
336 : // is not true when running instrumented EXEs. Long term it would be good
337 : // to make this run in the instrumented process and have it shuttle the
338 : // data across in the first buffer.
339 :
340 : // Open the process given by pid. We need a process handle that (1) remains
341 : // valid over time (2) lets us query for info about the process, and (3)
342 : // allows us to read the command line from the process memory.
343 : const DWORD kFlags =
344 E : PROCESS_DUP_HANDLE | PROCESS_QUERY_INFORMATION | PROCESS_VM_READ;
345 :
346 E : process_handle.Set(::OpenProcess(kFlags, FALSE, pid));
347 :
348 E : if (!process_handle.IsValid()) {
349 i : DWORD error = ::GetLastError();
350 i : LOG(ERROR) << "Failed to open PID=" << pid << " " << com::LogWe(error)
351 : << ".";
352 i : Reset();
353 i : return false;
354 : }
355 :
356 E : process_id = pid;
357 :
358 E : ::GetSystemInfo(&system_info);
359 :
360 : // Get the executable path, command line and environment string.
361 : if (!GetProcessStrings(process_id, process_handle, system_info.dwPageSize,
362 E : &executable_path, &command_line, &environment)) {
363 i : Reset();
364 i : return false;
365 : }
366 :
367 : // Get the operating system and hardware information.
368 E : os_version_info.dwOSVersionInfoSize = sizeof(os_version_info);
369 : if (!::GetVersionEx(
370 E : reinterpret_cast<OSVERSIONINFO*>(&os_version_info))) {
371 i : DWORD error = ::GetLastError();
372 i : LOG(ERROR) << "Failed to get OS version information: "
373 : << com::LogWe(error) << ".";
374 i : Reset();
375 i : return false;
376 : }
377 :
378 E : memory_status.dwLength = sizeof(memory_status);
379 E : if (!::GlobalMemoryStatusEx(&memory_status)) {
380 i : DWORD error = ::GetLastError();
381 i : LOG(ERROR) << "Failed to get global memory status: "
382 : << com::LogWe(error) << ".";
383 i : Reset();
384 i : return false;
385 : }
386 :
387 : // Get the headers for the running image and use these to populate various
388 : // fields.
389 : IMAGE_NT_HEADERS nt_headers;
390 E : if (!GetProcessNtHeaders(process_id, process_handle, &nt_headers)) {
391 i : Reset();
392 i : return false;
393 : }
394 E : exe_base_address = nt_headers.OptionalHeader.ImageBase;
395 E : exe_image_size = nt_headers.OptionalHeader.SizeOfImage;
396 E : exe_checksum = nt_headers.OptionalHeader.CheckSum;
397 E : exe_time_date_stamp = nt_headers.FileHeader.TimeDateStamp;
398 :
399 E : return true;
400 E : }
401 :
402 : } // namespace service
403 : } // namespace trace
|