1 | // Licensed to the .NET Foundation under one or more agreements. |
2 | // The .NET Foundation licenses this file to you under the MIT license. |
3 | // See the LICENSE file in the project root for more information. |
4 | |
5 | /*++ |
6 | |
7 | |
8 | |
9 | Module Name: |
10 | |
11 | process.cpp |
12 | |
13 | Abstract: |
14 | |
15 | Implementation of process object and functions related to processes. |
16 | |
17 | |
18 | |
19 | --*/ |
20 | |
21 | #include "pal/dbgmsg.h" |
22 | SET_DEFAULT_DEBUG_CHANNEL(PROCESS); // some headers have code with asserts, so do this first |
23 | |
24 | #include "pal/procobj.hpp" |
25 | #include "pal/thread.hpp" |
26 | #include "pal/file.hpp" |
27 | #include "pal/handlemgr.hpp" |
28 | #include "pal/module.h" |
29 | #include "procprivate.hpp" |
30 | #include "pal/palinternal.h" |
31 | #include "pal/process.h" |
32 | #include "pal/init.h" |
33 | #include "pal/critsect.h" |
34 | #include "pal/debug.h" |
35 | #include "pal/utils.h" |
36 | #include "pal/environ.h" |
37 | #include "pal/virtual.h" |
38 | #include "pal/stackstring.hpp" |
39 | |
40 | #include <errno.h> |
41 | #if HAVE_POLL |
42 | #include <poll.h> |
43 | #else |
44 | #include "pal/fakepoll.h" |
45 | #endif // HAVE_POLL |
46 | |
47 | #include <unistd.h> |
48 | #include <sys/mman.h> |
49 | #include <sys/types.h> |
50 | #include <sys/stat.h> |
51 | #include <signal.h> |
52 | #if HAVE_PRCTL_H |
53 | #include <sys/prctl.h> |
54 | #include <sys/syscall.h> |
55 | #endif |
56 | #include <sys/wait.h> |
57 | #include <sys/time.h> |
58 | #include <sys/resource.h> |
59 | #include <debugmacrosext.h> |
60 | #include <semaphore.h> |
61 | #include <stdint.h> |
62 | #include <dlfcn.h> |
63 | |
64 | #ifdef __linux__ |
65 | #include <sys/syscall.h> // __NR_membarrier |
66 | // Ensure __NR_membarrier is defined for portable builds. |
67 | # if !defined(__NR_membarrier) |
68 | # if defined(__amd64__) |
69 | # define __NR_membarrier 324 |
70 | # elif defined(__i386__) |
71 | # define __NR_membarrier 375 |
72 | # elif defined(__arm__) |
73 | # define __NR_membarrier 389 |
74 | # elif defined(__aarch64__) |
75 | # define __NR_membarrier 283 |
76 | # elif |
77 | # error Unknown architecture |
78 | # endif |
79 | # endif |
80 | #endif |
81 | |
82 | #ifdef __APPLE__ |
83 | #include <sys/sysctl.h> |
84 | #include <sys/posix_sem.h> |
85 | #endif |
86 | |
87 | #ifdef __NetBSD__ |
88 | #include <sys/cdefs.h> |
89 | #include <sys/param.h> |
90 | #include <sys/sysctl.h> |
91 | #include <kvm.h> |
92 | #endif |
93 | |
94 | extern char *g_szCoreCLRPath; |
95 | |
96 | using namespace CorUnix; |
97 | |
98 | CObjectType CorUnix::otProcess( |
99 | otiProcess, |
100 | NULL, // No cleanup routine |
101 | NULL, // No initialization routine |
102 | 0, // No immutable data |
103 | NULL, // No immutable data copy routine |
104 | NULL, // No immutable data cleanup routine |
105 | sizeof(CProcProcessLocalData), |
106 | NULL, // No process local data cleanup routine |
107 | 0, // No shared data |
108 | PROCESS_ALL_ACCESS, |
109 | CObjectType::SecuritySupported, |
110 | CObjectType::SecurityInfoNotPersisted, |
111 | CObjectType::UnnamedObject, |
112 | CObjectType::CrossProcessDuplicationAllowed, |
113 | CObjectType::WaitableObject, |
114 | CObjectType::SingleTransitionObject, |
115 | CObjectType::ThreadReleaseHasNoSideEffects, |
116 | CObjectType::NoOwner |
117 | ); |
118 | |
119 | // |
120 | // Helper membarrier function |
121 | // |
122 | #ifdef __NR_membarrier |
123 | # define membarrier(...) syscall(__NR_membarrier, __VA_ARGS__) |
124 | #else |
125 | # define membarrier(...) -ENOSYS |
126 | #endif |
127 | |
128 | enum membarrier_cmd |
129 | { |
130 | MEMBARRIER_CMD_QUERY = 0, |
131 | MEMBARRIER_CMD_GLOBAL = (1 << 0), |
132 | MEMBARRIER_CMD_GLOBAL_EXPEDITED = (1 << 1), |
133 | MEMBARRIER_CMD_REGISTER_GLOBAL_EXPEDITED = (1 << 2), |
134 | MEMBARRIER_CMD_PRIVATE_EXPEDITED = (1 << 3), |
135 | MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED = (1 << 4), |
136 | MEMBARRIER_CMD_PRIVATE_EXPEDITED_SYNC_CORE = (1 << 5), |
137 | MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED_SYNC_CORE = (1 << 6) |
138 | }; |
139 | |
140 | // |
141 | // Tracks if the OS supports FlushProcessWriteBuffers using membarrier |
142 | // |
143 | static int s_flushUsingMemBarrier = 0; |
144 | |
145 | // |
146 | // Helper memory page used by the FlushProcessWriteBuffers |
147 | // |
148 | static int* s_helperPage = 0; |
149 | |
150 | // |
151 | // Mutex to make the FlushProcessWriteBuffersMutex thread safe |
152 | // |
153 | pthread_mutex_t flushProcessWriteBuffersMutex; |
154 | |
155 | CAllowedObjectTypes aotProcess(otiProcess); |
156 | |
157 | // |
158 | // The representative IPalObject for this process |
159 | // |
160 | IPalObject* CorUnix::g_pobjProcess; |
161 | |
162 | // |
163 | // Critical section that protects process data (e.g., the |
164 | // list of active threads)/ |
165 | // |
166 | CRITICAL_SECTION g_csProcess; |
167 | |
168 | // |
169 | // List and count of active threads |
170 | // |
171 | CPalThread* CorUnix::pGThreadList; |
172 | DWORD g_dwThreadCount; |
173 | |
174 | // |
175 | // The command line and app name for the process |
176 | // |
177 | LPWSTR g_lpwstrCmdLine = NULL; |
178 | LPWSTR g_lpwstrAppDir = NULL; |
179 | |
180 | // Thread ID of thread that has started the ExitProcess process |
181 | Volatile<LONG> terminator = 0; |
182 | |
183 | // Process and session ID of this process. |
184 | DWORD gPID = (DWORD) -1; |
185 | DWORD gSID = (DWORD) -1; |
186 | |
187 | // Application group ID for this process |
188 | #ifdef __APPLE__ |
189 | LPCSTR gApplicationGroupId = nullptr; |
190 | int gApplicationGroupIdLength = 0; |
191 | #endif // __APPLE__ |
192 | PathCharString* gSharedFilesPath = nullptr; |
193 | |
194 | // The lowest common supported semaphore length, including null character |
195 | // NetBSD-7.99.25: 15 characters |
196 | // MacOSX 10.11: 31 -- Core 1.0 RC2 compatibility |
197 | #if defined(__NetBSD__) |
198 | #define CLR_SEM_MAX_NAMELEN 15 |
199 | #elif defined(__APPLE__) |
200 | #define CLR_SEM_MAX_NAMELEN PSEMNAMLEN |
201 | #else |
202 | #define CLR_SEM_MAX_NAMELEN (NAME_MAX - 4) |
203 | #endif |
204 | |
205 | static_assert_no_msg(CLR_SEM_MAX_NAMELEN <= MAX_PATH); |
206 | |
207 | // Function to call during PAL/process shutdown/abort |
208 | Volatile<PSHUTDOWN_CALLBACK> g_shutdownCallback = nullptr; |
209 | |
210 | // Crash dump generating program arguments. Initialized in PROCAbortInitialize(). |
211 | char* g_argvCreateDump[8] = { nullptr }; |
212 | |
213 | // |
214 | // Key used for associating CPalThread's with the underlying pthread |
215 | // (through pthread_setspecific) |
216 | // |
217 | pthread_key_t CorUnix::thObjKey; |
218 | |
219 | static WCHAR W16_WHITESPACE[]= {0x0020, 0x0009, 0x000D, 0}; |
220 | static WCHAR W16_WHITESPACE_DQUOTE[]= {0x0020, 0x0009, 0x000D, '"', 0}; |
221 | |
222 | enum FILETYPE |
223 | { |
224 | FILE_ERROR,/*ERROR*/ |
225 | FILE_UNIX, /*Unix Executable*/ |
226 | FILE_DIR /*Directory*/ |
227 | }; |
228 | |
229 | #pragma pack(push,1) |
230 | // When creating the semaphore name on Mac running in a sandbox, We reference this structure as a byte array |
231 | // in order to encode its data into a string. Its important to make sure there is no padding between the fields |
232 | // and also at the end of the buffer. Hence, this structure is defined inside a pack(1) |
233 | struct UnambiguousProcessDescriptor |
234 | { |
235 | UnambiguousProcessDescriptor() |
236 | { |
237 | } |
238 | |
239 | UnambiguousProcessDescriptor(DWORD processId, UINT64 disambiguationKey) |
240 | { |
241 | Init(processId, disambiguationKey); |
242 | } |
243 | |
244 | void Init(DWORD processId, UINT64 disambiguationKey) |
245 | { |
246 | m_processId = processId; |
247 | m_disambiguationKey = disambiguationKey; |
248 | } |
249 | UINT64 m_disambiguationKey; |
250 | DWORD m_processId; |
251 | }; |
252 | #pragma pack(pop) |
253 | |
254 | static |
255 | DWORD |
256 | PALAPI |
257 | StartupHelperThread( |
258 | LPVOID p); |
259 | |
260 | static |
261 | BOOL |
262 | GetProcessIdDisambiguationKey( |
263 | IN DWORD processId, |
264 | OUT UINT64 *disambiguationKey); |
265 | |
266 | PAL_ERROR |
267 | PROCGetProcessStatus( |
268 | CPalThread *pThread, |
269 | HANDLE hProcess, |
270 | PROCESS_STATE *pps, |
271 | DWORD *pdwExitCode); |
272 | |
273 | static |
274 | void |
275 | CreateSemaphoreName( |
276 | char semName[CLR_SEM_MAX_NAMELEN], |
277 | LPCSTR semaphoreName, |
278 | const UnambiguousProcessDescriptor& unambiguousProcessDescriptor, |
279 | LPCSTR applicationGroupId); |
280 | |
281 | static BOOL getFileName(LPCWSTR lpApplicationName, LPWSTR lpCommandLine, PathCharString& lpFileName); |
282 | static char ** buildArgv(LPCWSTR lpCommandLine, PathCharString& lpAppPath, UINT *pnArg); |
283 | static BOOL getPath(PathCharString& lpFileName, PathCharString& lpPathFileName); |
284 | static int checkFileType(LPCSTR lpFileName); |
285 | static BOOL PROCEndProcess(HANDLE hProcess, UINT uExitCode, BOOL bTerminateUnconditionally); |
286 | |
287 | ProcessModules *GetProcessModulesFromHandle(IN HANDLE hProcess, OUT LPDWORD lpCount); |
288 | ProcessModules *CreateProcessModules(IN DWORD dwProcessId, OUT LPDWORD lpCount); |
289 | void DestroyProcessModules(IN ProcessModules *listHead); |
290 | |
291 | /*++ |
292 | Function: |
293 | GetCurrentProcessId |
294 | |
295 | See MSDN doc. |
296 | --*/ |
297 | DWORD |
298 | PALAPI |
299 | GetCurrentProcessId( |
300 | VOID) |
301 | { |
302 | PERF_ENTRY(GetCurrentProcessId); |
303 | ENTRY("GetCurrentProcessId()\n" ); |
304 | |
305 | LOGEXIT("GetCurrentProcessId returns DWORD %#x\n" , gPID); |
306 | PERF_EXIT(GetCurrentProcessId); |
307 | return gPID; |
308 | } |
309 | |
310 | |
311 | /*++ |
312 | Function: |
313 | GetCurrentSessionId |
314 | |
315 | See MSDN doc. |
316 | --*/ |
317 | DWORD |
318 | PALAPI |
319 | GetCurrentSessionId( |
320 | VOID) |
321 | { |
322 | PERF_ENTRY(GetCurrentSessionId); |
323 | ENTRY("GetCurrentSessionId()\n" ); |
324 | |
325 | LOGEXIT("GetCurrentSessionId returns DWORD %#x\n" , gSID); |
326 | PERF_EXIT(GetCurrentSessionId); |
327 | return gSID; |
328 | } |
329 | |
330 | |
331 | /*++ |
332 | Function: |
333 | GetCurrentProcess |
334 | |
335 | See MSDN doc. |
336 | --*/ |
337 | HANDLE |
338 | PALAPI |
339 | GetCurrentProcess( |
340 | VOID) |
341 | { |
342 | PERF_ENTRY(GetCurrentProcess); |
343 | ENTRY("GetCurrentProcess()\n" ); |
344 | |
345 | LOGEXIT("GetCurrentProcess returns HANDLE %p\n" , hPseudoCurrentProcess); |
346 | PERF_EXIT(GetCurrentProcess); |
347 | |
348 | /* return a pseudo handle */ |
349 | return hPseudoCurrentProcess; |
350 | } |
351 | |
352 | /*++ |
353 | Function: |
354 | CreateProcessA |
355 | |
356 | Note: |
357 | Only Standard handles need to be inherited. |
358 | Security attributes parameters are not used. |
359 | |
360 | See MSDN doc. |
361 | --*/ |
362 | BOOL |
363 | PALAPI |
364 | CreateProcessA( |
365 | IN LPCSTR lpApplicationName, |
366 | IN LPSTR lpCommandLine, |
367 | IN LPSECURITY_ATTRIBUTES lpProcessAttributes, |
368 | IN LPSECURITY_ATTRIBUTES lpThreadAttributes, |
369 | IN BOOL bInheritHandles, |
370 | IN DWORD dwCreationFlags, |
371 | IN LPVOID lpEnvironment, |
372 | IN LPCSTR lpCurrentDirectory, |
373 | IN LPSTARTUPINFOA lpStartupInfo, |
374 | OUT LPPROCESS_INFORMATION lpProcessInformation) |
375 | { |
376 | PAL_ERROR palError = NO_ERROR; |
377 | CPalThread *pThread; |
378 | STARTUPINFOW StartupInfoW; |
379 | LPWSTR CommandLineW = NULL; |
380 | LPWSTR ApplicationNameW = NULL; |
381 | LPWSTR CurrentDirectoryW = NULL; |
382 | |
383 | int n; |
384 | |
385 | PERF_ENTRY(CreateProcessA); |
386 | ENTRY("CreateProcessA(lpAppName=%p (%s), lpCmdLine=%p (%s), lpProcessAttr=%p, " |
387 | "lpThreadAttr=%p, bInherit=%d, dwFlags=%#x, lpEnv=%p, " |
388 | "lpCurrentDir=%p (%s), lpStartupInfo=%p, lpProcessInfo=%p)\n" , |
389 | lpApplicationName?lpApplicationName:"NULL" , |
390 | lpApplicationName?lpApplicationName:"NULL" , |
391 | lpCommandLine?lpCommandLine:"NULL" , |
392 | lpCommandLine?lpCommandLine:"NULL" , |
393 | lpProcessAttributes, lpThreadAttributes, bInheritHandles, |
394 | dwCreationFlags, lpEnvironment, |
395 | lpCurrentDirectory?lpCurrentDirectory:"NULL" , |
396 | lpCurrentDirectory?lpCurrentDirectory:"NULL" , |
397 | lpStartupInfo, lpProcessInformation); |
398 | |
399 | pThread = InternalGetCurrentThread(); |
400 | |
401 | if(lpStartupInfo == NULL) |
402 | { |
403 | ASSERT("lpStartupInfo is NULL!\n" ); |
404 | palError = ERROR_INVALID_PARAMETER; |
405 | goto done; |
406 | } |
407 | |
408 | /* convert parameters to Unicode */ |
409 | |
410 | if(lpApplicationName) |
411 | { |
412 | n = MultiByteToWideChar(CP_ACP, 0, lpApplicationName, -1, NULL, 0); |
413 | if(0 == n) |
414 | { |
415 | ASSERT("MultiByteToWideChar failed!\n" ); |
416 | palError = ERROR_INTERNAL_ERROR; |
417 | goto done; |
418 | } |
419 | ApplicationNameW = (LPWSTR)InternalMalloc(sizeof(WCHAR)*n); |
420 | if(!ApplicationNameW) |
421 | { |
422 | ERROR("malloc() failed!\n" ); |
423 | palError = ERROR_NOT_ENOUGH_MEMORY; |
424 | goto done; |
425 | } |
426 | MultiByteToWideChar(CP_ACP, 0, lpApplicationName, -1, ApplicationNameW, |
427 | n); |
428 | } |
429 | |
430 | if(lpCommandLine) |
431 | { |
432 | n = MultiByteToWideChar(CP_ACP, 0, lpCommandLine, -1, NULL, 0); |
433 | if(0 == n) |
434 | { |
435 | ASSERT("MultiByteToWideChar failed!\n" ); |
436 | palError = ERROR_INTERNAL_ERROR; |
437 | goto done; |
438 | } |
439 | CommandLineW = (LPWSTR)InternalMalloc(sizeof(WCHAR)*n); |
440 | if(!CommandLineW) |
441 | { |
442 | ERROR("malloc() failed!\n" ); |
443 | palError = ERROR_NOT_ENOUGH_MEMORY; |
444 | goto done; |
445 | } |
446 | MultiByteToWideChar(CP_ACP, 0, lpCommandLine, -1, CommandLineW, n); |
447 | } |
448 | |
449 | if(lpCurrentDirectory) |
450 | { |
451 | n = MultiByteToWideChar(CP_ACP, 0, lpCurrentDirectory, -1, NULL, 0); |
452 | if(0 == n) |
453 | { |
454 | ASSERT("MultiByteToWideChar failed!\n" ); |
455 | palError = ERROR_INTERNAL_ERROR; |
456 | goto done; |
457 | } |
458 | CurrentDirectoryW = (LPWSTR)InternalMalloc(sizeof(WCHAR)*n); |
459 | if(!CurrentDirectoryW) |
460 | { |
461 | ERROR("malloc() failed!\n" ); |
462 | palError = ERROR_NOT_ENOUGH_MEMORY; |
463 | goto done; |
464 | } |
465 | MultiByteToWideChar(CP_ACP, 0, lpCurrentDirectory, -1, |
466 | CurrentDirectoryW, n); |
467 | } |
468 | |
469 | // lpEnvironment should remain ansi on the call to CreateProcessW |
470 | |
471 | StartupInfoW.cb = sizeof StartupInfoW; |
472 | StartupInfoW.dwFlags = lpStartupInfo->dwFlags; |
473 | StartupInfoW.hStdError = lpStartupInfo->hStdError; |
474 | StartupInfoW.hStdInput = lpStartupInfo->hStdInput; |
475 | StartupInfoW.hStdOutput = lpStartupInfo->hStdOutput; |
476 | /* all other members are PAL_Undefined, we can ignore them */ |
477 | |
478 | palError = InternalCreateProcess( |
479 | pThread, |
480 | ApplicationNameW, |
481 | CommandLineW, |
482 | lpProcessAttributes, |
483 | lpThreadAttributes, |
484 | bInheritHandles, |
485 | dwCreationFlags, |
486 | lpEnvironment, |
487 | CurrentDirectoryW, |
488 | &StartupInfoW, |
489 | lpProcessInformation |
490 | ); |
491 | done: |
492 | free(ApplicationNameW); |
493 | free(CommandLineW); |
494 | free(CurrentDirectoryW); |
495 | |
496 | if (NO_ERROR != palError) |
497 | { |
498 | pThread->SetLastError(palError); |
499 | } |
500 | |
501 | LOGEXIT("CreateProcessA returns BOOL %d\n" , NO_ERROR == palError); |
502 | PERF_EXIT(CreateProcessA); |
503 | return NO_ERROR == palError; |
504 | } |
505 | |
506 | |
507 | /*++ |
508 | Function: |
509 | CreateProcessW |
510 | |
511 | Note: |
512 | Only Standard handles need to be inherited. |
513 | Security attributes parameters are not used. |
514 | |
515 | See MSDN doc. |
516 | --*/ |
517 | BOOL |
518 | PALAPI |
519 | CreateProcessW( |
520 | IN LPCWSTR lpApplicationName, |
521 | IN LPWSTR lpCommandLine, |
522 | IN LPSECURITY_ATTRIBUTES lpProcessAttributes, |
523 | IN LPSECURITY_ATTRIBUTES lpThreadAttributes, |
524 | IN BOOL bInheritHandles, |
525 | IN DWORD dwCreationFlags, |
526 | IN LPVOID lpEnvironment, |
527 | IN LPCWSTR lpCurrentDirectory, |
528 | IN LPSTARTUPINFOW lpStartupInfo, |
529 | OUT LPPROCESS_INFORMATION lpProcessInformation) |
530 | { |
531 | PAL_ERROR palError = NO_ERROR; |
532 | CPalThread *pThread; |
533 | |
534 | PERF_ENTRY(CreateProcessW); |
535 | ENTRY("CreateProcessW(lpAppName=%p (%S), lpCmdLine=%p (%S), lpProcessAttr=%p," |
536 | "lpThreadAttr=%p, bInherit=%d, dwFlags=%#x, lpEnv=%p," |
537 | "lpCurrentDir=%p (%S), lpStartupInfo=%p, lpProcessInfo=%p)\n" , |
538 | lpApplicationName?lpApplicationName:W16_NULLSTRING, |
539 | lpApplicationName?lpApplicationName:W16_NULLSTRING, |
540 | lpCommandLine?lpCommandLine:W16_NULLSTRING, |
541 | lpCommandLine?lpCommandLine:W16_NULLSTRING,lpProcessAttributes, |
542 | lpThreadAttributes, bInheritHandles, dwCreationFlags,lpEnvironment, |
543 | lpCurrentDirectory?lpCurrentDirectory:W16_NULLSTRING, |
544 | lpCurrentDirectory?lpCurrentDirectory:W16_NULLSTRING, |
545 | lpStartupInfo, lpProcessInformation); |
546 | |
547 | pThread = InternalGetCurrentThread(); |
548 | |
549 | palError = InternalCreateProcess( |
550 | pThread, |
551 | lpApplicationName, |
552 | lpCommandLine, |
553 | lpProcessAttributes, |
554 | lpThreadAttributes, |
555 | bInheritHandles, |
556 | dwCreationFlags, |
557 | lpEnvironment, |
558 | lpCurrentDirectory, |
559 | lpStartupInfo, |
560 | lpProcessInformation |
561 | ); |
562 | |
563 | if (NO_ERROR != palError) |
564 | { |
565 | pThread->SetLastError(palError); |
566 | } |
567 | |
568 | LOGEXIT("CreateProcessW returns BOOL %d\n" , NO_ERROR == palError); |
569 | PERF_EXIT(CreateProcessW); |
570 | |
571 | return NO_ERROR == palError; |
572 | } |
573 | |
574 | PAL_ERROR |
575 | PrepareStandardHandle( |
576 | CPalThread *pThread, |
577 | HANDLE hFile, |
578 | IPalObject **ppobjFile, |
579 | int *piFd |
580 | ) |
581 | { |
582 | PAL_ERROR palError = NO_ERROR; |
583 | IPalObject *pobjFile = NULL; |
584 | IDataLock *pDataLock = NULL; |
585 | CFileProcessLocalData *pLocalData = NULL; |
586 | int iError = 0; |
587 | |
588 | palError = g_pObjectManager->ReferenceObjectByHandle( |
589 | pThread, |
590 | hFile, |
591 | &aotFile, |
592 | 0, |
593 | &pobjFile |
594 | ); |
595 | |
596 | if (NO_ERROR != palError) |
597 | { |
598 | ERROR("Bad handle passed through CreateProcess\n" ); |
599 | goto PrepareStandardHandleExit; |
600 | } |
601 | |
602 | palError = pobjFile->GetProcessLocalData( |
603 | pThread, |
604 | ReadLock, |
605 | &pDataLock, |
606 | reinterpret_cast<void **>(&pLocalData) |
607 | ); |
608 | |
609 | if (NO_ERROR != palError) |
610 | { |
611 | ASSERT("Unable to access file data\n" ); |
612 | goto PrepareStandardHandleExit; |
613 | } |
614 | |
615 | // |
616 | // The passed in file needs to be inheritable |
617 | // |
618 | |
619 | if (!pLocalData->inheritable) |
620 | { |
621 | ERROR("Non-inheritable handle passed through CreateProcess\n" ); |
622 | palError = ERROR_INVALID_HANDLE; |
623 | goto PrepareStandardHandleExit; |
624 | } |
625 | |
626 | iError = fcntl(pLocalData->unix_fd, F_SETFD, 0); |
627 | if (-1 == iError) |
628 | { |
629 | ERROR("Unable to remove close-on-exec for file (errno %i)\n" , errno); |
630 | palError = ERROR_INVALID_HANDLE; |
631 | goto PrepareStandardHandleExit; |
632 | } |
633 | |
634 | *piFd = pLocalData->unix_fd; |
635 | pDataLock->ReleaseLock(pThread, FALSE); |
636 | pDataLock = NULL; |
637 | |
638 | // |
639 | // Transfer pobjFile reference to out parameter |
640 | // |
641 | |
642 | *ppobjFile = pobjFile; |
643 | pobjFile = NULL; |
644 | |
645 | PrepareStandardHandleExit: |
646 | |
647 | if (NULL != pDataLock) |
648 | { |
649 | pDataLock->ReleaseLock(pThread, FALSE); |
650 | } |
651 | |
652 | if (NULL != pobjFile) |
653 | { |
654 | pobjFile->ReleaseReference(pThread); |
655 | } |
656 | |
657 | return palError; |
658 | } |
659 | |
660 | PAL_ERROR |
661 | CorUnix::InternalCreateProcess( |
662 | CPalThread *pThread, |
663 | LPCWSTR lpApplicationName, |
664 | LPWSTR lpCommandLine, |
665 | LPSECURITY_ATTRIBUTES lpProcessAttributes, |
666 | LPSECURITY_ATTRIBUTES lpThreadAttributes, |
667 | BOOL bInheritHandles, |
668 | DWORD dwCreationFlags, |
669 | LPVOID lpEnvironment, |
670 | LPCWSTR lpCurrentDirectory, |
671 | LPSTARTUPINFOW lpStartupInfo, |
672 | LPPROCESS_INFORMATION lpProcessInformation |
673 | ) |
674 | { |
675 | PAL_ERROR palError = NO_ERROR; |
676 | IPalObject *pobjProcess = NULL; |
677 | IPalObject *pobjProcessRegistered = NULL; |
678 | IDataLock *pLocalDataLock = NULL; |
679 | CProcProcessLocalData *pLocalData; |
680 | IDataLock *pSharedDataLock = NULL; |
681 | CPalThread *pDummyThread = NULL; |
682 | HANDLE hDummyThread = NULL; |
683 | HANDLE hProcess = NULL; |
684 | CObjectAttributes oa(NULL, lpProcessAttributes); |
685 | |
686 | IPalObject *pobjFileIn = NULL; |
687 | int iFdIn = -1; |
688 | IPalObject *pobjFileOut = NULL; |
689 | int iFdOut = -1; |
690 | IPalObject *pobjFileErr = NULL; |
691 | int iFdErr = -1; |
692 | |
693 | pid_t processId; |
694 | PathCharString lpFileNamePS; |
695 | char **lppArgv = NULL; |
696 | UINT nArg; |
697 | int iRet; |
698 | char **EnvironmentArray=NULL; |
699 | int child_blocking_pipe = -1; |
700 | int parent_blocking_pipe = -1; |
701 | |
702 | /* Validate parameters */ |
703 | |
704 | /* note : specs indicate lpApplicationName should always |
705 | be NULL; however support for it is already implemented. Leaving the code |
706 | in, specs can change; but rejecting non-NULL for now to conform to the |
707 | spec. */ |
708 | if( NULL != lpApplicationName ) |
709 | { |
710 | ASSERT("lpApplicationName should be NULL, but is %S instead\n" , |
711 | lpApplicationName); |
712 | palError = ERROR_INVALID_PARAMETER; |
713 | goto InternalCreateProcessExit; |
714 | } |
715 | |
716 | if (0 != (dwCreationFlags & ~(CREATE_SUSPENDED|CREATE_NEW_CONSOLE))) |
717 | { |
718 | ASSERT("Unexpected creation flags (%#x)\n" , dwCreationFlags); |
719 | palError = ERROR_INVALID_PARAMETER; |
720 | goto InternalCreateProcessExit; |
721 | } |
722 | |
723 | /* Security attributes parameters are ignored */ |
724 | if (lpProcessAttributes != NULL && |
725 | (lpProcessAttributes->lpSecurityDescriptor != NULL || |
726 | lpProcessAttributes->bInheritHandle != TRUE)) |
727 | { |
728 | ASSERT("lpProcessAttributes is invalid, parameter ignored (%p)\n" , |
729 | lpProcessAttributes); |
730 | palError = ERROR_INVALID_PARAMETER; |
731 | goto InternalCreateProcessExit; |
732 | } |
733 | |
734 | if (lpThreadAttributes != NULL) |
735 | { |
736 | ASSERT("lpThreadAttributes parameter must be NULL (%p)\n" , |
737 | lpThreadAttributes); |
738 | palError = ERROR_INVALID_PARAMETER; |
739 | goto InternalCreateProcessExit; |
740 | } |
741 | |
742 | /* note : Win32 crashes in this case */ |
743 | if(NULL == lpStartupInfo) |
744 | { |
745 | ERROR("lpStartupInfo is NULL\n" ); |
746 | palError = ERROR_INVALID_PARAMETER; |
747 | goto InternalCreateProcessExit; |
748 | } |
749 | |
750 | /* Validate lpStartupInfo.cb field */ |
751 | if (lpStartupInfo->cb < sizeof(STARTUPINFOW)) |
752 | { |
753 | ASSERT("lpStartupInfo parameter structure size is invalid (%u)\n" , |
754 | lpStartupInfo->cb); |
755 | palError = ERROR_INVALID_PARAMETER; |
756 | goto InternalCreateProcessExit; |
757 | } |
758 | |
759 | /* lpStartupInfo should be either zero or STARTF_USESTDHANDLES */ |
760 | if (lpStartupInfo->dwFlags & ~STARTF_USESTDHANDLES) |
761 | { |
762 | ASSERT("lpStartupInfo parameter invalid flags (%#x)\n" , |
763 | lpStartupInfo->dwFlags); |
764 | palError = ERROR_INVALID_PARAMETER; |
765 | goto InternalCreateProcessExit; |
766 | } |
767 | |
768 | /* validate given standard handles if we have any */ |
769 | if (lpStartupInfo->dwFlags & STARTF_USESTDHANDLES) |
770 | { |
771 | palError = PrepareStandardHandle( |
772 | pThread, |
773 | lpStartupInfo->hStdInput, |
774 | &pobjFileIn, |
775 | &iFdIn |
776 | ); |
777 | |
778 | if (NO_ERROR != palError) |
779 | { |
780 | goto InternalCreateProcessExit; |
781 | } |
782 | |
783 | palError = PrepareStandardHandle( |
784 | pThread, |
785 | lpStartupInfo->hStdOutput, |
786 | &pobjFileOut, |
787 | &iFdOut |
788 | ); |
789 | |
790 | if (NO_ERROR != palError) |
791 | { |
792 | goto InternalCreateProcessExit; |
793 | } |
794 | |
795 | palError = PrepareStandardHandle( |
796 | pThread, |
797 | lpStartupInfo->hStdError, |
798 | &pobjFileErr, |
799 | &iFdErr |
800 | ); |
801 | |
802 | if (NO_ERROR != palError) |
803 | { |
804 | goto InternalCreateProcessExit; |
805 | } |
806 | } |
807 | |
808 | if (!getFileName(lpApplicationName, lpCommandLine, lpFileNamePS)) |
809 | { |
810 | ERROR("Can't find executable!\n" ); |
811 | palError = ERROR_FILE_NOT_FOUND; |
812 | goto InternalCreateProcessExit; |
813 | } |
814 | |
815 | /* check type of file */ |
816 | iRet = checkFileType(lpFileNamePS); |
817 | |
818 | switch (iRet) |
819 | { |
820 | case FILE_ERROR: /* file not found, or not an executable */ |
821 | WARN ("File is not valid (%s)" , lpFileNamePS.GetString()); |
822 | palError = ERROR_FILE_NOT_FOUND; |
823 | goto InternalCreateProcessExit; |
824 | |
825 | case FILE_UNIX: /* Unix binary file */ |
826 | break; /* nothing to do */ |
827 | |
828 | case FILE_DIR:/*Directory*/ |
829 | WARN ("File is a Directory (%s)" , lpFileNamePS.GetString()); |
830 | palError = ERROR_ACCESS_DENIED; |
831 | goto InternalCreateProcessExit; |
832 | break; |
833 | |
834 | default: /* not supposed to get here */ |
835 | ASSERT ("Invalid return type from checkFileType" ); |
836 | palError = ERROR_FILE_NOT_FOUND; |
837 | goto InternalCreateProcessExit; |
838 | } |
839 | |
840 | /* build Argument list, lppArgv is allocated in buildArgv function and |
841 | requires to be freed */ |
842 | lppArgv = buildArgv(lpCommandLine, lpFileNamePS, &nArg); |
843 | |
844 | /* set the Environment variable */ |
845 | if (lpEnvironment != NULL) |
846 | { |
847 | unsigned i; |
848 | // Since CREATE_UNICODE_ENVIRONMENT isn't supported we know the string is ansi |
849 | unsigned EnvironmentEntries = 0; |
850 | // Convert the environment block to array of strings |
851 | // Count the number of entries |
852 | // Is it a string that contains null terminated string, the end is delimited |
853 | // by two null in a row. |
854 | for (i = 0; ((char *)lpEnvironment)[i]!='\0'; i++) |
855 | { |
856 | EnvironmentEntries ++; |
857 | for (;((char *)lpEnvironment)[i]!='\0'; i++) |
858 | { |
859 | } |
860 | } |
861 | EnvironmentEntries++; |
862 | EnvironmentArray = (char **)InternalMalloc(EnvironmentEntries * sizeof(char *)); |
863 | |
864 | EnvironmentEntries = 0; |
865 | // Convert the environment block to array of strings |
866 | // Count the number of entries |
867 | // Is it a string that contains null terminated string, the end is delimited |
868 | // by two null in a row. |
869 | for (i = 0; ((char *)lpEnvironment)[i]!='\0'; i++) |
870 | { |
871 | EnvironmentArray[EnvironmentEntries] = &((char *)lpEnvironment)[i]; |
872 | EnvironmentEntries ++; |
873 | for (;((char *)lpEnvironment)[i]!='\0'; i++) |
874 | { |
875 | } |
876 | } |
877 | EnvironmentArray[EnvironmentEntries] = NULL; |
878 | } |
879 | |
880 | // |
881 | // Allocate and register the process object for the new process |
882 | // |
883 | |
884 | palError = g_pObjectManager->AllocateObject( |
885 | pThread, |
886 | &otProcess, |
887 | &oa, |
888 | &pobjProcess |
889 | ); |
890 | |
891 | if (NO_ERROR != palError) |
892 | { |
893 | ERROR("Unable to allocate object for new proccess\n" ); |
894 | goto InternalCreateProcessExit; |
895 | } |
896 | |
897 | palError = g_pObjectManager->RegisterObject( |
898 | pThread, |
899 | pobjProcess, |
900 | &aotProcess, |
901 | PROCESS_ALL_ACCESS, |
902 | &hProcess, |
903 | &pobjProcessRegistered |
904 | ); |
905 | |
906 | // |
907 | // pobjProcess is invalidated by the above call, so |
908 | // NULL it out here |
909 | // |
910 | |
911 | pobjProcess = NULL; |
912 | |
913 | if (NO_ERROR != palError) |
914 | { |
915 | ERROR("Unable to register new process object\n" ); |
916 | goto InternalCreateProcessExit; |
917 | } |
918 | |
919 | // |
920 | // Create a new "dummy" thread object |
921 | // |
922 | |
923 | palError = InternalCreateDummyThread( |
924 | pThread, |
925 | lpThreadAttributes, |
926 | &pDummyThread, |
927 | &hDummyThread |
928 | ); |
929 | |
930 | if (dwCreationFlags & CREATE_SUSPENDED) |
931 | { |
932 | int pipe_descs[2]; |
933 | |
934 | if (-1 == pipe(pipe_descs)) |
935 | { |
936 | ERROR("pipe() failed! error is %d (%s)\n" , errno, strerror(errno)); |
937 | palError = ERROR_NOT_ENOUGH_MEMORY; |
938 | goto InternalCreateProcessExit; |
939 | } |
940 | |
941 | /* [0] is read end, [1] is write end */ |
942 | pDummyThread->suspensionInfo.SetBlockingPipe(pipe_descs[1]); |
943 | parent_blocking_pipe = pipe_descs[1]; |
944 | child_blocking_pipe = pipe_descs[0]; |
945 | } |
946 | |
947 | palError = pobjProcessRegistered->GetProcessLocalData( |
948 | pThread, |
949 | WriteLock, |
950 | &pLocalDataLock, |
951 | reinterpret_cast<void **>(&pLocalData) |
952 | ); |
953 | |
954 | if (NO_ERROR != palError) |
955 | { |
956 | ASSERT("Unable to obtain local data for new process object\n" ); |
957 | goto InternalCreateProcessExit; |
958 | } |
959 | |
960 | |
961 | /* fork the new process */ |
962 | processId = fork(); |
963 | |
964 | if (processId == -1) |
965 | { |
966 | ASSERT("Unable to create a new process with fork()\n" ); |
967 | if (-1 != child_blocking_pipe) |
968 | { |
969 | close(child_blocking_pipe); |
970 | close(parent_blocking_pipe); |
971 | } |
972 | |
973 | palError = ERROR_INTERNAL_ERROR; |
974 | goto InternalCreateProcessExit; |
975 | } |
976 | |
977 | /* From the time the child process begins running, to when it reaches execve, |
978 | the child process is not a real PAL process and does not own any PAL |
979 | resources, although it has access to the PAL resources of its parent process. |
980 | Thus, while the child process is in this window, it is dangerous for it to affect |
981 | its parent's PAL resources. As a consequence, no PAL code should be used |
982 | in this window; all code should make unix calls. Note the use of _exit |
983 | instead of exit to avoid calling PAL_Terminate and the lack of TRACE's and |
984 | ASSERT's. */ |
985 | |
986 | if (processId == 0) /* child process */ |
987 | { |
988 | // At this point, the PAL should be considered uninitialized for this child process. |
989 | |
990 | // Don't want to enter the init_critsec here since we're trying to avoid |
991 | // calling PAL functions. Furthermore, nothing should be changing |
992 | // the init_count in the child process at this point since this is the only |
993 | // thread executing. |
994 | init_count = 0; |
995 | |
996 | sigset_t sm; |
997 | |
998 | // |
999 | // Clear out the signal mask for the new process. |
1000 | // |
1001 | |
1002 | sigemptyset(&sm); |
1003 | iRet = sigprocmask(SIG_SETMASK, &sm, NULL); |
1004 | if (iRet != 0) |
1005 | { |
1006 | _exit(EXIT_FAILURE); |
1007 | } |
1008 | |
1009 | if (dwCreationFlags & CREATE_SUSPENDED) |
1010 | { |
1011 | BYTE resume_code = 0; |
1012 | ssize_t read_ret; |
1013 | |
1014 | /* close the write end of the pipe, the child doesn't need it */ |
1015 | close(parent_blocking_pipe); |
1016 | |
1017 | read_again: |
1018 | /* block until ResumeThread writes something to the pipe */ |
1019 | read_ret = read(child_blocking_pipe, &resume_code, sizeof(resume_code)); |
1020 | if (sizeof(resume_code) != read_ret) |
1021 | { |
1022 | if (read_ret == -1 && EINTR == errno) |
1023 | { |
1024 | goto read_again; |
1025 | } |
1026 | else |
1027 | { |
1028 | /* note : read might return 0 (and return EAGAIN) if the other |
1029 | end of the pipe gets closed - for example because the parent |
1030 | process dies (very) abruptly */ |
1031 | _exit(EXIT_FAILURE); |
1032 | } |
1033 | } |
1034 | if (WAKEUPCODE != resume_code) |
1035 | { |
1036 | // resume_code should always equal WAKEUPCODE. |
1037 | _exit(EXIT_FAILURE); |
1038 | } |
1039 | |
1040 | close(child_blocking_pipe); |
1041 | } |
1042 | |
1043 | /* Set the current directory */ |
1044 | if (lpCurrentDirectory) |
1045 | { |
1046 | SetCurrentDirectoryW(lpCurrentDirectory); |
1047 | } |
1048 | |
1049 | /* Set the standard handles to the incoming values */ |
1050 | if (lpStartupInfo->dwFlags & STARTF_USESTDHANDLES) |
1051 | { |
1052 | /* For each handle, we need to duplicate the incoming unix |
1053 | fd to the corresponding standard one. The API that I use, |
1054 | dup2, will copy the source to the destination, automatically |
1055 | closing the existing destination, in an atomic way */ |
1056 | if (dup2(iFdIn, STDIN_FILENO) == -1) |
1057 | { |
1058 | // Didn't duplicate standard in. |
1059 | _exit(EXIT_FAILURE); |
1060 | } |
1061 | |
1062 | if (dup2(iFdOut, STDOUT_FILENO) == -1) |
1063 | { |
1064 | // Didn't duplicate standard out. |
1065 | _exit(EXIT_FAILURE); |
1066 | } |
1067 | |
1068 | if (dup2(iFdErr, STDERR_FILENO) == -1) |
1069 | { |
1070 | // Didn't duplicate standard error. |
1071 | _exit(EXIT_FAILURE); |
1072 | } |
1073 | |
1074 | /* now close the original FDs, we don't need them anymore */ |
1075 | close(iFdIn); |
1076 | close(iFdOut); |
1077 | close(iFdErr); |
1078 | } |
1079 | |
1080 | /* execute the new process */ |
1081 | |
1082 | if (EnvironmentArray) |
1083 | { |
1084 | execve(lpFileNamePS, lppArgv, EnvironmentArray); |
1085 | } |
1086 | else |
1087 | { |
1088 | execve(lpFileNamePS, lppArgv, palEnvironment); |
1089 | } |
1090 | |
1091 | /* if we get here, it means the execve function call failed so just exit */ |
1092 | _exit(EXIT_FAILURE); |
1093 | } |
1094 | |
1095 | /* parent process */ |
1096 | |
1097 | /* close the read end of the pipe, the parent doesn't need it */ |
1098 | close(child_blocking_pipe); |
1099 | |
1100 | /* Set the process ID */ |
1101 | pLocalData->dwProcessId = processId; |
1102 | pLocalDataLock->ReleaseLock(pThread, TRUE); |
1103 | pLocalDataLock = NULL; |
1104 | |
1105 | // |
1106 | // Release file handle info; we don't need them anymore. Note that |
1107 | // this must happen after we've released the data locks, as |
1108 | // otherwise a deadlock could result. |
1109 | // |
1110 | |
1111 | if (lpStartupInfo->dwFlags & STARTF_USESTDHANDLES) |
1112 | { |
1113 | pobjFileIn->ReleaseReference(pThread); |
1114 | pobjFileIn = NULL; |
1115 | pobjFileOut->ReleaseReference(pThread); |
1116 | pobjFileOut = NULL; |
1117 | pobjFileErr->ReleaseReference(pThread); |
1118 | pobjFileErr = NULL; |
1119 | } |
1120 | |
1121 | /* fill PROCESS_INFORMATION strucutre */ |
1122 | lpProcessInformation->hProcess = hProcess; |
1123 | lpProcessInformation->hThread = hDummyThread; |
1124 | lpProcessInformation->dwProcessId = processId; |
1125 | lpProcessInformation->dwThreadId_PAL_Undefined = 0; |
1126 | |
1127 | |
1128 | TRACE("New process created: id=%#x\n" , processId); |
1129 | |
1130 | InternalCreateProcessExit: |
1131 | |
1132 | if (NULL != pLocalDataLock) |
1133 | { |
1134 | pLocalDataLock->ReleaseLock(pThread, FALSE); |
1135 | } |
1136 | |
1137 | if (NULL != pSharedDataLock) |
1138 | { |
1139 | pSharedDataLock->ReleaseLock(pThread, FALSE); |
1140 | } |
1141 | |
1142 | if (NULL != pobjProcess) |
1143 | { |
1144 | pobjProcess->ReleaseReference(pThread); |
1145 | } |
1146 | |
1147 | if (NULL != pobjProcessRegistered) |
1148 | { |
1149 | pobjProcessRegistered->ReleaseReference(pThread); |
1150 | } |
1151 | |
1152 | if (NO_ERROR != palError) |
1153 | { |
1154 | if (NULL != hProcess) |
1155 | { |
1156 | g_pObjectManager->RevokeHandle(pThread, hProcess); |
1157 | } |
1158 | |
1159 | if (NULL != hDummyThread) |
1160 | { |
1161 | g_pObjectManager->RevokeHandle(pThread, hDummyThread); |
1162 | } |
1163 | } |
1164 | |
1165 | if (EnvironmentArray) |
1166 | { |
1167 | free(EnvironmentArray); |
1168 | } |
1169 | |
1170 | /* if we still have the file structures at this point, it means we |
1171 | encountered an error sometime between when we acquired them and when we |
1172 | fork()ed. We not only have to release them, we have to give them back |
1173 | their close-on-exec flag */ |
1174 | if (NULL != pobjFileIn) |
1175 | { |
1176 | if(-1 == fcntl(iFdIn, F_SETFD, 1)) |
1177 | { |
1178 | WARN("couldn't restore close-on-exec flag to stdin descriptor! " |
1179 | "errno is %d (%s)\n" , errno, strerror(errno)); |
1180 | } |
1181 | pobjFileIn->ReleaseReference(pThread); |
1182 | } |
1183 | |
1184 | if (NULL != pobjFileOut) |
1185 | { |
1186 | if(-1 == fcntl(iFdOut, F_SETFD, 1)) |
1187 | { |
1188 | WARN("couldn't restore close-on-exec flag to stdout descriptor! " |
1189 | "errno is %d (%s)\n" , errno, strerror(errno)); |
1190 | } |
1191 | pobjFileOut->ReleaseReference(pThread); |
1192 | } |
1193 | |
1194 | if (NULL != pobjFileErr) |
1195 | { |
1196 | if(-1 == fcntl(iFdErr, F_SETFD, 1)) |
1197 | { |
1198 | WARN("couldn't restore close-on-exec flag to stderr descriptor! " |
1199 | "errno is %d (%s)\n" , errno, strerror(errno)); |
1200 | } |
1201 | pobjFileErr->ReleaseReference(pThread); |
1202 | } |
1203 | |
1204 | /* free allocated memory */ |
1205 | if (lppArgv) |
1206 | { |
1207 | free(*lppArgv); |
1208 | free(lppArgv); |
1209 | } |
1210 | |
1211 | return palError; |
1212 | } |
1213 | |
1214 | |
1215 | /*++ |
1216 | Function: |
1217 | GetExitCodeProcess |
1218 | |
1219 | See MSDN doc. |
1220 | --*/ |
1221 | BOOL |
1222 | PALAPI |
1223 | GetExitCodeProcess( |
1224 | IN HANDLE hProcess, |
1225 | IN LPDWORD lpExitCode) |
1226 | { |
1227 | CPalThread *pThread; |
1228 | PAL_ERROR palError = NO_ERROR; |
1229 | DWORD dwExitCode; |
1230 | PROCESS_STATE ps; |
1231 | |
1232 | PERF_ENTRY(GetExitCodeProcess); |
1233 | ENTRY("GetExitCodeProcess(hProcess = %p, lpExitCode = %p)\n" , |
1234 | hProcess, lpExitCode); |
1235 | |
1236 | pThread = InternalGetCurrentThread(); |
1237 | |
1238 | if(NULL == lpExitCode) |
1239 | { |
1240 | WARN("Got NULL lpExitCode\n" ); |
1241 | palError = ERROR_INVALID_PARAMETER; |
1242 | goto done; |
1243 | } |
1244 | |
1245 | palError = PROCGetProcessStatus( |
1246 | pThread, |
1247 | hProcess, |
1248 | &ps, |
1249 | &dwExitCode |
1250 | ); |
1251 | |
1252 | if (NO_ERROR != palError) |
1253 | { |
1254 | ASSERT("Couldn't get process status information!\n" ); |
1255 | goto done; |
1256 | } |
1257 | |
1258 | if( PS_DONE == ps ) |
1259 | { |
1260 | *lpExitCode = dwExitCode; |
1261 | } |
1262 | else |
1263 | { |
1264 | *lpExitCode = STILL_ACTIVE; |
1265 | } |
1266 | |
1267 | done: |
1268 | |
1269 | if (NO_ERROR != palError) |
1270 | { |
1271 | pThread->SetLastError(palError); |
1272 | } |
1273 | |
1274 | LOGEXIT("GetExitCodeProcess returns BOOL %d\n" , NO_ERROR == palError); |
1275 | PERF_EXIT(GetExitCodeProcess); |
1276 | |
1277 | return NO_ERROR == palError; |
1278 | } |
1279 | |
1280 | /*++ |
1281 | Function: |
1282 | ExitProcess |
1283 | |
1284 | See MSDN doc. |
1285 | --*/ |
1286 | PAL_NORETURN |
1287 | VOID |
1288 | PALAPI |
1289 | ExitProcess( |
1290 | IN UINT uExitCode) |
1291 | { |
1292 | DWORD old_terminator; |
1293 | |
1294 | PERF_ENTRY_ONLY(ExitProcess); |
1295 | ENTRY("ExitProcess(uExitCode=0x%x)\n" , uExitCode ); |
1296 | |
1297 | old_terminator = InterlockedCompareExchange(&terminator, GetCurrentThreadId(), 0); |
1298 | |
1299 | if (GetCurrentThreadId() == old_terminator) |
1300 | { |
1301 | // This thread has already initiated termination. This can happen |
1302 | // in two ways: |
1303 | // 1) DllMain(DLL_PROCESS_DETACH) triggers a call to ExitProcess. |
1304 | // 2) PAL_exit() is called after the last PALTerminate(). |
1305 | // If the PAL is still initialized, we go straight through to |
1306 | // PROCEndProcess. If it isn't, we simply exit. |
1307 | if (!PALIsInitialized()) |
1308 | { |
1309 | exit(uExitCode); |
1310 | ASSERT("exit has returned\n" ); |
1311 | } |
1312 | else |
1313 | { |
1314 | WARN("thread re-called ExitProcess\n" ); |
1315 | PROCEndProcess(GetCurrentProcess(), uExitCode, FALSE); |
1316 | } |
1317 | } |
1318 | else if (0 != old_terminator) |
1319 | { |
1320 | /* another thread has already initiated the termination process. we |
1321 | could just block on the PALInitLock critical section, but then |
1322 | PROCSuspendOtherThreads would hang... so sleep forever here, we're |
1323 | terminating anyway |
1324 | |
1325 | Update: [TODO] PROCSuspendOtherThreads has been removed. Can this |
1326 | code be changed? */ |
1327 | WARN("termination already started from another thread; blocking.\n" ); |
1328 | poll(NULL, 0, INFTIM); |
1329 | } |
1330 | |
1331 | /* ExitProcess may be called even if PAL is not initialized. |
1332 | Verify if process structure exist |
1333 | */ |
1334 | if (PALInitLock() && PALIsInitialized()) |
1335 | { |
1336 | PROCEndProcess(GetCurrentProcess(), uExitCode, FALSE); |
1337 | |
1338 | /* Should not get here, because we terminate the current process */ |
1339 | ASSERT("PROCEndProcess has returned\n" ); |
1340 | } |
1341 | else |
1342 | { |
1343 | exit(uExitCode); |
1344 | |
1345 | /* Should not get here, because we terminate the current process */ |
1346 | ASSERT("exit has returned\n" ); |
1347 | } |
1348 | |
1349 | /* this should never get executed */ |
1350 | ASSERT("ExitProcess should not return!\n" ); |
1351 | for (;;); |
1352 | } |
1353 | |
1354 | /*++ |
1355 | Function: |
1356 | TerminateProcess |
1357 | |
1358 | Note: |
1359 | hProcess is a handle on the current process. |
1360 | |
1361 | See MSDN doc. |
1362 | --*/ |
1363 | BOOL |
1364 | PALAPI |
1365 | TerminateProcess( |
1366 | IN HANDLE hProcess, |
1367 | IN UINT uExitCode) |
1368 | { |
1369 | BOOL ret; |
1370 | |
1371 | PERF_ENTRY(TerminateProcess); |
1372 | ENTRY("TerminateProcess(hProcess=%p, uExitCode=%u)\n" ,hProcess, uExitCode ); |
1373 | |
1374 | ret = PROCEndProcess(hProcess, uExitCode, TRUE); |
1375 | |
1376 | LOGEXIT("TerminateProcess returns BOOL %d\n" , ret); |
1377 | PERF_EXIT(TerminateProcess); |
1378 | return ret; |
1379 | } |
1380 | |
1381 | /*++ |
1382 | Function: |
1383 | RaiseFailFastException |
1384 | |
1385 | See MSDN doc. |
1386 | --*/ |
1387 | VOID |
1388 | PALAPI |
1389 | RaiseFailFastException( |
1390 | IN PEXCEPTION_RECORD pExceptionRecord, |
1391 | IN PCONTEXT pContextRecord, |
1392 | IN DWORD dwFlags) |
1393 | { |
1394 | PERF_ENTRY(RaiseFailFastException); |
1395 | ENTRY("RaiseFailFastException" ); |
1396 | |
1397 | TerminateCurrentProcessNoExit(TRUE); |
1398 | PROCAbort(); |
1399 | |
1400 | LOGEXIT("RaiseFailFastException" ); |
1401 | PERF_EXIT(RaiseFailFastException); |
1402 | } |
1403 | |
1404 | /*++ |
1405 | Function: |
1406 | PROCEndProcess |
1407 | |
1408 | Called from TerminateProcess and ExitProcess. This does the work of |
1409 | TerminateProcess, but also takes a flag that determines whether we |
1410 | shut down unconditionally. If the flag is set, the PAL will do very |
1411 | little extra work before exiting. Most importantly, it won't shut |
1412 | down any DLLs that are loaded. |
1413 | |
1414 | --*/ |
1415 | static BOOL PROCEndProcess(HANDLE hProcess, UINT uExitCode, BOOL bTerminateUnconditionally) |
1416 | { |
1417 | DWORD dwProcessId; |
1418 | BOOL ret = FALSE; |
1419 | |
1420 | dwProcessId = PROCGetProcessIDFromHandle(hProcess); |
1421 | if (dwProcessId == 0) |
1422 | { |
1423 | SetLastError(ERROR_INVALID_HANDLE); |
1424 | } |
1425 | else if(dwProcessId != GetCurrentProcessId()) |
1426 | { |
1427 | if (uExitCode != 0) |
1428 | WARN("exit code 0x%x ignored for external process.\n" , uExitCode); |
1429 | |
1430 | if (kill(dwProcessId, SIGKILL) == 0) |
1431 | { |
1432 | ret = TRUE; |
1433 | } |
1434 | else |
1435 | { |
1436 | switch (errno) { |
1437 | case ESRCH: |
1438 | SetLastError(ERROR_INVALID_HANDLE); |
1439 | break; |
1440 | case EPERM: |
1441 | SetLastError(ERROR_ACCESS_DENIED); |
1442 | break; |
1443 | default: |
1444 | // Unexpected failure. |
1445 | ASSERT(FALSE); |
1446 | SetLastError(ERROR_INTERNAL_ERROR); |
1447 | break; |
1448 | } |
1449 | } |
1450 | } |
1451 | else |
1452 | { |
1453 | // WARN/ERROR before starting the termination process and/or leaving the PAL. |
1454 | if (bTerminateUnconditionally) |
1455 | { |
1456 | WARN("exit code 0x%x ignored for terminate.\n" , uExitCode); |
1457 | } |
1458 | else if ((uExitCode & 0xff) != uExitCode) |
1459 | { |
1460 | // TODO: Convert uExitCodes into sysexits(3)? |
1461 | ERROR("exit() only supports the lower 8-bits of an exit code. " |
1462 | "status will only see error 0x%x instead of 0x%x.\n" , uExitCode & 0xff, uExitCode); |
1463 | } |
1464 | |
1465 | TerminateCurrentProcessNoExit(bTerminateUnconditionally); |
1466 | |
1467 | LOGEXIT("PROCEndProcess will not return\n" ); |
1468 | |
1469 | // exit() runs atexit handlers possibly registered by foreign code. |
1470 | // The right thing to do here is to leave the PAL. If our client |
1471 | // registered our own PAL_Terminate with atexit(), the latter will |
1472 | // explicitly re-enter us. |
1473 | PAL_Leave(PAL_BoundaryBottom); |
1474 | |
1475 | if (bTerminateUnconditionally) |
1476 | { |
1477 | // abort() has the semantics that |
1478 | // (1) it doesn't run atexit handlers |
1479 | // (2) can invoke CrashReporter or produce a coredump, |
1480 | // which is appropriate for TerminateProcess calls |
1481 | PROCAbort(); |
1482 | } |
1483 | else |
1484 | { |
1485 | exit(uExitCode); |
1486 | } |
1487 | |
1488 | ASSERT(FALSE); // we shouldn't get here |
1489 | } |
1490 | |
1491 | return ret; |
1492 | } |
1493 | |
1494 | /*++ |
1495 | Function: |
1496 | PAL_SetShutdownCallback |
1497 | |
1498 | Abstract: |
1499 | Sets a callback that is executed when the PAL is shut down because of |
1500 | ExitProcess, TerminateProcess or PAL_Shutdown but not PAL_Terminate/Ex. |
1501 | |
1502 | NOTE: Currently only one callback can be set at a time. |
1503 | --*/ |
1504 | PALIMPORT |
1505 | VOID |
1506 | PALAPI |
1507 | PAL_SetShutdownCallback( |
1508 | IN PSHUTDOWN_CALLBACK callback) |
1509 | { |
1510 | _ASSERTE(g_shutdownCallback == nullptr); |
1511 | g_shutdownCallback = callback; |
1512 | } |
1513 | |
1514 | static bool IsCoreClrModule(const char* pModulePath) |
1515 | { |
1516 | // Strip off everything up to and including the last slash in the path to get name |
1517 | const char* pModuleName = pModulePath; |
1518 | while (strchr(pModuleName, '/') != NULL) |
1519 | { |
1520 | pModuleName = strchr(pModuleName, '/'); |
1521 | pModuleName++; // pass the slash |
1522 | } |
1523 | |
1524 | return _stricmp(pModuleName, MAKEDLLNAME_A("coreclr" )) == 0; |
1525 | } |
1526 | |
1527 | // Build the semaphore names using the PID and a value that can be used for distinguishing |
1528 | // between processes with the same PID (which ran at different times). This is to avoid |
1529 | // cases where a prior process with the same PID exited abnormally without having a chance |
1530 | // to clean up its semaphore. |
1531 | // Note to anyone modifying these names in the future: Semaphore names on OS X are limited |
1532 | // to SEM_NAME_LEN characters, including null. SEM_NAME_LEN is 31 (at least on OS X 10.11). |
1533 | // NetBSD limits semaphore names to 15 characters, including null (at least up to 7.99.25). |
1534 | // Keep 31 length for Core 1.0 RC2 compatibility |
1535 | #if defined(__NetBSD__) |
1536 | static const char* RuntimeSemaphoreNameFormat = "/clr%s%08llx" ; |
1537 | #else |
1538 | static const char* RuntimeSemaphoreNameFormat = "/clr%s%08x%016llx" ; |
1539 | #endif |
1540 | |
1541 | static const char* RuntimeStartupSemaphoreName = "st" ; |
1542 | static const char* RuntimeContinueSemaphoreName = "co" ; |
1543 | |
1544 | #if defined(__NetBSD__) |
1545 | static uint64_t HashSemaphoreName(uint64_t a, uint64_t b) |
1546 | { |
1547 | return (a ^ b) & 0xffffffff; |
1548 | } |
1549 | #else |
1550 | #define HashSemaphoreName(a,b) a,b |
1551 | #endif |
1552 | |
1553 | static const char* PipeNameFormat = "clr-debug-pipe-%d-%llu-%s" ; |
1554 | |
1555 | class PAL_RuntimeStartupHelper |
1556 | { |
1557 | LONG m_ref; |
1558 | bool m_canceled; |
1559 | PPAL_STARTUP_CALLBACK m_callback; |
1560 | PVOID m_parameter; |
1561 | DWORD m_threadId; |
1562 | HANDLE m_threadHandle; |
1563 | DWORD m_processId; |
1564 | #ifdef __APPLE__ |
1565 | char m_applicationGroupId[MAX_APPLICATION_GROUP_ID_LENGTH+1]; |
1566 | #endif // __APPLE__ |
1567 | char m_startupSemName[CLR_SEM_MAX_NAMELEN]; |
1568 | char m_continueSemName[CLR_SEM_MAX_NAMELEN]; |
1569 | |
1570 | // A value that, used in conjunction with the process ID, uniquely identifies a process. |
1571 | // See the format we use for debugger semaphore names for why this is necessary. |
1572 | UINT64 m_processIdDisambiguationKey; |
1573 | |
1574 | // Debugger waits on this semaphore and the runtime signals it on startup. |
1575 | sem_t *m_startupSem; |
1576 | |
1577 | // Debuggee waits on this semaphore and the debugger signals it after the startup callback |
1578 | // registered (m_callback) returns. |
1579 | sem_t *m_continueSem; |
1580 | |
1581 | LPCSTR GetApplicationGroupId() const |
1582 | { |
1583 | #ifdef __APPLE__ |
1584 | return m_applicationGroupId[0] == '\0' ? nullptr : m_applicationGroupId; |
1585 | #else // __APPLE__ |
1586 | return nullptr; |
1587 | #endif // __APPLE__ |
1588 | } |
1589 | |
1590 | public: |
1591 | PAL_RuntimeStartupHelper(DWORD dwProcessId, PPAL_STARTUP_CALLBACK pfnCallback, PVOID parameter) : |
1592 | m_ref(1), |
1593 | m_canceled(false), |
1594 | m_callback(pfnCallback), |
1595 | m_parameter(parameter), |
1596 | m_threadId(0), |
1597 | m_threadHandle(NULL), |
1598 | m_processId(dwProcessId), |
1599 | m_startupSem(SEM_FAILED), |
1600 | m_continueSem(SEM_FAILED) |
1601 | { |
1602 | } |
1603 | |
1604 | ~PAL_RuntimeStartupHelper() |
1605 | { |
1606 | if (m_startupSem != SEM_FAILED) |
1607 | { |
1608 | sem_close(m_startupSem); |
1609 | sem_unlink(m_startupSemName); |
1610 | } |
1611 | |
1612 | if (m_continueSem != SEM_FAILED) |
1613 | { |
1614 | sem_close(m_continueSem); |
1615 | sem_unlink(m_continueSemName); |
1616 | } |
1617 | |
1618 | if (m_threadHandle != NULL) |
1619 | { |
1620 | CloseHandle(m_threadHandle); |
1621 | } |
1622 | } |
1623 | |
1624 | LONG AddRef() |
1625 | { |
1626 | LONG ref = InterlockedIncrement(&m_ref); |
1627 | return ref; |
1628 | } |
1629 | |
1630 | LONG Release() |
1631 | { |
1632 | LONG ref = InterlockedDecrement(&m_ref); |
1633 | if (ref == 0) |
1634 | { |
1635 | InternalDelete(this); |
1636 | } |
1637 | return ref; |
1638 | } |
1639 | |
1640 | PAL_ERROR GetSemError() |
1641 | { |
1642 | PAL_ERROR pe; |
1643 | switch (errno) |
1644 | { |
1645 | case ENOENT: |
1646 | pe = ERROR_NOT_FOUND; |
1647 | break; |
1648 | case EACCES: |
1649 | pe = ERROR_INVALID_ACCESS; |
1650 | break; |
1651 | case EINVAL: |
1652 | case ENAMETOOLONG: |
1653 | pe = ERROR_INVALID_NAME; |
1654 | break; |
1655 | case ENOMEM: |
1656 | pe = ERROR_OUTOFMEMORY; |
1657 | break; |
1658 | case EEXIST: |
1659 | pe = ERROR_ALREADY_EXISTS; |
1660 | break; |
1661 | case ENOSPC: |
1662 | pe = ERROR_TOO_MANY_SEMAPHORES; |
1663 | break; |
1664 | default: |
1665 | pe = ERROR_INVALID_PARAMETER; |
1666 | break; |
1667 | } |
1668 | return pe; |
1669 | } |
1670 | |
1671 | PAL_ERROR Register(LPCWSTR lpApplicationGroupId) |
1672 | { |
1673 | CPalThread *pThread = InternalGetCurrentThread(); |
1674 | PAL_ERROR pe = NO_ERROR; |
1675 | BOOL ret; |
1676 | UnambiguousProcessDescriptor unambiguousProcessDescriptor; |
1677 | |
1678 | #ifdef __APPLE__ |
1679 | if (lpApplicationGroupId != NULL) |
1680 | { |
1681 | /* Convert to ASCII */ |
1682 | int applicationGroupIdLength = WideCharToMultiByte(CP_ACP, 0, lpApplicationGroupId, -1, m_applicationGroupId, sizeof(m_applicationGroupId), NULL, NULL); |
1683 | if (applicationGroupIdLength == 0) |
1684 | { |
1685 | pe = GetLastError(); |
1686 | TRACE("applicationGroupId: Failed to convert to multibyte string (%u)\n" , pe); |
1687 | if (pe == ERROR_INSUFFICIENT_BUFFER) |
1688 | { |
1689 | pe = ERROR_BAD_LENGTH; |
1690 | } |
1691 | goto exit; |
1692 | } |
1693 | } |
1694 | else |
1695 | { |
1696 | // Indicate that group ID is not being used |
1697 | m_applicationGroupId[0] = '\0'; |
1698 | } |
1699 | #endif // __APPLE__ |
1700 | |
1701 | // See semaphore name format for details about this value. We store it so that |
1702 | // it can be used by the cleanup code that removes the semaphore with sem_unlink. |
1703 | ret = GetProcessIdDisambiguationKey(m_processId, &m_processIdDisambiguationKey); |
1704 | |
1705 | // If GetProcessIdDisambiguationKey failed for some reason, it should set the value |
1706 | // to 0. We expect that anyone else opening the semaphore name will also fail and thus |
1707 | // will also try to use 0 as the value. |
1708 | _ASSERTE(ret == TRUE || m_processIdDisambiguationKey == 0); |
1709 | |
1710 | unambiguousProcessDescriptor.Init(m_processId, m_processIdDisambiguationKey); |
1711 | CreateSemaphoreName(m_startupSemName, RuntimeStartupSemaphoreName, unambiguousProcessDescriptor, GetApplicationGroupId()); |
1712 | CreateSemaphoreName(m_continueSemName, RuntimeContinueSemaphoreName, unambiguousProcessDescriptor, GetApplicationGroupId()); |
1713 | |
1714 | TRACE("PAL_RuntimeStartupHelper.Register creating startup '%s' continue '%s'\n" , m_startupSemName, m_continueSemName); |
1715 | |
1716 | // Create the continue semaphore first so we don't race with PAL_NotifyRuntimeStarted. This open will fail if another |
1717 | // debugger is trying to attach to this process because the name will already exist. |
1718 | m_continueSem = sem_open(m_continueSemName, O_CREAT | O_EXCL, S_IRWXU, 0); |
1719 | if (m_continueSem == SEM_FAILED) |
1720 | { |
1721 | TRACE("sem_open(continue) failed: errno is %d (%s)\n" , errno, strerror(errno)); |
1722 | pe = GetSemError(); |
1723 | goto exit; |
1724 | } |
1725 | |
1726 | // Create the debuggee startup semaphore so the runtime (debuggee) knows to wait for a debugger connection. |
1727 | m_startupSem = sem_open(m_startupSemName, O_CREAT | O_EXCL, S_IRWXU, 0); |
1728 | if (m_startupSem == SEM_FAILED) |
1729 | { |
1730 | TRACE("sem_open(startup) failed: errno is %d (%s)\n" , errno, strerror(errno)); |
1731 | pe = GetSemError(); |
1732 | goto exit; |
1733 | } |
1734 | |
1735 | // Add a reference for the thread handler |
1736 | AddRef(); |
1737 | |
1738 | pe = InternalCreateThread( |
1739 | pThread, |
1740 | NULL, |
1741 | 0, |
1742 | ::StartupHelperThread, |
1743 | this, |
1744 | 0, |
1745 | UserCreatedThread, |
1746 | &m_threadId, |
1747 | &m_threadHandle); |
1748 | |
1749 | if (NO_ERROR != pe) |
1750 | { |
1751 | TRACE("InternalCreateThread failed %d\n" , pe); |
1752 | Release(); |
1753 | goto exit; |
1754 | } |
1755 | |
1756 | exit: |
1757 | return pe; |
1758 | } |
1759 | |
1760 | void Unregister() |
1761 | { |
1762 | m_canceled = true; |
1763 | |
1764 | // Tell the runtime to continue |
1765 | if (sem_post(m_continueSem) != 0) |
1766 | { |
1767 | ASSERT("sem_post(continueSem) failed: errno is %d (%s)\n" , errno, strerror(errno)); |
1768 | } |
1769 | |
1770 | // Tell the worker thread to continue |
1771 | if (sem_post(m_startupSem) != 0) |
1772 | { |
1773 | ASSERT("sem_post(startupSem) failed: errno is %d (%s)\n" , errno, strerror(errno)); |
1774 | } |
1775 | |
1776 | // Don't need to wait for the worker thread if unregister called on it |
1777 | if (m_threadId != (DWORD)THREADSilentGetCurrentThreadId()) |
1778 | { |
1779 | // Wait for work thread to exit |
1780 | if (WaitForSingleObject(m_threadHandle, INFINITE) != WAIT_OBJECT_0) |
1781 | { |
1782 | ASSERT("WaitForSingleObject\n" ); |
1783 | } |
1784 | } |
1785 | } |
1786 | |
1787 | // |
1788 | // There are a couple race conditions that need to be considered here: |
1789 | // |
1790 | // * On launch, between the fork and execv in the PAL's CreateProcess where the target process |
1791 | // may contain a coreclr module image if the debugger process is running managed code. This |
1792 | // makes just checking if the coreclr module exists not enough. |
1793 | // |
1794 | // * On launch (after the execv) or attach when the coreclr is loaded but before the DAC globals |
1795 | // table is initialized where it is too soon to use/initialize the DAC on the debugger side. |
1796 | // |
1797 | // They are both fixed by check if the one of transport pipe files has been created. |
1798 | // |
1799 | bool IsCoreClrProcessReady() |
1800 | { |
1801 | char pipeName[MAX_DEBUGGER_TRANSPORT_PIPE_NAME_LENGTH]; |
1802 | |
1803 | PAL_GetTransportPipeName(pipeName, m_processId, GetApplicationGroupId(), "in" ); |
1804 | |
1805 | struct stat buf; |
1806 | if (stat(pipeName, &buf) == 0) |
1807 | { |
1808 | TRACE("IsCoreClrProcessReady: stat(%s) SUCCEEDED\n" , pipeName); |
1809 | return true; |
1810 | } |
1811 | TRACE("IsCoreClrProcessReady: stat(%s) FAILED: errno is %d (%s)\n" , pipeName, errno, strerror(errno)); |
1812 | return false; |
1813 | } |
1814 | |
1815 | PAL_ERROR InvokeStartupCallback() |
1816 | { |
1817 | ProcessModules *listHead = NULL; |
1818 | PAL_ERROR pe = NO_ERROR; |
1819 | DWORD count; |
1820 | |
1821 | if (m_canceled) |
1822 | { |
1823 | goto exit; |
1824 | } |
1825 | |
1826 | // Enumerate all the modules in the process and invoke the callback |
1827 | // for the coreclr module if found. |
1828 | listHead = CreateProcessModules(m_processId, &count); |
1829 | if (listHead == NULL) |
1830 | { |
1831 | TRACE("CreateProcessModules failed for pid %d\n" , m_processId); |
1832 | pe = ERROR_INVALID_PARAMETER; |
1833 | goto exit; |
1834 | } |
1835 | |
1836 | for (ProcessModules *entry = listHead; entry != NULL; entry = entry->Next) |
1837 | { |
1838 | if (IsCoreClrModule(entry->Name)) |
1839 | { |
1840 | PAL_CPP_TRY |
1841 | { |
1842 | TRACE("InvokeStartupCallback executing callback %p %s\n" , entry->BaseAddress, entry->Name); |
1843 | m_callback(entry->Name, entry->BaseAddress, m_parameter); |
1844 | } |
1845 | PAL_CPP_CATCH_ALL |
1846 | { |
1847 | } |
1848 | PAL_CPP_ENDTRY |
1849 | |
1850 | // Currently only the first coreclr module in a process is supported |
1851 | break; |
1852 | } |
1853 | } |
1854 | |
1855 | exit: |
1856 | // Wake up the runtime |
1857 | if (sem_post(m_continueSem) != 0) |
1858 | { |
1859 | ASSERT("sem_post(continueSem) failed: errno is %d (%s)\n" , errno, strerror(errno)); |
1860 | } |
1861 | if (listHead != NULL) |
1862 | { |
1863 | DestroyProcessModules(listHead); |
1864 | } |
1865 | return pe; |
1866 | } |
1867 | |
1868 | void StartupHelperThread() |
1869 | { |
1870 | PAL_ERROR pe = NO_ERROR; |
1871 | |
1872 | if (IsCoreClrProcessReady()) |
1873 | { |
1874 | pe = InvokeStartupCallback(); |
1875 | } |
1876 | else { |
1877 | TRACE("sem_wait(startup)\n" ); |
1878 | |
1879 | // Wait until the coreclr runtime (debuggee) starts up |
1880 | if (sem_wait(m_startupSem) == 0) |
1881 | { |
1882 | pe = InvokeStartupCallback(); |
1883 | } |
1884 | else |
1885 | { |
1886 | TRACE("sem_wait(startup) failed: errno is %d (%s)\n" , errno, strerror(errno)); |
1887 | pe = GetSemError(); |
1888 | } |
1889 | } |
1890 | |
1891 | // Invoke the callback on errors |
1892 | if (pe != NO_ERROR && !m_canceled) |
1893 | { |
1894 | SetLastError(pe); |
1895 | m_callback(NULL, NULL, m_parameter); |
1896 | } |
1897 | } |
1898 | }; |
1899 | |
1900 | static |
1901 | DWORD |
1902 | PALAPI |
1903 | StartupHelperThread(LPVOID p) |
1904 | { |
1905 | TRACE("PAL's StartupHelperThread starting\n" ); |
1906 | |
1907 | PAL_RuntimeStartupHelper *helper = (PAL_RuntimeStartupHelper *)p; |
1908 | helper->StartupHelperThread(); |
1909 | helper->Release(); |
1910 | return 0; |
1911 | } |
1912 | |
1913 | /*++ |
1914 | PAL_RegisterForRuntimeStartup |
1915 | |
1916 | Parameters: |
1917 | dwProcessId - process id of runtime process |
1918 | lpApplicationGroupId - A string representing the application group ID of a sandboxed |
1919 | process running in Mac. Pass NULL if the process is not |
1920 | running in a sandbox and other platforms. |
1921 | pfnCallback - function to callback for coreclr module found |
1922 | parameter - data to pass to callback |
1923 | ppUnregisterToken - pointer to put PAL_UnregisterForRuntimeStartup token. |
1924 | |
1925 | Return value: |
1926 | PAL_ERROR |
1927 | |
1928 | Note: |
1929 | If the modulePath or hModule is NULL when the callback is invoked, an error occured |
1930 | and GetLastError() will return the Win32 error code. |
1931 | |
1932 | The callback is always invoked on a separate thread and this API returns immediately. |
1933 | |
1934 | Only the first coreclr module is currently supported. |
1935 | |
1936 | --*/ |
1937 | DWORD |
1938 | PALAPI |
1939 | PAL_RegisterForRuntimeStartup( |
1940 | IN DWORD dwProcessId, |
1941 | IN LPCWSTR lpApplicationGroupId, |
1942 | IN PPAL_STARTUP_CALLBACK pfnCallback, |
1943 | IN PVOID parameter, |
1944 | OUT PVOID *ppUnregisterToken) |
1945 | { |
1946 | _ASSERTE(pfnCallback != NULL); |
1947 | _ASSERTE(ppUnregisterToken != NULL); |
1948 | |
1949 | PAL_RuntimeStartupHelper *helper = InternalNew<PAL_RuntimeStartupHelper>(dwProcessId, pfnCallback, parameter); |
1950 | |
1951 | // Create the debuggee startup semaphore so the runtime (debuggee) knows to wait for |
1952 | // a debugger connection. |
1953 | PAL_ERROR pe = helper->Register(lpApplicationGroupId); |
1954 | if (NO_ERROR != pe) |
1955 | { |
1956 | helper->Release(); |
1957 | helper = NULL; |
1958 | } |
1959 | |
1960 | *ppUnregisterToken = helper; |
1961 | return pe; |
1962 | } |
1963 | |
1964 | /*++ |
1965 | PAL_UnregisterForRuntimeStartup |
1966 | |
1967 | Stops/cancels startup notification. This API can be called in the startup callback. Otherwise, |
1968 | it will block until the callback thread finishes and no more callbacks will be initiated after |
1969 | this API returns. |
1970 | |
1971 | Parameters: |
1972 | dwUnregisterToken - token from PAL_RegisterForRuntimeStartup or NULL. |
1973 | |
1974 | Return value: |
1975 | PAL_ERROR |
1976 | --*/ |
1977 | DWORD |
1978 | PALAPI |
1979 | PAL_UnregisterForRuntimeStartup( |
1980 | IN PVOID pUnregisterToken) |
1981 | { |
1982 | if (pUnregisterToken != NULL) |
1983 | { |
1984 | PAL_RuntimeStartupHelper *helper = (PAL_RuntimeStartupHelper *)pUnregisterToken; |
1985 | helper->Unregister(); |
1986 | helper->Release(); |
1987 | } |
1988 | return NO_ERROR; |
1989 | } |
1990 | |
1991 | /*++ |
1992 | PAL_NotifyRuntimeStarted |
1993 | |
1994 | Signals the debugger waiting for runtime startup notification to continue and |
1995 | waits until the debugger signals us to continue. |
1996 | |
1997 | Parameters: |
1998 | None |
1999 | |
2000 | Return value: |
2001 | TRUE - successfully launched by debugger, FALSE - not launched or some failure in the handshake |
2002 | --*/ |
2003 | BOOL |
2004 | PALAPI |
2005 | PAL_NotifyRuntimeStarted() |
2006 | { |
2007 | char startupSemName[CLR_SEM_MAX_NAMELEN]; |
2008 | char continueSemName[CLR_SEM_MAX_NAMELEN]; |
2009 | sem_t *startupSem = SEM_FAILED; |
2010 | sem_t *continueSem = SEM_FAILED; |
2011 | BOOL launched = FALSE; |
2012 | |
2013 | UINT64 processIdDisambiguationKey = 0; |
2014 | BOOL ret = GetProcessIdDisambiguationKey(gPID, &processIdDisambiguationKey); |
2015 | |
2016 | // If GetProcessIdDisambiguationKey failed for some reason, it should set the value |
2017 | // to 0. We expect that anyone else making the semaphore name will also fail and thus |
2018 | // will also try to use 0 as the value. |
2019 | _ASSERTE(ret == TRUE || processIdDisambiguationKey == 0); |
2020 | |
2021 | UnambiguousProcessDescriptor unambiguousProcessDescriptor(gPID, processIdDisambiguationKey); |
2022 | LPCSTR applicationGroupId = |
2023 | #ifdef __APPLE__ |
2024 | PAL_GetApplicationGroupId(); |
2025 | #else |
2026 | nullptr; |
2027 | #endif |
2028 | CreateSemaphoreName(startupSemName, RuntimeStartupSemaphoreName, unambiguousProcessDescriptor, applicationGroupId); |
2029 | CreateSemaphoreName(continueSemName, RuntimeContinueSemaphoreName, unambiguousProcessDescriptor, applicationGroupId); |
2030 | |
2031 | TRACE("PAL_NotifyRuntimeStarted opening continue '%s' startup '%s'\n" , continueSemName, startupSemName); |
2032 | |
2033 | // Open the debugger startup semaphore. If it doesn't exists, then we do nothing and return |
2034 | startupSem = sem_open(startupSemName, 0); |
2035 | if (startupSem == SEM_FAILED) |
2036 | { |
2037 | TRACE("sem_open(%s) failed: %d (%s)\n" , startupSemName, errno, strerror(errno)); |
2038 | goto exit; |
2039 | } |
2040 | |
2041 | continueSem = sem_open(continueSemName, 0); |
2042 | if (continueSem == SEM_FAILED) |
2043 | { |
2044 | ASSERT("sem_open(%s) failed: %d (%s)\n" , continueSemName, errno, strerror(errno)); |
2045 | goto exit; |
2046 | } |
2047 | |
2048 | // Wake up the debugger waiting for startup |
2049 | if (sem_post(startupSem) != 0) |
2050 | { |
2051 | ASSERT("sem_post(startupSem) failed: errno is %d (%s)\n" , errno, strerror(errno)); |
2052 | goto exit; |
2053 | } |
2054 | |
2055 | // Now wait until the debugger's runtime startup notification is finished |
2056 | if (sem_wait(continueSem) != 0) |
2057 | { |
2058 | ASSERT("sem_wait(continueSem) failed: errno is %d (%s)\n" , errno, strerror(errno)); |
2059 | goto exit; |
2060 | } |
2061 | |
2062 | // Returns that the runtime was successfully launched for debugging |
2063 | launched = TRUE; |
2064 | |
2065 | exit: |
2066 | if (startupSem != SEM_FAILED) |
2067 | { |
2068 | sem_close(startupSem); |
2069 | } |
2070 | if (continueSem != SEM_FAILED) |
2071 | { |
2072 | sem_close(continueSem); |
2073 | } |
2074 | return launched; |
2075 | } |
2076 | |
2077 | #ifdef __APPLE__ |
2078 | LPCSTR |
2079 | PALAPI |
2080 | PAL_GetApplicationGroupId() |
2081 | { |
2082 | return gApplicationGroupId; |
2083 | } |
2084 | |
2085 | // We use 7bits from each byte, so this computes the extra size we need to encode a given byte count |
2086 | constexpr int GetExtraEncodedAreaSize(UINT rawByteCount) |
2087 | { |
2088 | return (rawByteCount+6)/7; |
2089 | } |
2090 | const int SEMAPHORE_ENCODED_NAME_EXTRA_LENGTH = GetExtraEncodedAreaSize(sizeof(UnambiguousProcessDescriptor)); |
2091 | const int SEMAPHORE_ENCODED_NAME_LENGTH = |
2092 | sizeof(UnambiguousProcessDescriptor) + /* For process ID + disambiguationKey */ |
2093 | SEMAPHORE_ENCODED_NAME_EXTRA_LENGTH; /* For base 255 extra encoding space */ |
2094 | |
2095 | static_assert_no_msg(MAX_APPLICATION_GROUP_ID_LENGTH |
2096 | + 1 /* For / */ |
2097 | + 2 /* For ST/CO name prefix */ |
2098 | + SEMAPHORE_ENCODED_NAME_LENGTH /* For encoded name string */ |
2099 | + 1 /* For null terminator */ |
2100 | <= CLR_SEM_MAX_NAMELEN); |
2101 | |
2102 | // In Apple we are limited by the length of the semaphore name. However, the characters which can be used in the |
2103 | // name can be anything between 1 and 255 (since 0 will terminate the string). Thus, we encode each byte b in |
2104 | // unambiguousProcessDescriptor as b ? b : 1, and mark an additional bit indicating if b is 0 or not. We use 7 bits |
2105 | // out of each extra byte so 1 bit will always be '1'. This will ensure that our extra bytes are never 0 which are |
2106 | // invalid characters. Thus we need an extra byte for each 7 input bytes. Hence, only extra 2 bytes for the name string. |
2107 | void EncodeSemaphoreName(char *encodedSemName, const UnambiguousProcessDescriptor& unambiguousProcessDescriptor) |
2108 | { |
2109 | const unsigned char *buffer = (const unsigned char *)&unambiguousProcessDescriptor; |
2110 | char *extraEncodingBits = encodedSemName + sizeof(UnambiguousProcessDescriptor); |
2111 | |
2112 | // Reset the extra encoding bit area |
2113 | for (int i=0; i<SEMAPHORE_ENCODED_NAME_EXTRA_LENGTH; i++) |
2114 | { |
2115 | extraEncodingBits[i] = 0x80; |
2116 | } |
2117 | |
2118 | // Encode each byte in unambiguousProcessDescriptor |
2119 | for (int i=0; i<sizeof(UnambiguousProcessDescriptor); i++) |
2120 | { |
2121 | unsigned char b = buffer[i]; |
2122 | encodedSemName[i] = b ? b : 1; |
2123 | extraEncodingBits[i/7] |= (b ? 0 : 1) << (i%7); |
2124 | } |
2125 | } |
2126 | #endif |
2127 | |
2128 | void CreateSemaphoreName(char semName[CLR_SEM_MAX_NAMELEN], LPCSTR semaphoreName, const UnambiguousProcessDescriptor& unambiguousProcessDescriptor, LPCSTR applicationGroupId) |
2129 | { |
2130 | int length = 0; |
2131 | |
2132 | #ifdef __APPLE__ |
2133 | if (applicationGroupId != nullptr) |
2134 | { |
2135 | // We assume here that applicationGroupId has been already tested for length and is less than MAX_APPLICATION_GROUP_ID_LENGTH |
2136 | length = sprintf_s(semName, CLR_SEM_MAX_NAMELEN, "%s/%s" , applicationGroupId, semaphoreName); |
2137 | _ASSERTE(length > 0 && length < CLR_SEM_MAX_NAMELEN); |
2138 | |
2139 | EncodeSemaphoreName(semName+length, unambiguousProcessDescriptor); |
2140 | length += SEMAPHORE_ENCODED_NAME_LENGTH; |
2141 | semName[length] = 0; |
2142 | } |
2143 | else |
2144 | #endif // __APPLE__ |
2145 | { |
2146 | length = sprintf_s( |
2147 | semName, |
2148 | CLR_SEM_MAX_NAMELEN, |
2149 | RuntimeSemaphoreNameFormat, |
2150 | semaphoreName, |
2151 | HashSemaphoreName(unambiguousProcessDescriptor.m_processId, unambiguousProcessDescriptor.m_disambiguationKey)); |
2152 | } |
2153 | |
2154 | _ASSERTE(length > 0 && length < CLR_SEM_MAX_NAMELEN ); |
2155 | } |
2156 | |
2157 | /*++ |
2158 | Function: |
2159 | GetProcessIdDisambiguationKey |
2160 | |
2161 | Get a numeric value that can be used to disambiguate between processes with the same PID, |
2162 | provided that one of them is still running. The numeric value can mean different things |
2163 | on different platforms, so it should not be used for any other purpose. Under the hood, |
2164 | it is implemented based on the creation time of the process. |
2165 | --*/ |
2166 | BOOL |
2167 | GetProcessIdDisambiguationKey(DWORD processId, UINT64 *disambiguationKey) |
2168 | { |
2169 | if (disambiguationKey == nullptr) |
2170 | { |
2171 | _ASSERTE(!"disambiguationKey argument cannot be null!" ); |
2172 | return FALSE; |
2173 | } |
2174 | |
2175 | *disambiguationKey = 0; |
2176 | |
2177 | #if defined(__APPLE__) |
2178 | |
2179 | // On OS X, we return the process start time expressed in Unix time (the number of seconds |
2180 | // since the start of the Unix epoch). |
2181 | struct kinfo_proc info = {}; |
2182 | size_t size = sizeof(info); |
2183 | int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, processId }; |
2184 | int ret = ::sysctl(mib, sizeof(mib)/sizeof(*mib), &info, &size, nullptr, 0); |
2185 | |
2186 | if (ret == 0) |
2187 | { |
2188 | timeval procStartTime = info.kp_proc.p_starttime; |
2189 | long secondsSinceEpoch = procStartTime.tv_sec; |
2190 | |
2191 | *disambiguationKey = secondsSinceEpoch; |
2192 | return TRUE; |
2193 | } |
2194 | else |
2195 | { |
2196 | _ASSERTE(!"Failed to get start time of a process." ); |
2197 | return FALSE; |
2198 | } |
2199 | |
2200 | #elif defined(__NetBSD__) |
2201 | |
2202 | // On NetBSD, we return the process start time expressed in Unix time (the number of seconds |
2203 | // since the start of the Unix epoch). |
2204 | kvm_t *kd; |
2205 | int cnt; |
2206 | struct kinfo_proc2 *info; |
2207 | |
2208 | kd = kvm_open(nullptr, nullptr, nullptr, KVM_NO_FILES, "kvm_open" ); |
2209 | if (kd == nullptr) |
2210 | { |
2211 | _ASSERTE(!"Failed to get start time of a process." ); |
2212 | return FALSE; |
2213 | } |
2214 | |
2215 | info = kvm_getproc2(kd, KERN_PROC_PID, processId, sizeof(struct kinfo_proc2), &cnt); |
2216 | if (info == nullptr || cnt < 1) |
2217 | { |
2218 | kvm_close(kd); |
2219 | _ASSERTE(!"Failed to get start time of a process." ); |
2220 | return FALSE; |
2221 | } |
2222 | |
2223 | kvm_close(kd); |
2224 | |
2225 | long secondsSinceEpoch = info->p_ustart_sec; |
2226 | *disambiguationKey = secondsSinceEpoch; |
2227 | |
2228 | return TRUE; |
2229 | |
2230 | #elif HAVE_PROCFS_STAT |
2231 | |
2232 | // Here we read /proc/<pid>/stat file to get the start time for the process. |
2233 | // We return this value (which is expressed in jiffies since boot time). |
2234 | |
2235 | // Making something like: /proc/123/stat |
2236 | char statFileName[64]; |
2237 | |
2238 | INDEBUG(int chars = ) |
2239 | snprintf(statFileName, sizeof(statFileName), "/proc/%d/stat" , processId); |
2240 | _ASSERTE(chars > 0 && chars <= sizeof(statFileName)); |
2241 | |
2242 | FILE *statFile = fopen(statFileName, "r" ); |
2243 | if (statFile == nullptr) |
2244 | { |
2245 | TRACE("GetProcessIdDisambiguationKey: fopen() FAILED" ); |
2246 | SetLastError(ERROR_INVALID_HANDLE); |
2247 | return FALSE; |
2248 | } |
2249 | |
2250 | char *line = nullptr; |
2251 | size_t lineLen = 0; |
2252 | if (getline(&line, &lineLen, statFile) == -1) |
2253 | { |
2254 | TRACE("GetProcessIdDisambiguationKey: getline() FAILED" ); |
2255 | SetLastError(ERROR_INVALID_HANDLE); |
2256 | return FALSE; |
2257 | } |
2258 | |
2259 | unsigned long long starttime; |
2260 | |
2261 | // According to `man proc`, the second field in the stat file is the filename of the executable, |
2262 | // in parentheses. Tokenizing the stat file using spaces as separators breaks when that name |
2263 | // has spaces in it, so we start using sscanf_s after skipping everything up to and including the |
2264 | // last closing paren and the space after it. |
2265 | char *scanStartPosition = strrchr(line, ')') + 2; |
2266 | |
2267 | // All the format specifiers for the fields in the stat file are provided by 'man proc'. |
2268 | int sscanfRet = sscanf_s(scanStartPosition, |
2269 | "%*c %*d %*d %*d %*d %*d %*u %*lu %*lu %*lu %*lu %*lu %*lu %*ld %*ld %*ld %*ld %*ld %*ld %llu \n" , |
2270 | &starttime); |
2271 | |
2272 | if (sscanfRet != 1) |
2273 | { |
2274 | _ASSERTE(!"Failed to parse stat file contents with sscanf_s." ); |
2275 | return FALSE; |
2276 | } |
2277 | |
2278 | free(line); |
2279 | fclose(statFile); |
2280 | |
2281 | *disambiguationKey = starttime; |
2282 | return TRUE; |
2283 | |
2284 | #else |
2285 | // If this is not OS X and we don't have /proc, we just return FALSE. |
2286 | WARN("GetProcessIdDisambiguationKey was called but is not implemented on this platform!" ); |
2287 | return FALSE; |
2288 | #endif |
2289 | } |
2290 | |
2291 | /*++ |
2292 | Function: |
2293 | PAL_GetTransportPipeName |
2294 | |
2295 | Builds the transport pipe names from the process id. |
2296 | --*/ |
2297 | VOID |
2298 | PALAPI |
2299 | PAL_GetTransportPipeName( |
2300 | OUT char *name, |
2301 | IN DWORD id, |
2302 | IN const char *applicationGroupId, |
2303 | IN const char *suffix) |
2304 | { |
2305 | *name = '\0'; |
2306 | DWORD dwRetVal = 0; |
2307 | UINT64 disambiguationKey = 0; |
2308 | PathCharString formatBufferString; |
2309 | BOOL ret = GetProcessIdDisambiguationKey(id, &disambiguationKey); |
2310 | char *formatBuffer = formatBufferString.OpenStringBuffer(MAX_DEBUGGER_TRANSPORT_PIPE_NAME_LENGTH-1); |
2311 | if (formatBuffer == nullptr) |
2312 | { |
2313 | ERROR("Out Of Memory" ); |
2314 | return; |
2315 | } |
2316 | |
2317 | // If GetProcessIdDisambiguationKey failed for some reason, it should set the value |
2318 | // to 0. We expect that anyone else making the pipe name will also fail and thus will |
2319 | // also try to use 0 as the value. |
2320 | _ASSERTE(ret == TRUE || disambiguationKey == 0); |
2321 | #ifdef __APPLE__ |
2322 | if (nullptr != applicationGroupId) |
2323 | { |
2324 | // Verify the length of the application group ID |
2325 | int applicationGroupIdLength = strlen(applicationGroupId); |
2326 | if (applicationGroupIdLength > MAX_APPLICATION_GROUP_ID_LENGTH) |
2327 | { |
2328 | ERROR("The length of applicationGroupId is larger than MAX_APPLICATION_GROUP_ID_LENGTH" ); |
2329 | return; |
2330 | } |
2331 | |
2332 | // In sandbox, all IPC files (locks, pipes) should be written to the application group |
2333 | // container. The path returned by GetTempPathA will be unique for each process and cannot |
2334 | // be used for IPC between two different processes |
2335 | if (!GetApplicationContainerFolder(formatBufferString, applicationGroupId, applicationGroupIdLength)) |
2336 | { |
2337 | ERROR("Out Of Memory" ); |
2338 | return; |
2339 | } |
2340 | |
2341 | // Verify the size of the path won't exceed maximum allowed size |
2342 | if (formatBufferString.GetCount() >= MAX_DEBUGGER_TRANSPORT_PIPE_NAME_LENGTH) |
2343 | { |
2344 | ERROR("GetApplicationContainerFolder returned a path that was larger than MAX_DEBUGGER_TRANSPORT_PIPE_NAME_LENGTH" ); |
2345 | return; |
2346 | } |
2347 | } |
2348 | else |
2349 | #endif // __APPLE__ |
2350 | { |
2351 | // Get a temp file location |
2352 | dwRetVal = ::GetTempPathA(MAX_DEBUGGER_TRANSPORT_PIPE_NAME_LENGTH, formatBuffer); |
2353 | if (dwRetVal == 0) |
2354 | { |
2355 | ERROR("GetTempPath failed (0x%08x)" , ::GetLastError()); |
2356 | return; |
2357 | } |
2358 | if (dwRetVal > MAX_DEBUGGER_TRANSPORT_PIPE_NAME_LENGTH) |
2359 | { |
2360 | ERROR("GetTempPath returned a path that was larger than MAX_DEBUGGER_TRANSPORT_PIPE_NAME_LENGTH" ); |
2361 | return; |
2362 | } |
2363 | } |
2364 | |
2365 | if (strncat_s(formatBuffer, MAX_DEBUGGER_TRANSPORT_PIPE_NAME_LENGTH, PipeNameFormat, strlen(PipeNameFormat)) == STRUNCATE) |
2366 | { |
2367 | ERROR("TransportPipeName was larger than MAX_DEBUGGER_TRANSPORT_PIPE_NAME_LENGTH" ); |
2368 | return; |
2369 | } |
2370 | |
2371 | int chars = snprintf(name, MAX_DEBUGGER_TRANSPORT_PIPE_NAME_LENGTH, formatBuffer, id, disambiguationKey, suffix); |
2372 | _ASSERTE(chars > 0 && chars < MAX_DEBUGGER_TRANSPORT_PIPE_NAME_LENGTH); |
2373 | } |
2374 | |
2375 | /*++ |
2376 | Function: |
2377 | GetProcessTimes |
2378 | |
2379 | See MSDN doc. |
2380 | --*/ |
2381 | BOOL |
2382 | PALAPI |
2383 | GetProcessTimes( |
2384 | IN HANDLE hProcess, |
2385 | OUT LPFILETIME lpCreationTime, |
2386 | OUT LPFILETIME lpExitTime, |
2387 | OUT LPFILETIME lpKernelTime, |
2388 | OUT LPFILETIME lpUserTime) |
2389 | { |
2390 | BOOL retval = FALSE; |
2391 | struct rusage resUsage; |
2392 | __int64 calcTime; |
2393 | const __int64 SECS_TO_NS = 1000000000; /* 10^9 */ |
2394 | const __int64 USECS_TO_NS = 1000; /* 10^3 */ |
2395 | |
2396 | |
2397 | PERF_ENTRY(GetProcessTimes); |
2398 | ENTRY("GetProcessTimes(hProcess=%p, lpExitTime=%p, lpKernelTime=%p," |
2399 | "lpUserTime=%p)\n" , |
2400 | hProcess, lpCreationTime, lpExitTime, lpKernelTime, lpUserTime ); |
2401 | |
2402 | /* Make sure hProcess is the current process, this is the only supported |
2403 | case */ |
2404 | if(PROCGetProcessIDFromHandle(hProcess)!=GetCurrentProcessId()) |
2405 | { |
2406 | ASSERT("GetProcessTimes() does not work on a process other than the " |
2407 | "current process.\n" ); |
2408 | SetLastError(ERROR_INVALID_HANDLE); |
2409 | goto GetProcessTimesExit; |
2410 | } |
2411 | |
2412 | /* First, we need to actually retrieve the relevant statistics from the |
2413 | OS */ |
2414 | if (getrusage (RUSAGE_SELF, &resUsage) == -1) |
2415 | { |
2416 | ASSERT("Unable to get resource usage information for the current " |
2417 | "process\n" ); |
2418 | SetLastError(ERROR_INTERNAL_ERROR); |
2419 | goto GetProcessTimesExit; |
2420 | } |
2421 | |
2422 | TRACE ("getrusage User: %ld sec,%ld microsec. Kernel: %ld sec,%ld" |
2423 | " microsec\n" , |
2424 | resUsage.ru_utime.tv_sec, resUsage.ru_utime.tv_usec, |
2425 | resUsage.ru_stime.tv_sec, resUsage.ru_stime.tv_usec); |
2426 | |
2427 | if (lpUserTime) |
2428 | { |
2429 | /* Get the time of user mode execution, in 100s of nanoseconds */ |
2430 | calcTime = (__int64)resUsage.ru_utime.tv_sec * SECS_TO_NS; |
2431 | calcTime += (__int64)resUsage.ru_utime.tv_usec * USECS_TO_NS; |
2432 | calcTime /= 100; /* Produce the time in 100s of ns */ |
2433 | /* Assign the time into lpUserTime */ |
2434 | lpUserTime->dwLowDateTime = (DWORD)calcTime; |
2435 | lpUserTime->dwHighDateTime = (DWORD)(calcTime >> 32); |
2436 | } |
2437 | |
2438 | if (lpKernelTime) |
2439 | { |
2440 | /* Get the time of kernel mode execution, in 100s of nanoseconds */ |
2441 | calcTime = (__int64)resUsage.ru_stime.tv_sec * SECS_TO_NS; |
2442 | calcTime += (__int64)resUsage.ru_stime.tv_usec * USECS_TO_NS; |
2443 | calcTime /= 100; /* Produce the time in 100s of ns */ |
2444 | /* Assign the time into lpUserTime */ |
2445 | lpKernelTime->dwLowDateTime = (DWORD)calcTime; |
2446 | lpKernelTime->dwHighDateTime = (DWORD)(calcTime >> 32); |
2447 | } |
2448 | |
2449 | retval = TRUE; |
2450 | |
2451 | |
2452 | GetProcessTimesExit: |
2453 | LOGEXIT("GetProcessTimes returns BOOL %d\n" , retval); |
2454 | PERF_EXIT(GetProcessTimes); |
2455 | return (retval); |
2456 | } |
2457 | |
2458 | #define FILETIME_TO_ULONGLONG(f) \ |
2459 | (((ULONGLONG)(f).dwHighDateTime << 32) | ((ULONGLONG)(f).dwLowDateTime)) |
2460 | |
2461 | /*++ |
2462 | Function: |
2463 | PAL_GetCPUBusyTime |
2464 | |
2465 | The main purpose of this function is to compute the overall CPU utilization |
2466 | for the CLR thread pool to regulate the number of I/O completion port |
2467 | worker threads. |
2468 | Since there is no consistent API on Unix to get the CPU utilization |
2469 | from a user process, getrusage and gettimeofday are used to |
2470 | compute the current process's CPU utilization instead. |
2471 | This function emulates the ThreadpoolMgr::GetCPUBusyTime_NT function in |
2472 | win32threadpool.cpp of the CLR. |
2473 | |
2474 | See MSDN doc for GetSystemTimes. |
2475 | --*/ |
2476 | INT |
2477 | PALAPI |
2478 | PAL_GetCPUBusyTime( |
2479 | IN OUT PAL_IOCP_CPU_INFORMATION *lpPrevCPUInfo) |
2480 | { |
2481 | ULONGLONG nLastRecordedCurrentTime = 0; |
2482 | ULONGLONG nLastRecordedUserTime = 0; |
2483 | ULONGLONG nLastRecordedKernelTime = 0; |
2484 | ULONGLONG nKernelTime = 0; |
2485 | ULONGLONG nUserTime = 0; |
2486 | ULONGLONG nCurrentTime = 0; |
2487 | ULONGLONG nCpuBusyTime = 0; |
2488 | ULONGLONG nCpuTotalTime = 0; |
2489 | DWORD nReading = 0; |
2490 | struct rusage resUsage; |
2491 | struct timeval tv; |
2492 | static DWORD dwNumberOfProcessors = 0; |
2493 | |
2494 | if (dwNumberOfProcessors <= 0) |
2495 | { |
2496 | SYSTEM_INFO SystemInfo; |
2497 | GetSystemInfo(&SystemInfo); |
2498 | dwNumberOfProcessors = SystemInfo.dwNumberOfProcessors; |
2499 | if (dwNumberOfProcessors <= 0) |
2500 | { |
2501 | return 0; |
2502 | } |
2503 | } |
2504 | |
2505 | if (getrusage(RUSAGE_SELF, &resUsage) == -1) |
2506 | { |
2507 | ASSERT("getrusage() failed; errno is %d (%s)\n" , errno, strerror(errno)); |
2508 | return 0; |
2509 | } |
2510 | else |
2511 | { |
2512 | nKernelTime = (ULONGLONG)resUsage.ru_stime.tv_sec*tccSecondsTo100NanoSeconds + |
2513 | resUsage.ru_stime.tv_usec*tccMicroSecondsTo100NanoSeconds; |
2514 | nUserTime = (ULONGLONG)resUsage.ru_utime.tv_sec*tccSecondsTo100NanoSeconds + |
2515 | resUsage.ru_utime.tv_usec*tccMicroSecondsTo100NanoSeconds; |
2516 | } |
2517 | |
2518 | if (gettimeofday(&tv, NULL) == -1) |
2519 | { |
2520 | ASSERT("gettimeofday() failed; errno is %d (%s)\n" , errno, strerror(errno)); |
2521 | return 0; |
2522 | } |
2523 | else |
2524 | { |
2525 | nCurrentTime = (ULONGLONG)tv.tv_sec*tccSecondsTo100NanoSeconds + |
2526 | tv.tv_usec*tccMicroSecondsTo100NanoSeconds; |
2527 | } |
2528 | |
2529 | nLastRecordedCurrentTime = FILETIME_TO_ULONGLONG(lpPrevCPUInfo->LastRecordedTime.ftLastRecordedCurrentTime); |
2530 | nLastRecordedUserTime = FILETIME_TO_ULONGLONG(lpPrevCPUInfo->ftLastRecordedUserTime); |
2531 | nLastRecordedKernelTime = FILETIME_TO_ULONGLONG(lpPrevCPUInfo->ftLastRecordedKernelTime); |
2532 | |
2533 | if (nCurrentTime > nLastRecordedCurrentTime) |
2534 | { |
2535 | nCpuTotalTime = (nCurrentTime - nLastRecordedCurrentTime); |
2536 | #if HAVE_THREAD_SELF || HAVE__LWP_SELF || HAVE_VM_READ |
2537 | // For systems that run multiple threads of a process on multiple processors, |
2538 | // the accumulated userTime and kernelTime of this process may exceed |
2539 | // the elapsed time. In this case, the cpuTotalTime needs to be adjusted |
2540 | // according to number of processors so that the cpu utilization |
2541 | // will not be greater than 100. |
2542 | nCpuTotalTime *= dwNumberOfProcessors; |
2543 | #endif // HAVE_THREAD_SELF || HAVE__LWP_SELF || HAVE_VM_READ |
2544 | } |
2545 | |
2546 | if (nUserTime >= nLastRecordedUserTime && |
2547 | nKernelTime >= nLastRecordedKernelTime) |
2548 | { |
2549 | nCpuBusyTime = |
2550 | (nUserTime - nLastRecordedUserTime)+ |
2551 | (nKernelTime - nLastRecordedKernelTime); |
2552 | } |
2553 | |
2554 | if (nCpuTotalTime > 0 && nCpuBusyTime > 0) |
2555 | { |
2556 | nReading = (DWORD)((nCpuBusyTime*100)/nCpuTotalTime); |
2557 | TRACE("PAL_GetCPUBusyTime: nCurrentTime=%lld, nKernelTime=%lld, nUserTime=%lld, nReading=%d\n" , |
2558 | nCurrentTime, nKernelTime, nUserTime, nReading); |
2559 | } |
2560 | |
2561 | if (nReading > 100) |
2562 | { |
2563 | ERROR("cpu utilization(%d) > 100\n" , nReading); |
2564 | } |
2565 | |
2566 | lpPrevCPUInfo->LastRecordedTime.ftLastRecordedCurrentTime.dwLowDateTime = (DWORD)nCurrentTime; |
2567 | lpPrevCPUInfo->LastRecordedTime.ftLastRecordedCurrentTime.dwHighDateTime = (DWORD)(nCurrentTime >> 32); |
2568 | |
2569 | lpPrevCPUInfo->ftLastRecordedUserTime.dwLowDateTime = (DWORD)nUserTime; |
2570 | lpPrevCPUInfo->ftLastRecordedUserTime.dwHighDateTime = (DWORD)(nUserTime >> 32); |
2571 | |
2572 | lpPrevCPUInfo->ftLastRecordedKernelTime.dwLowDateTime = (DWORD)nKernelTime; |
2573 | lpPrevCPUInfo->ftLastRecordedKernelTime.dwHighDateTime = (DWORD)(nKernelTime >> 32); |
2574 | |
2575 | return (DWORD)nReading; |
2576 | } |
2577 | |
2578 | /*++ |
2579 | Function: |
2580 | GetCommandLineW |
2581 | |
2582 | See MSDN doc. |
2583 | --*/ |
2584 | LPWSTR |
2585 | PALAPI |
2586 | GetCommandLineW( |
2587 | VOID) |
2588 | { |
2589 | PERF_ENTRY(GetCommandLineW); |
2590 | ENTRY("GetCommandLineW()\n" ); |
2591 | |
2592 | LPWSTR lpwstr = g_lpwstrCmdLine ? g_lpwstrCmdLine : (LPWSTR)W("" ); |
2593 | |
2594 | LOGEXIT("GetCommandLineW returns LPWSTR %p (%S)\n" , |
2595 | g_lpwstrCmdLine, |
2596 | lpwstr); |
2597 | PERF_EXIT(GetCommandLineW); |
2598 | |
2599 | return lpwstr; |
2600 | } |
2601 | |
2602 | /*++ |
2603 | Function: |
2604 | OpenProcess |
2605 | |
2606 | See MSDN doc. |
2607 | |
2608 | Notes : |
2609 | dwDesiredAccess is ignored (all supported operations will be allowed) |
2610 | bInheritHandle is ignored (no inheritance) |
2611 | --*/ |
2612 | HANDLE |
2613 | PALAPI |
2614 | OpenProcess( |
2615 | DWORD dwDesiredAccess, |
2616 | BOOL bInheritHandle, |
2617 | DWORD dwProcessId) |
2618 | { |
2619 | PAL_ERROR palError; |
2620 | CPalThread *pThread; |
2621 | IPalObject *pobjProcess = NULL; |
2622 | IPalObject *pobjProcessRegistered = NULL; |
2623 | IDataLock *pDataLock; |
2624 | CProcProcessLocalData *pLocalData; |
2625 | CObjectAttributes oa; |
2626 | HANDLE hProcess = NULL; |
2627 | |
2628 | PERF_ENTRY(OpenProcess); |
2629 | ENTRY("OpenProcess(dwDesiredAccess=0x%08x, bInheritHandle=%d, " |
2630 | "dwProcessId = 0x%08x)\n" , |
2631 | dwDesiredAccess, bInheritHandle, dwProcessId ); |
2632 | |
2633 | pThread = InternalGetCurrentThread(); |
2634 | |
2635 | if (0 == dwProcessId) |
2636 | { |
2637 | palError = ERROR_INVALID_PARAMETER; |
2638 | goto OpenProcessExit; |
2639 | } |
2640 | |
2641 | palError = g_pObjectManager->AllocateObject( |
2642 | pThread, |
2643 | &otProcess, |
2644 | &oa, |
2645 | &pobjProcess |
2646 | ); |
2647 | |
2648 | if (NO_ERROR != palError) |
2649 | { |
2650 | goto OpenProcessExit; |
2651 | } |
2652 | |
2653 | palError = pobjProcess->GetProcessLocalData( |
2654 | pThread, |
2655 | WriteLock, |
2656 | &pDataLock, |
2657 | reinterpret_cast<void **>(&pLocalData) |
2658 | ); |
2659 | |
2660 | if (NO_ERROR != palError) |
2661 | { |
2662 | goto OpenProcessExit; |
2663 | } |
2664 | |
2665 | pLocalData->dwProcessId = dwProcessId; |
2666 | pDataLock->ReleaseLock(pThread, TRUE); |
2667 | |
2668 | palError = g_pObjectManager->RegisterObject( |
2669 | pThread, |
2670 | pobjProcess, |
2671 | &aotProcess, |
2672 | dwDesiredAccess, |
2673 | &hProcess, |
2674 | &pobjProcessRegistered |
2675 | ); |
2676 | |
2677 | // |
2678 | // pobjProcess was invalidated by the above call, so NULL |
2679 | // it out here |
2680 | // |
2681 | |
2682 | pobjProcess = NULL; |
2683 | |
2684 | // |
2685 | // TODO: check to see if the process actually exists? |
2686 | // |
2687 | |
2688 | OpenProcessExit: |
2689 | |
2690 | if (NULL != pobjProcess) |
2691 | { |
2692 | pobjProcess->ReleaseReference(pThread); |
2693 | } |
2694 | |
2695 | if (NULL != pobjProcessRegistered) |
2696 | { |
2697 | pobjProcessRegistered->ReleaseReference(pThread); |
2698 | } |
2699 | |
2700 | if (NO_ERROR != palError) |
2701 | { |
2702 | pThread->SetLastError(palError); |
2703 | } |
2704 | |
2705 | LOGEXIT("OpenProcess returns HANDLE %p\n" , hProcess); |
2706 | PERF_EXIT(OpenProcess); |
2707 | return hProcess; |
2708 | } |
2709 | |
2710 | /*++ |
2711 | Function: |
2712 | EnumProcessModules |
2713 | |
2714 | Abstract |
2715 | Returns a process's module list |
2716 | |
2717 | Return |
2718 | TRUE if it succeeded, FALSE otherwise |
2719 | |
2720 | Notes |
2721 | This API is tricky because the module handles are never closed/freed so there can't be any |
2722 | allocations for the module handle or name strings, etc. The "handles" are actually the base |
2723 | addresses of the modules. The module handles should only be used by GetModuleFileNameExW |
2724 | below. |
2725 | --*/ |
2726 | BOOL |
2727 | PALAPI |
2728 | EnumProcessModules( |
2729 | IN HANDLE hProcess, |
2730 | OUT HMODULE *lphModule, |
2731 | IN DWORD cb, |
2732 | OUT LPDWORD lpcbNeeded) |
2733 | { |
2734 | PERF_ENTRY(EnumProcessModules); |
2735 | ENTRY("EnumProcessModules(hProcess=0x%08x, cb=%d)\n" , hProcess, cb); |
2736 | |
2737 | BOOL result = TRUE; |
2738 | DWORD count = 0; |
2739 | ProcessModules *listHead = GetProcessModulesFromHandle(hProcess, &count); |
2740 | if (listHead != NULL) |
2741 | { |
2742 | for (ProcessModules *entry = listHead; entry != NULL; entry = entry->Next) |
2743 | { |
2744 | if (cb <= 0) |
2745 | { |
2746 | break; |
2747 | } |
2748 | cb -= sizeof(HMODULE); |
2749 | *lphModule = (HMODULE)entry->BaseAddress; |
2750 | lphModule++; |
2751 | } |
2752 | } |
2753 | else |
2754 | { |
2755 | result = FALSE; |
2756 | } |
2757 | |
2758 | if (lpcbNeeded) |
2759 | { |
2760 | // This return value isn't exactly up to spec because it should return the actual |
2761 | // number of modules in the process even if "cb" isn't big enough but for our use |
2762 | // it works just fine. |
2763 | (*lpcbNeeded) = count * sizeof(HMODULE); |
2764 | } |
2765 | |
2766 | LOGEXIT("EnumProcessModules returns %d\n" , result); |
2767 | PERF_EXIT(EnumProcessModules); |
2768 | return result; |
2769 | } |
2770 | |
2771 | /*++ |
2772 | Function: |
2773 | GetModuleFileNameExW |
2774 | |
2775 | Used only with module handles returned from EnumProcessModule (for dbgshim). |
2776 | |
2777 | --*/ |
2778 | DWORD |
2779 | PALAPI |
2780 | GetModuleFileNameExW( |
2781 | IN HANDLE hProcess, |
2782 | IN HMODULE hModule, |
2783 | OUT LPWSTR lpFilename, |
2784 | IN DWORD nSize |
2785 | ) |
2786 | { |
2787 | DWORD result = 0; |
2788 | DWORD count = 0; |
2789 | |
2790 | ProcessModules *listHead = GetProcessModulesFromHandle(hProcess, &count); |
2791 | if (listHead != NULL) |
2792 | { |
2793 | for (ProcessModules *entry = listHead; entry != NULL; entry = entry->Next) |
2794 | { |
2795 | if ((HMODULE)entry->BaseAddress == hModule) |
2796 | { |
2797 | // Convert CHAR string into WCHAR string |
2798 | result = MultiByteToWideChar(CP_ACP, 0, entry->Name, -1, lpFilename, nSize); |
2799 | break; |
2800 | } |
2801 | } |
2802 | } |
2803 | |
2804 | return result; |
2805 | } |
2806 | |
2807 | /*++ |
2808 | Function: |
2809 | GetProcessModulesFromHandle |
2810 | |
2811 | Abstract |
2812 | Returns a process's module list |
2813 | |
2814 | Return |
2815 | ProcessModules * list |
2816 | |
2817 | --*/ |
2818 | ProcessModules * |
2819 | GetProcessModulesFromHandle( |
2820 | IN HANDLE hProcess, |
2821 | OUT LPDWORD lpCount) |
2822 | { |
2823 | CPalThread* pThread = InternalGetCurrentThread(); |
2824 | CProcProcessLocalData *pLocalData = NULL; |
2825 | ProcessModules *listHead = NULL; |
2826 | IPalObject *pobjProcess = NULL; |
2827 | IDataLock *pDataLock = NULL; |
2828 | PAL_ERROR palError = NO_ERROR; |
2829 | DWORD dwProcessId = 0; |
2830 | DWORD count = 0; |
2831 | |
2832 | _ASSERTE(lpCount != NULL); |
2833 | |
2834 | if (hPseudoCurrentProcess == hProcess) |
2835 | { |
2836 | pobjProcess = g_pobjProcess; |
2837 | pobjProcess->AddReference(); |
2838 | } |
2839 | else |
2840 | { |
2841 | CAllowedObjectTypes aotProcess(otiProcess); |
2842 | |
2843 | palError = g_pObjectManager->ReferenceObjectByHandle( |
2844 | pThread, |
2845 | hProcess, |
2846 | &aotProcess, |
2847 | 0, |
2848 | &pobjProcess); |
2849 | |
2850 | if (NO_ERROR != palError) |
2851 | { |
2852 | pThread->SetLastError(ERROR_INVALID_HANDLE); |
2853 | goto exit; |
2854 | } |
2855 | } |
2856 | |
2857 | palError = pobjProcess->GetProcessLocalData( |
2858 | pThread, |
2859 | WriteLock, |
2860 | &pDataLock, |
2861 | reinterpret_cast<void **>(&pLocalData)); |
2862 | |
2863 | _ASSERTE(NO_ERROR == palError); |
2864 | |
2865 | dwProcessId = pLocalData->dwProcessId; |
2866 | listHead = pLocalData->pProcessModules; |
2867 | count = pLocalData->cProcessModules; |
2868 | |
2869 | // If the module list hasn't been created yet, create it now |
2870 | if (listHead == NULL) |
2871 | { |
2872 | listHead = CreateProcessModules(dwProcessId, &count); |
2873 | if (listHead == NULL) |
2874 | { |
2875 | pThread->SetLastError(ERROR_INVALID_PARAMETER); |
2876 | goto exit; |
2877 | } |
2878 | |
2879 | if (pLocalData != NULL) |
2880 | { |
2881 | pLocalData->pProcessModules = listHead; |
2882 | pLocalData->cProcessModules = count; |
2883 | } |
2884 | } |
2885 | |
2886 | exit: |
2887 | if (NULL != pDataLock) |
2888 | { |
2889 | pDataLock->ReleaseLock(pThread, TRUE); |
2890 | } |
2891 | if (NULL != pobjProcess) |
2892 | { |
2893 | pobjProcess->ReleaseReference(pThread); |
2894 | } |
2895 | |
2896 | *lpCount = count; |
2897 | return listHead; |
2898 | } |
2899 | |
2900 | /*++ |
2901 | Function: |
2902 | CreateProcessModules |
2903 | |
2904 | Abstract |
2905 | Returns a process's module list |
2906 | |
2907 | Return |
2908 | ProcessModules * list |
2909 | |
2910 | --*/ |
2911 | ProcessModules * |
2912 | CreateProcessModules( |
2913 | IN DWORD dwProcessId, |
2914 | OUT LPDWORD lpCount) |
2915 | { |
2916 | ProcessModules *listHead = NULL; |
2917 | _ASSERTE(lpCount != NULL); |
2918 | |
2919 | #if defined(__APPLE__) |
2920 | |
2921 | // For OS X, the "vmmap" command outputs something similar to the /proc/*/maps file so popen the |
2922 | // command and read the relevant lines: |
2923 | // |
2924 | // ... |
2925 | // ==== regions for process 347 (non-writable and writable regions are interleaved) |
2926 | // REGION TYPE START - END [ VSIZE] PRT/MAX SHRMOD REGION DETAIL |
2927 | // __TEXT 000000010446d000-0000000104475000 [ 32K] r-x/rwx SM=COW /Users/mikem/coreclr/bin/Product/OSx.x64.Debug/corerun |
2928 | // __DATA 0000000104475000-0000000104476000 [ 4K] rw-/rwx SM=PRV /Users/mikem/coreclr/bin/Product/OSx.x64.Debug/corerun |
2929 | // __LINKEDIT 0000000104476000-000000010447a000 [ 16K] r--/rwx SM=COW /Users/mikem/coreclr/bin/Product/OSx.x64.Debug/corerun |
2930 | // Kernel Alloc Once 000000010447a000-000000010447b000 [ 4K] rw-/rwx SM=PRV |
2931 | // MALLOC (admin) 000000010447b000-000000010447c000 [ 4K] r--/rwx SM=ZER |
2932 | // ... |
2933 | // MALLOC (admin) 00000001044ab000-00000001044ac000 [ 4K] r--/rwx SM=PRV |
2934 | // __TEXT 00000001044ac000-0000000104c84000 [ 8032K] r-x/rwx SM=COW /Users/mikem/coreclr/bin/Product/OSx.x64.Debug/libcoreclr.dylib |
2935 | // __TEXT 0000000104c84000-0000000104c85000 [ 4K] rwx/rwx SM=PRV /Users/mikem/coreclr/bin/Product/OSx.x64.Debug/libcoreclr.dylib |
2936 | // __TEXT 0000000104c85000-000000010513b000 [ 4824K] r-x/rwx SM=COW /Users/mikem/coreclr/bin/Product/OSx.x64.Debug/libcoreclr.dylib |
2937 | // __TEXT 000000010513b000-000000010513c000 [ 4K] rwx/rwx SM=PRV /Users/mikem/coreclr/bin/Product/OSx.x64.Debug/libcoreclr.dylib |
2938 | // __TEXT 000000010513c000-000000010516f000 [ 204K] r-x/rwx SM=COW /Users/mikem/coreclr/bin/Product/OSx.x64.Debug/libcoreclr.dylib |
2939 | // __DATA 000000010516f000-00000001051ce000 [ 380K] rw-/rwx SM=COW /Users/mikem/coreclr/bin/Product/OSx.x64.Debug/libcoreclr.dylib |
2940 | // __DATA 00000001051ce000-00000001051fa000 [ 176K] rw-/rwx SM=PRV /Users/mikem/coreclr/bin/Product/OSx.x64.Debug/libcoreclr.dylib |
2941 | // __LINKEDIT 00000001051fa000-0000000105bac000 [ 9928K] r--/rwx SM=COW /Users/mikem/coreclr/bin/Product/OSx.x64.Debug/libcoreclr.dylib |
2942 | // VM_ALLOCATE 0000000105bac000-0000000105bad000 [ 4K] r--/rw- SM=SHM |
2943 | // MALLOC (admin) 0000000105bad000-0000000105bae000 [ 4K] r--/rwx SM=ZER |
2944 | // MALLOC 0000000105bae000-0000000105baf000 [ 4K] rw-/rwx SM=ZER |
2945 | |
2946 | // OS X Sierra (10.12.4 Beta) |
2947 | // REGION TYPE START - END [ VSIZE RSDNT DIRTY SWAP] PRT/MAX SHRMOD PURGE REGION DETAIL |
2948 | // Stack 00007fff5a930000-00007fff5b130000 [ 8192K 32K 32K 0K] rw-/rwx SM=PRV thread 0 |
2949 | // __TEXT 00007fffa4a0b000-00007fffa4a0d000 [ 8K 8K 0K 0K] r-x/r-x SM=COW /usr/lib/libSystem.B.dylib |
2950 | // __TEXT 00007fffa4bbe000-00007fffa4c15000 [ 348K 348K 0K 0K] r-x/r-x SM=COW /usr/lib/libc++.1.dylib |
2951 | |
2952 | // NOTE: the module path can have spaces in the name |
2953 | // __TEXT 0000000196220000-00000001965b4000 [ 3664K 2340K 0K 0K] r-x/rwx SM=COW /Volumes/Builds/builds/devmain/rawproduct/debug/build/out/Applications/Microsoft Excel.app/Contents/SharedSupport/PowerQuery/libcoreclr.dylib |
2954 | char *line = NULL; |
2955 | size_t lineLen = 0; |
2956 | int count = 0; |
2957 | ssize_t read; |
2958 | |
2959 | char vmmapCommand[100]; |
2960 | int chars = snprintf(vmmapCommand, sizeof(vmmapCommand), "/usr/bin/vmmap -interleaved %d -wide" , dwProcessId); |
2961 | _ASSERTE(chars > 0 && chars <= sizeof(vmmapCommand)); |
2962 | |
2963 | FILE *vmmapFile = popen(vmmapCommand, "r" ); |
2964 | if (vmmapFile == NULL) |
2965 | { |
2966 | goto exit; |
2967 | } |
2968 | |
2969 | // Reading maps file line by line |
2970 | while ((read = getline(&line, &lineLen, vmmapFile)) != -1) |
2971 | { |
2972 | void *startAddress, *endAddress; |
2973 | char moduleName[PATH_MAX]; |
2974 | |
2975 | if (sscanf_s(line, "__TEXT %p-%p [ %*[0-9K ]] %*[-/rwxsp] SM=%*[A-Z] %[^\n]" , &startAddress, &endAddress, moduleName, _countof(moduleName)) == 3) |
2976 | { |
2977 | bool dup = false; |
2978 | for (ProcessModules *entry = listHead; entry != NULL; entry = entry->Next) |
2979 | { |
2980 | if (strcmp(moduleName, entry->Name) == 0) |
2981 | { |
2982 | dup = true; |
2983 | break; |
2984 | } |
2985 | } |
2986 | |
2987 | if (!dup) |
2988 | { |
2989 | int cbModuleName = strlen(moduleName) + 1; |
2990 | ProcessModules *entry = (ProcessModules *)InternalMalloc(sizeof(ProcessModules) + cbModuleName); |
2991 | if (entry == NULL) |
2992 | { |
2993 | DestroyProcessModules(listHead); |
2994 | listHead = NULL; |
2995 | count = 0; |
2996 | break; |
2997 | } |
2998 | strcpy_s(entry->Name, cbModuleName, moduleName); |
2999 | entry->BaseAddress = startAddress; |
3000 | entry->Next = listHead; |
3001 | listHead = entry; |
3002 | count++; |
3003 | } |
3004 | } |
3005 | } |
3006 | |
3007 | *lpCount = count; |
3008 | |
3009 | free(line); // We didn't allocate line, but as per contract of getline we should free it |
3010 | pclose(vmmapFile); |
3011 | exit: |
3012 | |
3013 | #elif HAVE_PROCFS_MAPS |
3014 | |
3015 | // Here we read /proc/<pid>/maps file in order to parse it and figure out what it says |
3016 | // about a library we are looking for. This file looks something like this: |
3017 | // |
3018 | // [address] [perms] [offset] [dev] [inode] [pathname] - HEADER is not preset in an actual file |
3019 | // |
3020 | // 35b1800000-35b1820000 r-xp 00000000 08:02 135522 /usr/lib64/ld-2.15.so |
3021 | // 35b1a1f000-35b1a20000 r--p 0001f000 08:02 135522 /usr/lib64/ld-2.15.so |
3022 | // 35b1a20000-35b1a21000 rw-p 00020000 08:02 135522 /usr/lib64/ld-2.15.so |
3023 | // 35b1a21000-35b1a22000 rw-p 00000000 00:00 0 [heap] |
3024 | // 35b1c00000-35b1dac000 r-xp 00000000 08:02 135870 /usr/lib64/libc-2.15.so |
3025 | // 35b1dac000-35b1fac000 ---p 001ac000 08:02 135870 /usr/lib64/libc-2.15.so |
3026 | // 35b1fac000-35b1fb0000 r--p 001ac000 08:02 135870 /usr/lib64/libc-2.15.so |
3027 | // 35b1fb0000-35b1fb2000 rw-p 001b0000 08:02 135870 /usr/lib64/libc-2.15.so |
3028 | |
3029 | // Making something like: /proc/123/maps |
3030 | char mapFileName[100]; |
3031 | char *line = NULL; |
3032 | size_t lineLen = 0; |
3033 | int count = 0; |
3034 | ssize_t read; |
3035 | |
3036 | INDEBUG(int chars = ) |
3037 | snprintf(mapFileName, sizeof(mapFileName), "/proc/%d/maps" , dwProcessId); |
3038 | _ASSERTE(chars > 0 && chars <= sizeof(mapFileName)); |
3039 | |
3040 | FILE *mapsFile = fopen(mapFileName, "r" ); |
3041 | if (mapsFile == NULL) |
3042 | { |
3043 | goto exit; |
3044 | } |
3045 | |
3046 | // Reading maps file line by line |
3047 | while ((read = getline(&line, &lineLen, mapsFile)) != -1) |
3048 | { |
3049 | void *startAddress, *endAddress, *offset; |
3050 | int devHi, devLo, inode; |
3051 | char moduleName[PATH_MAX]; |
3052 | |
3053 | if (sscanf_s(line, "%p-%p %*[-rwxsp] %p %x:%x %d %s\n" , &startAddress, &endAddress, &offset, &devHi, &devLo, &inode, moduleName, _countof(moduleName)) == 7) |
3054 | { |
3055 | if (inode != 0) |
3056 | { |
3057 | bool dup = false; |
3058 | for (ProcessModules *entry = listHead; entry != NULL; entry = entry->Next) |
3059 | { |
3060 | if (strcmp(moduleName, entry->Name) == 0) |
3061 | { |
3062 | dup = true; |
3063 | break; |
3064 | } |
3065 | } |
3066 | |
3067 | if (!dup) |
3068 | { |
3069 | int cbModuleName = strlen(moduleName) + 1; |
3070 | ProcessModules *entry = (ProcessModules *)InternalMalloc(sizeof(ProcessModules) + cbModuleName); |
3071 | if (entry == NULL) |
3072 | { |
3073 | DestroyProcessModules(listHead); |
3074 | listHead = NULL; |
3075 | count = 0; |
3076 | break; |
3077 | } |
3078 | strcpy_s(entry->Name, cbModuleName, moduleName); |
3079 | entry->BaseAddress = startAddress; |
3080 | entry->Next = listHead; |
3081 | listHead = entry; |
3082 | count++; |
3083 | } |
3084 | } |
3085 | } |
3086 | } |
3087 | |
3088 | *lpCount = count; |
3089 | |
3090 | free(line); // We didn't allocate line, but as per contract of getline we should free it |
3091 | fclose(mapsFile); |
3092 | exit: |
3093 | |
3094 | #else |
3095 | _ASSERTE(!"Not implemented on this platform" ); |
3096 | #endif |
3097 | return listHead; |
3098 | } |
3099 | |
3100 | /*++ |
3101 | Function: |
3102 | DestroyProcessModules |
3103 | |
3104 | Abstract |
3105 | Cleans up the process module table. |
3106 | |
3107 | Return |
3108 | None |
3109 | |
3110 | --*/ |
3111 | VOID |
3112 | DestroyProcessModules(IN ProcessModules *listHead) |
3113 | { |
3114 | for (ProcessModules *entry = listHead; entry != NULL; ) |
3115 | { |
3116 | ProcessModules *next = entry->Next; |
3117 | free(entry); |
3118 | entry = next; |
3119 | } |
3120 | } |
3121 | |
3122 | /*++ |
3123 | Function |
3124 | PROCNotifyProcessShutdown |
3125 | |
3126 | Calls the abort handler to do any shutdown cleanup. Call be called |
3127 | from the unhandled native exception handler. |
3128 | |
3129 | (no return value) |
3130 | --*/ |
3131 | __attribute__((destructor)) |
3132 | VOID |
3133 | PROCNotifyProcessShutdown() |
3134 | { |
3135 | // Call back into the coreclr to clean up the debugger transport pipes |
3136 | PSHUTDOWN_CALLBACK callback = InterlockedExchangePointer(&g_shutdownCallback, NULL); |
3137 | if (callback != NULL) |
3138 | { |
3139 | callback(); |
3140 | } |
3141 | } |
3142 | |
3143 | /*++ |
3144 | Function |
3145 | PROCAbortInitialize() |
3146 | |
3147 | Abstract |
3148 | Initialize the process abort crash dump program file path and |
3149 | name. Doing all of this ahead of time so nothing is allocated |
3150 | or copied in PROCAbort/signal handler. |
3151 | |
3152 | Return |
3153 | TRUE - succeeds, FALSE - fails |
3154 | |
3155 | --*/ |
3156 | BOOL |
3157 | PROCAbortInitialize() |
3158 | { |
3159 | char* enabled = getenv("COMPlus_DbgEnableMiniDump" ); |
3160 | if (enabled != nullptr && _stricmp(enabled, "1" ) == 0) |
3161 | { |
3162 | if (g_szCoreCLRPath == nullptr) |
3163 | { |
3164 | return FALSE; |
3165 | } |
3166 | const char* DumpGeneratorName = "createdump" ; |
3167 | int programLen = strlen(g_szCoreCLRPath) + strlen(DumpGeneratorName) + 1; |
3168 | char* program = (char*)InternalMalloc(programLen); |
3169 | if (program == nullptr) |
3170 | { |
3171 | return FALSE; |
3172 | } |
3173 | if (strcpy_s(program, programLen, g_szCoreCLRPath) != SAFECRT_SUCCESS) |
3174 | { |
3175 | return FALSE; |
3176 | } |
3177 | char *last = strrchr(program, '/'); |
3178 | if (last != nullptr) |
3179 | { |
3180 | *(last + 1) = '\0'; |
3181 | } |
3182 | else |
3183 | { |
3184 | program[0] = '\0'; |
3185 | } |
3186 | if (strcat_s(program, programLen, DumpGeneratorName) != SAFECRT_SUCCESS) |
3187 | { |
3188 | return FALSE; |
3189 | } |
3190 | char* pidarg = (char*)InternalMalloc(128); |
3191 | if (pidarg == nullptr) |
3192 | { |
3193 | return FALSE; |
3194 | } |
3195 | if (sprintf_s(pidarg, 128, "%d" , gPID) == -1) |
3196 | { |
3197 | return FALSE; |
3198 | } |
3199 | const char** argv = (const char**)g_argvCreateDump; |
3200 | *argv++ = program; |
3201 | |
3202 | char* envvar = getenv("COMPlus_DbgMiniDumpName" ); |
3203 | if (envvar != nullptr) |
3204 | { |
3205 | *argv++ = "--name" ; |
3206 | *argv++ = envvar; |
3207 | } |
3208 | |
3209 | envvar = getenv("COMPlus_DbgMiniDumpType" ); |
3210 | if (envvar != nullptr) |
3211 | { |
3212 | if (strcmp(envvar, "1" ) == 0) |
3213 | { |
3214 | *argv++ = "--normal" ; |
3215 | } |
3216 | else if (strcmp(envvar, "2" ) == 0) |
3217 | { |
3218 | *argv++ = "--withheap" ; |
3219 | } |
3220 | else if (strcmp(envvar, "3" ) == 0) |
3221 | { |
3222 | *argv++ = "--triage" ; |
3223 | } |
3224 | else if (strcmp(envvar, "4" ) == 0) |
3225 | { |
3226 | *argv++ = "--full" ; |
3227 | } |
3228 | } |
3229 | |
3230 | envvar = getenv("COMPlus_CreateDumpDiagnostics" ); |
3231 | if (envvar != nullptr && strcmp(envvar, "1" ) == 0) |
3232 | { |
3233 | *argv++ = "--diag" ; |
3234 | } |
3235 | |
3236 | *argv++ = pidarg; |
3237 | *argv = nullptr; |
3238 | } |
3239 | return TRUE; |
3240 | } |
3241 | |
3242 | /*++ |
3243 | Function: |
3244 | PROCCreateCrashDumpIfEnabled |
3245 | |
3246 | Creates crash dump of the process (if enabled). Can be |
3247 | called from the unhandled native exception handler. |
3248 | |
3249 | (no return value) |
3250 | --*/ |
3251 | VOID |
3252 | PROCCreateCrashDumpIfEnabled() |
3253 | { |
3254 | #if HAVE_PRCTL_H && HAVE_PR_SET_PTRACER |
3255 | // If enabled, launch the create minidump utility and wait until it completes |
3256 | if (g_argvCreateDump[0] == nullptr) |
3257 | return; |
3258 | |
3259 | // Fork the core dump child process. |
3260 | pid_t childpid = fork(); |
3261 | |
3262 | // If error, write an error to trace log and abort |
3263 | if (childpid == -1) |
3264 | { |
3265 | ERROR("PROCAbort: fork() FAILED %d (%s)\n" , errno, strerror(errno)); |
3266 | } |
3267 | else if (childpid == 0) |
3268 | { |
3269 | // Child process |
3270 | if (execve(g_argvCreateDump[0], g_argvCreateDump, palEnvironment) == -1) |
3271 | { |
3272 | ERROR("PROCAbort: execve FAILED %d (%s)\n" , errno, strerror(errno)); |
3273 | } |
3274 | } |
3275 | else |
3276 | { |
3277 | // Gives the child process permission to use /proc/<pid>/mem and ptrace |
3278 | if (prctl(PR_SET_PTRACER, childpid, 0, 0, 0) == -1) |
3279 | { |
3280 | ERROR("PROCAbort: prctl() FAILED %d (%s)\n" , errno, strerror(errno)); |
3281 | } |
3282 | // Parent waits until the child process is done |
3283 | int wstatus; |
3284 | int result = waitpid(childpid, &wstatus, 0); |
3285 | if (result != childpid) |
3286 | { |
3287 | ERROR("PROCAbort: waitpid FAILED result %d wstatus %d errno %d (%s)\n" , |
3288 | result, wstatus, errno, strerror(errno)); |
3289 | } |
3290 | } |
3291 | #endif // HAVE_PRCTL_H && HAVE_PR_SET_PTRACER |
3292 | } |
3293 | |
3294 | /*++ |
3295 | Function: |
3296 | PROCAbort() |
3297 | |
3298 | Aborts the process after calling the shutdown cleanup handler. This function |
3299 | should be called instead of calling abort() directly. |
3300 | |
3301 | Does not return |
3302 | --*/ |
3303 | PAL_NORETURN |
3304 | VOID |
3305 | PROCAbort() |
3306 | { |
3307 | // Do any shutdown cleanup before aborting or creating a core dump |
3308 | PROCNotifyProcessShutdown(); |
3309 | |
3310 | PROCCreateCrashDumpIfEnabled(); |
3311 | |
3312 | // Abort the process after waiting for the core dump to complete |
3313 | abort(); |
3314 | } |
3315 | |
3316 | /*++ |
3317 | Function: |
3318 | InitializeFlushProcessWriteBuffers |
3319 | |
3320 | Abstract |
3321 | This function initializes data structures needed for the FlushProcessWriteBuffers |
3322 | Return |
3323 | TRUE if it succeeded, FALSE otherwise |
3324 | --*/ |
3325 | BOOL |
3326 | InitializeFlushProcessWriteBuffers() |
3327 | { |
3328 | _ASSERTE(s_helperPage == 0); |
3329 | _ASSERTE(s_flushUsingMemBarrier == 0); |
3330 | |
3331 | // Starting with Linux kernel 4.14, process memory barriers can be generated |
3332 | // using MEMBARRIER_CMD_PRIVATE_EXPEDITED. |
3333 | int mask = membarrier(MEMBARRIER_CMD_QUERY, 0); |
3334 | if (mask >= 0 && |
3335 | mask & MEMBARRIER_CMD_PRIVATE_EXPEDITED) |
3336 | { |
3337 | // Register intent to use the private expedited command. |
3338 | if (membarrier(MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED, 0) == 0) |
3339 | { |
3340 | s_flushUsingMemBarrier = TRUE; |
3341 | return TRUE; |
3342 | } |
3343 | } |
3344 | |
3345 | s_helperPage = static_cast<int*>(mmap(0, GetVirtualPageSize(), PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0)); |
3346 | |
3347 | if(s_helperPage == MAP_FAILED) |
3348 | { |
3349 | return FALSE; |
3350 | } |
3351 | |
3352 | // Verify that the s_helperPage is really aligned to the GetVirtualPageSize() |
3353 | _ASSERTE((((SIZE_T)s_helperPage) & (GetVirtualPageSize() - 1)) == 0); |
3354 | |
3355 | // Locking the page ensures that it stays in memory during the two mprotect |
3356 | // calls in the FlushProcessWriteBuffers below. If the page was unmapped between |
3357 | // those calls, they would not have the expected effect of generating IPI. |
3358 | int status = mlock(s_helperPage, GetVirtualPageSize()); |
3359 | |
3360 | if (status != 0) |
3361 | { |
3362 | return FALSE; |
3363 | } |
3364 | |
3365 | status = pthread_mutex_init(&flushProcessWriteBuffersMutex, NULL); |
3366 | if (status != 0) |
3367 | { |
3368 | munlock(s_helperPage, GetVirtualPageSize()); |
3369 | } |
3370 | |
3371 | return status == 0; |
3372 | } |
3373 | |
3374 | #define FATAL_ASSERT(e, msg) \ |
3375 | do \ |
3376 | { \ |
3377 | if (!(e)) \ |
3378 | { \ |
3379 | fprintf(stderr, "FATAL ERROR: " msg); \ |
3380 | PROCAbort(); \ |
3381 | } \ |
3382 | } \ |
3383 | while(0) |
3384 | |
3385 | /*++ |
3386 | Function: |
3387 | FlushProcessWriteBuffers |
3388 | |
3389 | See MSDN doc. |
3390 | --*/ |
3391 | VOID |
3392 | PALAPI |
3393 | FlushProcessWriteBuffers() |
3394 | { |
3395 | if (s_flushUsingMemBarrier) |
3396 | { |
3397 | int status = membarrier(MEMBARRIER_CMD_PRIVATE_EXPEDITED, 0); |
3398 | FATAL_ASSERT(status == 0, "Failed to flush using membarrier" ); |
3399 | } |
3400 | else |
3401 | { |
3402 | int status = pthread_mutex_lock(&flushProcessWriteBuffersMutex); |
3403 | FATAL_ASSERT(status == 0, "Failed to lock the flushProcessWriteBuffersMutex lock" ); |
3404 | |
3405 | // Changing a helper memory page protection from read / write to no access |
3406 | // causes the OS to issue IPI to flush TLBs on all processors. This also |
3407 | // results in flushing the processor buffers. |
3408 | status = mprotect(s_helperPage, GetVirtualPageSize(), PROT_READ | PROT_WRITE); |
3409 | FATAL_ASSERT(status == 0, "Failed to change helper page protection to read / write" ); |
3410 | |
3411 | // Ensure that the page is dirty before we change the protection so that |
3412 | // we prevent the OS from skipping the global TLB flush. |
3413 | InterlockedIncrement(s_helperPage); |
3414 | |
3415 | status = mprotect(s_helperPage, GetVirtualPageSize(), PROT_NONE); |
3416 | FATAL_ASSERT(status == 0, "Failed to change helper page protection to no access" ); |
3417 | |
3418 | status = pthread_mutex_unlock(&flushProcessWriteBuffersMutex); |
3419 | FATAL_ASSERT(status == 0, "Failed to unlock the flushProcessWriteBuffersMutex lock" ); |
3420 | } |
3421 | } |
3422 | |
3423 | /*++ |
3424 | Function: |
3425 | PROCGetProcessIDFromHandle |
3426 | |
3427 | Abstract |
3428 | Return the process ID from a process handle |
3429 | |
3430 | Parameter |
3431 | hProcess: process handle |
3432 | |
3433 | Return |
3434 | Return the process ID, or 0 if it's not a valid handle |
3435 | --*/ |
3436 | DWORD |
3437 | PROCGetProcessIDFromHandle( |
3438 | HANDLE hProcess) |
3439 | { |
3440 | PAL_ERROR palError; |
3441 | IPalObject *pobjProcess = NULL; |
3442 | CPalThread *pThread = InternalGetCurrentThread(); |
3443 | |
3444 | DWORD dwProcessId = 0; |
3445 | |
3446 | if (hPseudoCurrentProcess == hProcess) |
3447 | { |
3448 | dwProcessId = gPID; |
3449 | goto PROCGetProcessIDFromHandleExit; |
3450 | } |
3451 | |
3452 | |
3453 | palError = g_pObjectManager->ReferenceObjectByHandle( |
3454 | pThread, |
3455 | hProcess, |
3456 | &aotProcess, |
3457 | 0, |
3458 | &pobjProcess |
3459 | ); |
3460 | |
3461 | if (NO_ERROR == palError) |
3462 | { |
3463 | IDataLock *pDataLock; |
3464 | CProcProcessLocalData *pLocalData; |
3465 | |
3466 | palError = pobjProcess->GetProcessLocalData( |
3467 | pThread, |
3468 | ReadLock, |
3469 | &pDataLock, |
3470 | reinterpret_cast<void **>(&pLocalData) |
3471 | ); |
3472 | |
3473 | if (NO_ERROR == palError) |
3474 | { |
3475 | dwProcessId = pLocalData->dwProcessId; |
3476 | pDataLock->ReleaseLock(pThread, FALSE); |
3477 | } |
3478 | |
3479 | pobjProcess->ReleaseReference(pThread); |
3480 | } |
3481 | |
3482 | PROCGetProcessIDFromHandleExit: |
3483 | |
3484 | return dwProcessId; |
3485 | } |
3486 | |
3487 | PAL_ERROR |
3488 | CorUnix::InitializeProcessData( |
3489 | void |
3490 | ) |
3491 | { |
3492 | PAL_ERROR palError = NO_ERROR; |
3493 | bool fLockInitialized = FALSE; |
3494 | |
3495 | pGThreadList = NULL; |
3496 | g_dwThreadCount = 0; |
3497 | |
3498 | InternalInitializeCriticalSection(&g_csProcess); |
3499 | fLockInitialized = TRUE; |
3500 | |
3501 | if (NO_ERROR != palError) |
3502 | { |
3503 | if (fLockInitialized) |
3504 | { |
3505 | InternalDeleteCriticalSection(&g_csProcess); |
3506 | } |
3507 | } |
3508 | |
3509 | return palError; |
3510 | } |
3511 | |
3512 | /*++ |
3513 | Function |
3514 | InitializeProcessCommandLine |
3515 | |
3516 | Abstract |
3517 | Initializes (or re-initializes) the saved command line and exe path. |
3518 | |
3519 | Parameter |
3520 | lpwstrCmdLine |
3521 | lpwstrFullPath |
3522 | |
3523 | Return |
3524 | PAL_ERROR |
3525 | |
3526 | Notes |
3527 | This function takes ownership of lpwstrCmdLine, but not of lpwstrFullPath |
3528 | --*/ |
3529 | |
3530 | PAL_ERROR |
3531 | CorUnix::InitializeProcessCommandLine( |
3532 | LPWSTR lpwstrCmdLine, |
3533 | LPWSTR lpwstrFullPath |
3534 | ) |
3535 | { |
3536 | PAL_ERROR palError = NO_ERROR; |
3537 | LPWSTR initial_dir = NULL; |
3538 | |
3539 | // |
3540 | // Save the command line and initial directory |
3541 | // |
3542 | |
3543 | if (lpwstrFullPath) |
3544 | { |
3545 | LPWSTR lpwstr = PAL_wcsrchr(lpwstrFullPath, '/'); |
3546 | lpwstr[0] = '\0'; |
3547 | size_t n = PAL_wcslen(lpwstrFullPath) + 1; |
3548 | |
3549 | size_t iLen = n; |
3550 | initial_dir = reinterpret_cast<LPWSTR>(InternalMalloc(iLen*sizeof(WCHAR))); |
3551 | if (NULL == initial_dir) |
3552 | { |
3553 | ERROR("malloc() failed! (initial_dir) \n" ); |
3554 | palError = ERROR_NOT_ENOUGH_MEMORY; |
3555 | goto exit; |
3556 | } |
3557 | |
3558 | if (wcscpy_s(initial_dir, iLen, lpwstrFullPath) != SAFECRT_SUCCESS) |
3559 | { |
3560 | ERROR("wcscpy_s failed!\n" ); |
3561 | free(initial_dir); |
3562 | palError = ERROR_INTERNAL_ERROR; |
3563 | goto exit; |
3564 | } |
3565 | |
3566 | lpwstr[0] = '/'; |
3567 | |
3568 | free(g_lpwstrAppDir); |
3569 | g_lpwstrAppDir = initial_dir; |
3570 | } |
3571 | |
3572 | free(g_lpwstrCmdLine); |
3573 | g_lpwstrCmdLine = lpwstrCmdLine; |
3574 | |
3575 | exit: |
3576 | return palError; |
3577 | } |
3578 | |
3579 | |
3580 | /*++ |
3581 | Function: |
3582 | CreateInitialProcessAndThreadObjects |
3583 | |
3584 | Abstract |
3585 | Creates the IPalObjects that represent the current process |
3586 | and the initial thread |
3587 | |
3588 | Parameter |
3589 | pThread - the initial thread |
3590 | |
3591 | Return |
3592 | PAL_ERROR |
3593 | --*/ |
3594 | |
3595 | PAL_ERROR |
3596 | CorUnix::CreateInitialProcessAndThreadObjects( |
3597 | CPalThread *pThread |
3598 | ) |
3599 | { |
3600 | PAL_ERROR palError = NO_ERROR; |
3601 | HANDLE hThread; |
3602 | IPalObject *pobjProcess = NULL; |
3603 | IDataLock *pDataLock; |
3604 | CProcProcessLocalData *pLocalData; |
3605 | CObjectAttributes oa; |
3606 | HANDLE hProcess; |
3607 | |
3608 | // |
3609 | // Create initial thread object |
3610 | // |
3611 | |
3612 | palError = CreateThreadObject(pThread, pThread, &hThread); |
3613 | if (NO_ERROR != palError) |
3614 | { |
3615 | goto CreateInitialProcessAndThreadObjectsExit; |
3616 | } |
3617 | |
3618 | // |
3619 | // This handle isn't needed |
3620 | // |
3621 | |
3622 | (void) g_pObjectManager->RevokeHandle(pThread, hThread); |
3623 | |
3624 | // |
3625 | // Create and initialize process object |
3626 | // |
3627 | |
3628 | palError = g_pObjectManager->AllocateObject( |
3629 | pThread, |
3630 | &otProcess, |
3631 | &oa, |
3632 | &pobjProcess |
3633 | ); |
3634 | |
3635 | if (NO_ERROR != palError) |
3636 | { |
3637 | ERROR("Unable to allocate process object" ); |
3638 | goto CreateInitialProcessAndThreadObjectsExit; |
3639 | } |
3640 | |
3641 | palError = pobjProcess->GetProcessLocalData( |
3642 | pThread, |
3643 | WriteLock, |
3644 | &pDataLock, |
3645 | reinterpret_cast<void **>(&pLocalData) |
3646 | ); |
3647 | |
3648 | if (NO_ERROR != palError) |
3649 | { |
3650 | ASSERT("Unable to access local data" ); |
3651 | goto CreateInitialProcessAndThreadObjectsExit; |
3652 | } |
3653 | |
3654 | pLocalData->dwProcessId = gPID; |
3655 | pLocalData->ps = PS_RUNNING; |
3656 | pDataLock->ReleaseLock(pThread, TRUE); |
3657 | |
3658 | palError = g_pObjectManager->RegisterObject( |
3659 | pThread, |
3660 | pobjProcess, |
3661 | &aotProcess, |
3662 | PROCESS_ALL_ACCESS, |
3663 | &hProcess, |
3664 | &g_pobjProcess |
3665 | ); |
3666 | |
3667 | // |
3668 | // pobjProcess is invalidated by the call to RegisterObject, so |
3669 | // NULL it out here to prevent it from being released later |
3670 | // |
3671 | |
3672 | pobjProcess = NULL; |
3673 | |
3674 | if (NO_ERROR != palError) |
3675 | { |
3676 | ASSERT("Failure registering process object" ); |
3677 | goto CreateInitialProcessAndThreadObjectsExit; |
3678 | } |
3679 | |
3680 | // |
3681 | // There's no need to keep this handle around, so revoke |
3682 | // it now |
3683 | // |
3684 | |
3685 | g_pObjectManager->RevokeHandle(pThread, hProcess); |
3686 | |
3687 | CreateInitialProcessAndThreadObjectsExit: |
3688 | |
3689 | if (NULL != pobjProcess) |
3690 | { |
3691 | pobjProcess->ReleaseReference(pThread); |
3692 | } |
3693 | |
3694 | return palError; |
3695 | } |
3696 | |
3697 | |
3698 | /*++ |
3699 | Function: |
3700 | PROCCleanupInitialProcess |
3701 | |
3702 | Abstract |
3703 | Cleanup all the structures for the initial process. |
3704 | |
3705 | Parameter |
3706 | VOID |
3707 | |
3708 | Return |
3709 | VOID |
3710 | |
3711 | --*/ |
3712 | VOID |
3713 | PROCCleanupInitialProcess(VOID) |
3714 | { |
3715 | CPalThread *pThread = InternalGetCurrentThread(); |
3716 | |
3717 | InternalEnterCriticalSection(pThread, &g_csProcess); |
3718 | |
3719 | /* Free the application directory */ |
3720 | free(g_lpwstrAppDir); |
3721 | |
3722 | /* Free the stored command line */ |
3723 | free(g_lpwstrCmdLine); |
3724 | |
3725 | InternalLeaveCriticalSection(pThread, &g_csProcess); |
3726 | |
3727 | // |
3728 | // Object manager shutdown will handle freeing the underlying |
3729 | // thread and process data |
3730 | // |
3731 | |
3732 | } |
3733 | |
3734 | /*++ |
3735 | Function: |
3736 | PROCAddThread |
3737 | |
3738 | Abstract |
3739 | Add a thread to the thread list of the current process |
3740 | |
3741 | Parameter |
3742 | pThread: Thread object |
3743 | |
3744 | --*/ |
3745 | VOID |
3746 | CorUnix::PROCAddThread( |
3747 | CPalThread *pCurrentThread, |
3748 | CPalThread *pTargetThread |
3749 | ) |
3750 | { |
3751 | /* protect the access of the thread list with critical section for |
3752 | mutithreading access */ |
3753 | InternalEnterCriticalSection(pCurrentThread, &g_csProcess); |
3754 | |
3755 | pTargetThread->SetNext(pGThreadList); |
3756 | pGThreadList = pTargetThread; |
3757 | g_dwThreadCount += 1; |
3758 | |
3759 | TRACE("Thread 0x%p (id %#x) added to the process thread list\n" , |
3760 | pTargetThread, pTargetThread->GetThreadId()); |
3761 | |
3762 | InternalLeaveCriticalSection(pCurrentThread, &g_csProcess); |
3763 | } |
3764 | |
3765 | |
3766 | /*++ |
3767 | Function: |
3768 | PROCRemoveThread |
3769 | |
3770 | Abstract |
3771 | Remove a thread form the thread list of the current process |
3772 | |
3773 | Parameter |
3774 | CPalThread *pThread : thread object to remove |
3775 | |
3776 | (no return value) |
3777 | --*/ |
3778 | VOID |
3779 | CorUnix::PROCRemoveThread( |
3780 | CPalThread *pCurrentThread, |
3781 | CPalThread *pTargetThread |
3782 | ) |
3783 | { |
3784 | CPalThread *curThread, *prevThread; |
3785 | |
3786 | /* protect the access of the thread list with critical section for |
3787 | mutithreading access */ |
3788 | InternalEnterCriticalSection(pCurrentThread, &g_csProcess); |
3789 | |
3790 | curThread = pGThreadList; |
3791 | |
3792 | /* if thread list is empty */ |
3793 | if (curThread == NULL) |
3794 | { |
3795 | ASSERT("Thread list is empty.\n" ); |
3796 | goto EXIT; |
3797 | } |
3798 | |
3799 | /* do we remove the first thread? */ |
3800 | if (curThread == pTargetThread) |
3801 | { |
3802 | pGThreadList = curThread->GetNext(); |
3803 | TRACE("Thread 0x%p (id %#x) removed from the process thread list\n" , |
3804 | pTargetThread, pTargetThread->GetThreadId()); |
3805 | goto EXIT; |
3806 | } |
3807 | |
3808 | prevThread = curThread; |
3809 | curThread = curThread->GetNext(); |
3810 | /* find the thread to remove */ |
3811 | while (curThread != NULL) |
3812 | { |
3813 | if (curThread == pTargetThread) |
3814 | { |
3815 | /* found, fix the chain list */ |
3816 | prevThread->SetNext(curThread->GetNext()); |
3817 | g_dwThreadCount -= 1; |
3818 | TRACE("Thread %p removed from the process thread list\n" , pTargetThread); |
3819 | goto EXIT; |
3820 | } |
3821 | |
3822 | prevThread = curThread; |
3823 | curThread = curThread->GetNext(); |
3824 | } |
3825 | |
3826 | WARN("Thread %p not removed (it wasn't found in the list)\n" , pTargetThread); |
3827 | |
3828 | EXIT: |
3829 | InternalLeaveCriticalSection(pCurrentThread, &g_csProcess); |
3830 | } |
3831 | |
3832 | |
3833 | /*++ |
3834 | Function: |
3835 | PROCGetNumberOfThreads |
3836 | |
3837 | Abstract |
3838 | Return the number of threads in the thread list. |
3839 | |
3840 | Parameter |
3841 | void |
3842 | |
3843 | Return |
3844 | the number of threads. |
3845 | --*/ |
3846 | INT |
3847 | CorUnix::PROCGetNumberOfThreads( |
3848 | VOID) |
3849 | { |
3850 | return g_dwThreadCount; |
3851 | } |
3852 | |
3853 | |
3854 | /*++ |
3855 | Function: |
3856 | PROCProcessLock |
3857 | |
3858 | Abstract |
3859 | Enter the critical section associated to the current process |
3860 | |
3861 | Parameter |
3862 | void |
3863 | |
3864 | Return |
3865 | void |
3866 | --*/ |
3867 | VOID |
3868 | PROCProcessLock( |
3869 | VOID) |
3870 | { |
3871 | CPalThread * pThread = |
3872 | (PALIsThreadDataInitialized() ? InternalGetCurrentThread() : NULL); |
3873 | |
3874 | InternalEnterCriticalSection(pThread, &g_csProcess); |
3875 | } |
3876 | |
3877 | |
3878 | /*++ |
3879 | Function: |
3880 | PROCProcessUnlock |
3881 | |
3882 | Abstract |
3883 | Leave the critical section associated to the current process |
3884 | |
3885 | Parameter |
3886 | void |
3887 | |
3888 | Return |
3889 | void |
3890 | --*/ |
3891 | VOID |
3892 | PROCProcessUnlock( |
3893 | VOID) |
3894 | { |
3895 | CPalThread * pThread = |
3896 | (PALIsThreadDataInitialized() ? InternalGetCurrentThread() : NULL); |
3897 | |
3898 | InternalLeaveCriticalSection(pThread, &g_csProcess); |
3899 | } |
3900 | |
3901 | #if USE_SYSV_SEMAPHORES |
3902 | /*++ |
3903 | Function: |
3904 | PROCCleanupThreadSemIds |
3905 | |
3906 | Abstract |
3907 | Cleanup SysV semaphore ids for all threads |
3908 | |
3909 | (no parameters, no return value) |
3910 | --*/ |
3911 | VOID |
3912 | PROCCleanupThreadSemIds(void) |
3913 | { |
3914 | // |
3915 | // When using SysV semaphores, the semaphore ids used by PAL threads must be removed |
3916 | // so they can be used again. |
3917 | // |
3918 | |
3919 | PROCProcessLock(); |
3920 | |
3921 | CPalThread *pTargetThread = pGThreadList; |
3922 | while (NULL != pTargetThread) |
3923 | { |
3924 | pTargetThread->suspensionInfo.DestroySemaphoreIds(); |
3925 | pTargetThread = pTargetThread->GetNext(); |
3926 | } |
3927 | |
3928 | PROCProcessUnlock(); |
3929 | |
3930 | } |
3931 | #endif // USE_SYSV_SEMAPHORES |
3932 | |
3933 | /*++ |
3934 | Function: |
3935 | TerminateCurrentProcessNoExit |
3936 | |
3937 | Abstract: |
3938 | Terminate current Process, but leave the caller alive |
3939 | |
3940 | Parameters: |
3941 | BOOL bTerminateUnconditionally - If this is set, the PAL will exit as |
3942 | quickly as possible. In particular, it will not unload DLLs. |
3943 | |
3944 | Return value : |
3945 | No return |
3946 | |
3947 | Note: |
3948 | This function is used in ExitThread and TerminateProcess |
3949 | |
3950 | --*/ |
3951 | VOID |
3952 | CorUnix::TerminateCurrentProcessNoExit(BOOL bTerminateUnconditionally) |
3953 | { |
3954 | BOOL locked; |
3955 | DWORD old_terminator; |
3956 | |
3957 | old_terminator = InterlockedCompareExchange(&terminator, GetCurrentThreadId(), 0); |
3958 | |
3959 | if (0 != old_terminator && GetCurrentThreadId() != old_terminator) |
3960 | { |
3961 | /* another thread has already initiated the termination process. we |
3962 | could just block on the PALInitLock critical section, but then |
3963 | PROCSuspendOtherThreads would hang... so sleep forever here, we're |
3964 | terminating anyway |
3965 | |
3966 | Update: [TODO] PROCSuspendOtherThreads has been removed. Can this |
3967 | code be changed? */ |
3968 | |
3969 | /* note that if *this* thread has already started the termination |
3970 | process, we want to proceed. the only way this can happen is if a |
3971 | call to DllMain (from ExitProcess) brought us here (because DllMain |
3972 | called ExitProcess, or TerminateProcess, or ExitThread); |
3973 | TerminateProcess won't call DllMain, so there's no danger to get |
3974 | caught in an infinite loop */ |
3975 | WARN("termination already started from another thread; blocking.\n" ); |
3976 | poll(NULL, 0, INFTIM); |
3977 | } |
3978 | |
3979 | /* Try to lock the initialization count to prevent multiple threads from |
3980 | terminating/initializing the PAL simultaneously */ |
3981 | |
3982 | /* note : it's also important to take this lock before the process lock, |
3983 | because Init/Shutdown take the init lock, and the functions they call |
3984 | may take the process lock. We must do it in the same order to avoid |
3985 | deadlocks */ |
3986 | |
3987 | locked = PALInitLock(); |
3988 | if(locked && PALIsInitialized()) |
3989 | { |
3990 | PROCNotifyProcessShutdown(); |
3991 | PALCommonCleanup(); |
3992 | } |
3993 | } |
3994 | |
3995 | /*++ |
3996 | Function: |
3997 | PROCGetProcessStatus |
3998 | |
3999 | Abstract: |
4000 | Retrieve process state information (state & exit code). |
4001 | |
4002 | Parameters: |
4003 | DWORD process_id : PID of process to retrieve state for |
4004 | PROCESS_STATE *state : state of process (starting, running, done) |
4005 | DWORD *exit_code : exit code of process (from ExitProcess, etc.) |
4006 | |
4007 | Return value : |
4008 | TRUE on success |
4009 | --*/ |
4010 | PAL_ERROR |
4011 | PROCGetProcessStatus( |
4012 | CPalThread *pThread, |
4013 | HANDLE hProcess, |
4014 | PROCESS_STATE *pps, |
4015 | DWORD *pdwExitCode |
4016 | ) |
4017 | { |
4018 | PAL_ERROR palError = NO_ERROR; |
4019 | IPalObject *pobjProcess = NULL; |
4020 | IDataLock *pDataLock; |
4021 | CProcProcessLocalData *pLocalData; |
4022 | pid_t wait_retval; |
4023 | int status; |
4024 | |
4025 | // |
4026 | // First, check if we already know the status of this process. This will be |
4027 | // the case if this function has already been called for the same process. |
4028 | // |
4029 | |
4030 | palError = g_pObjectManager->ReferenceObjectByHandle( |
4031 | pThread, |
4032 | hProcess, |
4033 | &aotProcess, |
4034 | 0, |
4035 | &pobjProcess |
4036 | ); |
4037 | |
4038 | if (NO_ERROR != palError) |
4039 | { |
4040 | goto PROCGetProcessStatusExit; |
4041 | } |
4042 | |
4043 | palError = pobjProcess->GetProcessLocalData( |
4044 | pThread, |
4045 | WriteLock, |
4046 | &pDataLock, |
4047 | reinterpret_cast<void **>(&pLocalData) |
4048 | ); |
4049 | |
4050 | if (PS_DONE == pLocalData->ps) |
4051 | { |
4052 | TRACE("We already called waitpid() on process ID %#x; process has " |
4053 | "terminated, exit code is %d\n" , |
4054 | pLocalData->dwProcessId, pLocalData->dwExitCode); |
4055 | |
4056 | *pps = pLocalData->ps; |
4057 | *pdwExitCode = pLocalData->dwExitCode; |
4058 | |
4059 | pDataLock->ReleaseLock(pThread, FALSE); |
4060 | |
4061 | goto PROCGetProcessStatusExit; |
4062 | } |
4063 | |
4064 | /* By using waitpid(), we can even retrieve the exit code of a non-PAL |
4065 | process. However, note that waitpid() can only provide the low 8 bits |
4066 | of the exit code. This is all that is required for the PAL spec. */ |
4067 | TRACE("Looking for status of process; trying wait()" ); |
4068 | |
4069 | while(1) |
4070 | { |
4071 | /* try to get state of process, using non-blocking call */ |
4072 | wait_retval = waitpid(pLocalData->dwProcessId, &status, WNOHANG); |
4073 | |
4074 | if ( wait_retval == (pid_t) pLocalData->dwProcessId ) |
4075 | { |
4076 | /* success; get the exit code */ |
4077 | if ( WIFEXITED( status ) ) |
4078 | { |
4079 | *pdwExitCode = WEXITSTATUS(status); |
4080 | TRACE("Exit code was %d\n" , *pdwExitCode); |
4081 | } |
4082 | else |
4083 | { |
4084 | WARN("process terminated without exiting; can't get exit " |
4085 | "code. faking it.\n" ); |
4086 | *pdwExitCode = EXIT_FAILURE; |
4087 | } |
4088 | *pps = PS_DONE; |
4089 | } |
4090 | else if (0 == wait_retval) |
4091 | { |
4092 | // The process is still running. |
4093 | TRACE("Process %#x is still active.\n" , pLocalData->dwProcessId); |
4094 | *pps = PS_RUNNING; |
4095 | *pdwExitCode = 0; |
4096 | } |
4097 | else if (-1 == wait_retval) |
4098 | { |
4099 | // This might happen if waitpid() had already been called, but |
4100 | // this shouldn't happen - we call waitpid once, store the |
4101 | // result, and use that afterwards. |
4102 | // One legitimate cause of failure is EINTR; if this happens we |
4103 | // have to try again. A second legitimate cause is ECHILD, which |
4104 | // happens if we're trying to retrieve the status of a currently- |
4105 | // running process that isn't a child of this process. |
4106 | if(EINTR == errno) |
4107 | { |
4108 | TRACE("waitpid() failed with EINTR; re-waiting" ); |
4109 | continue; |
4110 | } |
4111 | else if (ECHILD == errno) |
4112 | { |
4113 | TRACE("waitpid() failed with ECHILD; calling kill instead" ); |
4114 | if (kill(pLocalData->dwProcessId, 0) != 0) |
4115 | { |
4116 | if(ESRCH == errno) |
4117 | { |
4118 | WARN("kill() failed with ESRCH, i.e. target " |
4119 | "process exited and it wasn't a child, " |
4120 | "so can't get the exit code, assuming " |
4121 | "it was 0.\n" ); |
4122 | *pdwExitCode = 0; |
4123 | } |
4124 | else |
4125 | { |
4126 | ERROR("kill(pid, 0) failed; errno is %d (%s)\n" , |
4127 | errno, strerror(errno)); |
4128 | *pdwExitCode = EXIT_FAILURE; |
4129 | } |
4130 | *pps = PS_DONE; |
4131 | } |
4132 | else |
4133 | { |
4134 | *pps = PS_RUNNING; |
4135 | *pdwExitCode = 0; |
4136 | } |
4137 | } |
4138 | else |
4139 | { |
4140 | // Ignoring unexpected waitpid errno and assuming that |
4141 | // the process is still running |
4142 | ERROR("waitpid(pid=%u) failed with unexpected errno=%d (%s)\n" , |
4143 | pLocalData->dwProcessId, errno, strerror(errno)); |
4144 | *pps = PS_RUNNING; |
4145 | *pdwExitCode = 0; |
4146 | } |
4147 | } |
4148 | else |
4149 | { |
4150 | ASSERT("waitpid returned unexpected value %d\n" ,wait_retval); |
4151 | *pdwExitCode = EXIT_FAILURE; |
4152 | *pps = PS_DONE; |
4153 | } |
4154 | // Break out of the loop in all cases except EINTR. |
4155 | break; |
4156 | } |
4157 | |
4158 | // Save the exit code for future reference (waitpid will only work once). |
4159 | if(PS_DONE == *pps) |
4160 | { |
4161 | pLocalData->ps = PS_DONE; |
4162 | pLocalData->dwExitCode = *pdwExitCode; |
4163 | } |
4164 | |
4165 | TRACE( "State of process 0x%08x : %d (exit code %d)\n" , |
4166 | pLocalData->dwProcessId, *pps, *pdwExitCode ); |
4167 | |
4168 | pDataLock->ReleaseLock(pThread, TRUE); |
4169 | |
4170 | PROCGetProcessStatusExit: |
4171 | |
4172 | if (NULL != pobjProcess) |
4173 | { |
4174 | pobjProcess->ReleaseReference(pThread); |
4175 | } |
4176 | |
4177 | return palError; |
4178 | } |
4179 | |
4180 | #ifdef __APPLE__ |
4181 | bool GetApplicationContainerFolder(PathCharString& buffer, const char *applicationGroupId, int applicationGroupIdLength) |
4182 | { |
4183 | const char *homeDir = getpwuid(getuid())->pw_dir; |
4184 | int homeDirLength = strlen(homeDir); |
4185 | |
4186 | // The application group container folder is defined as: |
4187 | // /user/{loginname}/Library/Group Containers/{AppGroupId}/ |
4188 | return buffer.Set(homeDir, homeDirLength) |
4189 | && buffer.Append(APPLICATION_CONTAINER_BASE_PATH_SUFFIX) |
4190 | && buffer.Append(applicationGroupId, applicationGroupIdLength) |
4191 | && buffer.Append('/'); |
4192 | } |
4193 | #endif // __APPLE__ |
4194 | |
4195 | #ifdef _DEBUG |
4196 | void PROCDumpThreadList() |
4197 | { |
4198 | CPalThread *pThread; |
4199 | |
4200 | PROCProcessLock(); |
4201 | |
4202 | TRACE ("Threads:{\n" ); |
4203 | |
4204 | pThread = pGThreadList; |
4205 | while (NULL != pThread) |
4206 | { |
4207 | TRACE (" {pThr=0x%p tid=%#x lwpid=%#x state=%d finsusp=%d}\n" , |
4208 | pThread, (int)pThread->GetThreadId(), (int)pThread->GetLwpId(), |
4209 | (int)pThread->synchronizationInfo.GetThreadState(), |
4210 | (int)pThread->suspensionInfo.GetSuspendedForShutdown()); |
4211 | |
4212 | pThread = pThread->GetNext(); |
4213 | } |
4214 | TRACE ("Threads:}\n" ); |
4215 | |
4216 | PROCProcessUnlock(); |
4217 | } |
4218 | #endif |
4219 | |
4220 | /* Internal function definitions **********************************************/ |
4221 | |
4222 | /*++ |
4223 | Function: |
4224 | getFileName |
4225 | |
4226 | Abstract: |
4227 | Helper function for CreateProcessW, it retrieves the executable filename |
4228 | from the application name, and the command line. |
4229 | |
4230 | Parameters: |
4231 | IN lpApplicationName: first parameter from CreateProcessW (an unicode string) |
4232 | IN lpCommandLine: second parameter from CreateProcessW (an unicode string) |
4233 | OUT lpFileName: file to be executed (the new process) |
4234 | |
4235 | Return: |
4236 | TRUE: if the file name is retrieved |
4237 | FALSE: otherwise |
4238 | |
4239 | --*/ |
4240 | static |
4241 | BOOL |
4242 | getFileName( |
4243 | LPCWSTR lpApplicationName, |
4244 | LPWSTR lpCommandLine, |
4245 | PathCharString& lpPathFileName) |
4246 | { |
4247 | LPWSTR lpEnd; |
4248 | WCHAR wcEnd; |
4249 | char * lpFileName; |
4250 | PathCharString lpFileNamePS; |
4251 | char *lpTemp; |
4252 | |
4253 | if (lpApplicationName) |
4254 | { |
4255 | int length = WideCharToMultiByte(CP_ACP, 0, lpApplicationName, -1, |
4256 | NULL, 0, NULL, NULL); |
4257 | |
4258 | /* if only a file name is specified, prefix it with "./" */ |
4259 | if ((*lpApplicationName != '.') && (*lpApplicationName != '/') && |
4260 | (*lpApplicationName != '\\')) |
4261 | { |
4262 | length += 2; |
4263 | lpTemp = lpPathFileName.OpenStringBuffer(length); |
4264 | |
4265 | if (strcpy_s(lpTemp, length, "./" ) != SAFECRT_SUCCESS) |
4266 | { |
4267 | ERROR("strcpy_s failed!\n" ); |
4268 | return FALSE; |
4269 | } |
4270 | lpTemp+=2; |
4271 | |
4272 | } |
4273 | else |
4274 | { |
4275 | lpTemp = lpPathFileName.OpenStringBuffer(length); |
4276 | } |
4277 | |
4278 | /* Convert to ASCII */ |
4279 | length = WideCharToMultiByte(CP_ACP, 0, lpApplicationName, -1, |
4280 | lpTemp, length, NULL, NULL); |
4281 | if (length == 0) |
4282 | { |
4283 | lpPathFileName.CloseBuffer(0); |
4284 | ASSERT("WideCharToMultiByte failure\n" ); |
4285 | return FALSE; |
4286 | } |
4287 | |
4288 | lpPathFileName.CloseBuffer(length -1); |
4289 | |
4290 | /* Replace '\' by '/' */ |
4291 | FILEDosToUnixPathA(lpTemp); |
4292 | |
4293 | return TRUE; |
4294 | } |
4295 | else |
4296 | { |
4297 | /* use the Command line */ |
4298 | |
4299 | /* filename should be the first token of the command line */ |
4300 | |
4301 | /* first skip all leading whitespace */ |
4302 | lpCommandLine = UTIL_inverse_wcspbrk(lpCommandLine,W16_WHITESPACE); |
4303 | if(NULL == lpCommandLine) |
4304 | { |
4305 | ERROR("CommandLine contains only whitespace!\n" ); |
4306 | return FALSE; |
4307 | } |
4308 | |
4309 | /* check if it is starting with a quote (") character */ |
4310 | if (*lpCommandLine == 0x0022) |
4311 | { |
4312 | lpCommandLine++; /* skip the quote */ |
4313 | |
4314 | /* file name ends with another quote */ |
4315 | lpEnd = PAL_wcschr(lpCommandLine+1, 0x0022); |
4316 | |
4317 | /* if no quotes found, set lpEnd to the end of the Command line */ |
4318 | if (lpEnd == NULL) |
4319 | lpEnd = lpCommandLine + PAL_wcslen(lpCommandLine); |
4320 | } |
4321 | else |
4322 | { |
4323 | /* filename is end out by a whitespace */ |
4324 | lpEnd = PAL_wcspbrk(lpCommandLine, W16_WHITESPACE); |
4325 | |
4326 | /* if no whitespace found, set lpEnd to end of the Command line */ |
4327 | if (lpEnd == NULL) |
4328 | { |
4329 | lpEnd = lpCommandLine + PAL_wcslen(lpCommandLine); |
4330 | } |
4331 | } |
4332 | |
4333 | if (lpEnd == lpCommandLine) |
4334 | { |
4335 | ERROR("application name and command line are both empty!\n" ); |
4336 | return FALSE; |
4337 | } |
4338 | |
4339 | /* replace the last character by a null */ |
4340 | wcEnd = *lpEnd; |
4341 | *lpEnd = 0x0000; |
4342 | |
4343 | /* Convert to ASCII */ |
4344 | int size = 0; |
4345 | int length = (PAL_wcslen(lpCommandLine)+1) * sizeof(WCHAR); |
4346 | lpFileName = lpFileNamePS.OpenStringBuffer(length); |
4347 | if (NULL == lpFileName) |
4348 | { |
4349 | ERROR("Not Enough Memory!\n" ); |
4350 | return FALSE; |
4351 | } |
4352 | if (!(size = WideCharToMultiByte(CP_ACP, 0, lpCommandLine, -1, |
4353 | lpFileName, length, NULL, NULL))) |
4354 | { |
4355 | ASSERT("WideCharToMultiByte failure\n" ); |
4356 | return FALSE; |
4357 | } |
4358 | |
4359 | lpFileNamePS.CloseBuffer(size - 1); |
4360 | /* restore last character */ |
4361 | *lpEnd = wcEnd; |
4362 | |
4363 | /* Replace '\' by '/' */ |
4364 | FILEDosToUnixPathA(lpFileName); |
4365 | if (!getPath(lpFileNamePS, lpPathFileName)) |
4366 | { |
4367 | /* file is not in the path */ |
4368 | return FALSE; |
4369 | } |
4370 | } |
4371 | return TRUE; |
4372 | } |
4373 | |
4374 | /*++ |
4375 | Function: |
4376 | checkFileType |
4377 | |
4378 | Abstract: |
4379 | Return the type of the file. |
4380 | |
4381 | Parameters: |
4382 | IN lpFileName: file name |
4383 | |
4384 | Return: |
4385 | FILE_DIR: Directory |
4386 | FILE_UNIX: Unix executable file |
4387 | FILE_ERROR: Error |
4388 | --*/ |
4389 | static |
4390 | int |
4391 | checkFileType( LPCSTR lpFileName) |
4392 | { |
4393 | struct stat stat_data; |
4394 | |
4395 | /* check if the file exist */ |
4396 | if ( access(lpFileName, F_OK) != 0 ) |
4397 | { |
4398 | return FILE_ERROR; |
4399 | } |
4400 | |
4401 | /* if it's not a PE/COFF file, check if it is executable */ |
4402 | if ( -1 != stat( lpFileName, &stat_data ) ) |
4403 | { |
4404 | if((stat_data.st_mode & S_IFMT) == S_IFDIR ) |
4405 | { |
4406 | /*The given file is a directory*/ |
4407 | return FILE_DIR; |
4408 | } |
4409 | if ( UTIL_IsExecuteBitsSet( &stat_data ) ) |
4410 | { |
4411 | return FILE_UNIX; |
4412 | } |
4413 | else |
4414 | { |
4415 | return FILE_ERROR; |
4416 | } |
4417 | } |
4418 | return FILE_ERROR; |
4419 | |
4420 | } |
4421 | |
4422 | |
4423 | /*++ |
4424 | Function: |
4425 | buildArgv |
4426 | |
4427 | Abstract: |
4428 | Helper function for CreateProcessW, it builds the array of argument in |
4429 | a format than can be passed to execve function.lppArgv is allocated |
4430 | in this function and must be freed by the caller. |
4431 | |
4432 | Parameters: |
4433 | IN lpCommandLine: second parameter from CreateProcessW (an unicode string) |
4434 | IN lpAppPath: cannonical name of the application to launched |
4435 | OUT lppArgv: array of arguments to be passed to the new process |
4436 | |
4437 | Return: |
4438 | the number of arguments |
4439 | |
4440 | note: this doesn't yet match precisely the behavior of Windows, but should be |
4441 | sufficient. |
4442 | what's here: |
4443 | 1) stripping nonquoted whitespace |
4444 | 2) handling of quoted parameters and quoted "parts" of parameters, removal of |
4445 | doublequotes (<aaaa"b bbb b"ccc> becomes <aaaab bbb bccc>) |
4446 | 3) \" as an escaped doublequote, both within doublequoted sequences and out |
4447 | what's known missing : |
4448 | 1) \\ as an escaped backslash, but only if the string of '\' |
4449 | is followed by a " (escaped or not) |
4450 | 2) "alternate" escape sequence : double-doublequote within a double-quoted |
4451 | argument (<"aaa a""aa aaa">) expands to a single-doublequote(<aaa a"aa aaa>) |
4452 | note that there may be other special cases |
4453 | --*/ |
4454 | static |
4455 | char ** |
4456 | buildArgv( |
4457 | LPCWSTR lpCommandLine, |
4458 | PathCharString& lpAppPath, |
4459 | UINT *pnArg) |
4460 | { |
4461 | CPalThread *pThread = NULL; |
4462 | UINT iWlen; |
4463 | char *lpAsciiCmdLine; |
4464 | char *pChar; |
4465 | char **lppArgv; |
4466 | char **lppTemp; |
4467 | UINT i,j; |
4468 | |
4469 | *pnArg = 0; |
4470 | |
4471 | iWlen = WideCharToMultiByte(CP_ACP,0,lpCommandLine,-1,NULL,0,NULL,NULL); |
4472 | |
4473 | if(0 == iWlen) |
4474 | { |
4475 | ASSERT("Can't determine length of command line\n" ); |
4476 | return NULL; |
4477 | } |
4478 | |
4479 | pThread = InternalGetCurrentThread(); |
4480 | /* make sure to allocate enough space, up for the worst case scenario */ |
4481 | int iLength = (iWlen + lpAppPath.GetCount() + 2); |
4482 | lpAsciiCmdLine = (char *) InternalMalloc(iLength); |
4483 | |
4484 | if (lpAsciiCmdLine == NULL) |
4485 | { |
4486 | ERROR("Unable to allocate memory\n" ); |
4487 | return NULL; |
4488 | } |
4489 | |
4490 | pChar = lpAsciiCmdLine; |
4491 | |
4492 | /* put the cannonical name of the application as the first parameter */ |
4493 | if ((strcpy_s(lpAsciiCmdLine, iLength, "\"" ) != SAFECRT_SUCCESS) || |
4494 | (strcat_s(lpAsciiCmdLine, iLength, lpAppPath) != SAFECRT_SUCCESS) || |
4495 | (strcat_s(lpAsciiCmdLine, iLength, "\"" ) != SAFECRT_SUCCESS) || |
4496 | (strcat_s(lpAsciiCmdLine, iLength, " " ) != SAFECRT_SUCCESS)) |
4497 | { |
4498 | ERROR("strcpy_s/strcat_s failed!\n" ); |
4499 | return NULL; |
4500 | } |
4501 | |
4502 | pChar = lpAsciiCmdLine + strlen (lpAsciiCmdLine); |
4503 | |
4504 | /* let's skip the first argument in the command line */ |
4505 | |
4506 | /* strip leading whitespace; function returns NULL if there's only |
4507 | whitespace, so the if statement below will work correctly */ |
4508 | lpCommandLine = UTIL_inverse_wcspbrk((LPWSTR)lpCommandLine, W16_WHITESPACE); |
4509 | |
4510 | if (lpCommandLine) |
4511 | { |
4512 | LPCWSTR stringstart = lpCommandLine; |
4513 | |
4514 | do |
4515 | { |
4516 | /* find first whitespace or dquote character */ |
4517 | lpCommandLine = PAL_wcspbrk(lpCommandLine,W16_WHITESPACE_DQUOTE); |
4518 | if(NULL == lpCommandLine) |
4519 | { |
4520 | /* no whitespace or dquote found : first arg is only arg */ |
4521 | break; |
4522 | } |
4523 | else if('"' == *lpCommandLine) |
4524 | { |
4525 | /* got a dquote; skip over it if it's escaped; make sure we |
4526 | don't try to look before the first character in the |
4527 | string */ |
4528 | if(lpCommandLine > stringstart && '\\' == lpCommandLine[-1]) |
4529 | { |
4530 | lpCommandLine++; |
4531 | continue; |
4532 | } |
4533 | |
4534 | /* found beginning of dquoted sequence, run to the end */ |
4535 | /* don't stop if we hit an escaped dquote */ |
4536 | lpCommandLine++; |
4537 | while( *lpCommandLine ) |
4538 | { |
4539 | lpCommandLine = PAL_wcschr(lpCommandLine, '"'); |
4540 | if(NULL == lpCommandLine) |
4541 | { |
4542 | /* no ending dquote, arg runs to end of string */ |
4543 | break; |
4544 | } |
4545 | if('\\' != lpCommandLine[-1]) |
4546 | { |
4547 | /* dquote is not escaped, dquoted sequence is over*/ |
4548 | break; |
4549 | } |
4550 | lpCommandLine++; |
4551 | } |
4552 | if(NULL == lpCommandLine || '\0' == *lpCommandLine) |
4553 | { |
4554 | /* no terminating dquote */ |
4555 | break; |
4556 | } |
4557 | |
4558 | /* step over dquote, keep looking for end of arg */ |
4559 | lpCommandLine++; |
4560 | } |
4561 | else |
4562 | { |
4563 | /* found whitespace : end of arg. */ |
4564 | lpCommandLine++; |
4565 | break; |
4566 | } |
4567 | }while(lpCommandLine); |
4568 | } |
4569 | |
4570 | /* Convert to ASCII */ |
4571 | if (lpCommandLine) |
4572 | { |
4573 | if (!WideCharToMultiByte(CP_ACP, 0, lpCommandLine, -1, |
4574 | pChar, iWlen+1, NULL, NULL)) |
4575 | { |
4576 | ASSERT("Unable to convert to a multibyte string\n" ); |
4577 | free(lpAsciiCmdLine); |
4578 | return NULL; |
4579 | } |
4580 | } |
4581 | |
4582 | pChar = lpAsciiCmdLine; |
4583 | |
4584 | /* loops through all the arguments, to find out how many arguments there |
4585 | are; while looping replace whitespace by \0 */ |
4586 | |
4587 | /* skip leading whitespace (and replace by '\0') */ |
4588 | /* note : there shouldn't be any, command starts either with PE loader name |
4589 | or computed application path, but this won't hurt */ |
4590 | while (*pChar) |
4591 | { |
4592 | if (!isspace((unsigned char) *pChar)) |
4593 | { |
4594 | break; |
4595 | } |
4596 | WARN("unexpected whitespace in command line!\n" ); |
4597 | *pChar++ = '\0'; |
4598 | } |
4599 | |
4600 | while (*pChar) |
4601 | { |
4602 | (*pnArg)++; |
4603 | |
4604 | /* find end of current arg */ |
4605 | while(*pChar && !isspace((unsigned char) *pChar)) |
4606 | { |
4607 | if('"' == *pChar) |
4608 | { |
4609 | /* skip over dquote if it's escaped; make sure we don't try to |
4610 | look before the start of the string for the \ */ |
4611 | if(pChar > lpAsciiCmdLine && '\\' == pChar[-1]) |
4612 | { |
4613 | pChar++; |
4614 | continue; |
4615 | } |
4616 | |
4617 | /* found leading dquote : look for ending dquote */ |
4618 | pChar++; |
4619 | while (*pChar) |
4620 | { |
4621 | pChar = strchr(pChar,'"'); |
4622 | if(NULL == pChar) |
4623 | { |
4624 | /* no ending dquote found : argument extends to the end |
4625 | of the string*/ |
4626 | break; |
4627 | } |
4628 | if('\\' != pChar[-1]) |
4629 | { |
4630 | /* found a dquote, and it's not escaped : quoted |
4631 | sequence is over*/ |
4632 | break; |
4633 | } |
4634 | /* found a dquote, but it was escaped : skip over it, keep |
4635 | looking */ |
4636 | pChar++; |
4637 | } |
4638 | if(NULL == pChar || '\0' == *pChar) |
4639 | { |
4640 | /* reached the end of the string : we're done */ |
4641 | break; |
4642 | } |
4643 | } |
4644 | pChar++; |
4645 | } |
4646 | if(NULL == pChar) |
4647 | { |
4648 | /* reached the end of the string : we're done */ |
4649 | break; |
4650 | } |
4651 | /* reached end of arg; replace trailing whitespace by '\0', to split |
4652 | arguments into separate strings */ |
4653 | while (isspace((unsigned char) *pChar)) |
4654 | { |
4655 | *pChar++ = '\0'; |
4656 | } |
4657 | } |
4658 | |
4659 | /* allocate lppargv according to the number of arguments |
4660 | in the command line */ |
4661 | lppArgv = (char **) InternalMalloc((((*pnArg)+1) * sizeof(char *))); |
4662 | |
4663 | if (lppArgv == NULL) |
4664 | { |
4665 | free(lpAsciiCmdLine); |
4666 | return NULL; |
4667 | } |
4668 | |
4669 | lppTemp = lppArgv; |
4670 | |
4671 | /* at this point all parameters are separated by NULL |
4672 | we need to fill the array of arguments; we must also remove all dquotes |
4673 | from arguments (new process shouldn't see them) */ |
4674 | for (i = *pnArg, pChar = lpAsciiCmdLine; i; i--) |
4675 | { |
4676 | /* skip NULLs */ |
4677 | while (!*pChar) |
4678 | { |
4679 | pChar++; |
4680 | } |
4681 | |
4682 | *lppTemp = pChar; |
4683 | |
4684 | /* go to the next parameter, removing dquotes as we go along */ |
4685 | j = 0; |
4686 | while (*pChar) |
4687 | { |
4688 | /* copy character if it's not a dquote */ |
4689 | if('"' != *pChar) |
4690 | { |
4691 | /* if it's the \ of an escaped dquote, skip over it, we'll |
4692 | copy the " instead */ |
4693 | if( '\\' == pChar[0] && '"' == pChar[1] ) |
4694 | { |
4695 | pChar++; |
4696 | } |
4697 | (*lppTemp)[j++] = *pChar; |
4698 | } |
4699 | pChar++; |
4700 | } |
4701 | /* re-NULL terminate the argument */ |
4702 | (*lppTemp)[j] = '\0'; |
4703 | |
4704 | lppTemp++; |
4705 | } |
4706 | |
4707 | *lppTemp = NULL; |
4708 | |
4709 | return lppArgv; |
4710 | } |
4711 | |
4712 | |
4713 | /*++ |
4714 | Function: |
4715 | getPath |
4716 | |
4717 | Abstract: |
4718 | Helper function for CreateProcessW, it looks in the path environment |
4719 | variable to find where the process to executed is. |
4720 | |
4721 | Parameters: |
4722 | IN lpFileName: file name to search in the path |
4723 | OUT lpPathFileName: returned string containing the path and the filename |
4724 | |
4725 | Return: |
4726 | TRUE if found |
4727 | FALSE otherwise |
4728 | --*/ |
4729 | static |
4730 | BOOL |
4731 | getPath( |
4732 | PathCharString& lpFileNameString, |
4733 | PathCharString& lpPathFileName) |
4734 | { |
4735 | LPSTR lpPath; |
4736 | LPSTR lpNext; |
4737 | LPSTR lpCurrent; |
4738 | LPWSTR lpwstr; |
4739 | INT n; |
4740 | INT nextLen; |
4741 | INT slashLen; |
4742 | CPalThread *pThread = NULL; |
4743 | LPCSTR lpFileName = lpFileNameString.GetString(); |
4744 | |
4745 | /* if a path is specified, only look there */ |
4746 | if(strchr(lpFileName, '/')) |
4747 | { |
4748 | if (access (lpFileName, F_OK) == 0) |
4749 | { |
4750 | if (!lpPathFileName.Set(lpFileNameString)) |
4751 | { |
4752 | TRACE("Set of StackString failed!\n" ); |
4753 | return FALSE; |
4754 | } |
4755 | |
4756 | TRACE("file %s exists\n" , lpFileName); |
4757 | return TRUE; |
4758 | } |
4759 | else |
4760 | { |
4761 | TRACE("file %s doesn't exist.\n" , lpFileName); |
4762 | return FALSE; |
4763 | } |
4764 | } |
4765 | |
4766 | /* first look in directory from which the application loaded */ |
4767 | lpwstr = g_lpwstrAppDir; |
4768 | |
4769 | if (lpwstr) |
4770 | { |
4771 | /* convert path to multibyte, check buffer size */ |
4772 | n = WideCharToMultiByte(CP_ACP, 0, lpwstr, -1, NULL, 0, |
4773 | NULL, NULL); |
4774 | |
4775 | if (!lpPathFileName.Reserve(n + lpFileNameString.GetCount() + 1 )) |
4776 | { |
4777 | ERROR("StackString Reserve failed!\n" ); |
4778 | return FALSE; |
4779 | } |
4780 | |
4781 | lpPath = lpPathFileName.OpenStringBuffer(n); |
4782 | |
4783 | n = WideCharToMultiByte(CP_ACP, 0, lpwstr, -1, lpPath, n, |
4784 | NULL, NULL); |
4785 | |
4786 | if (n == 0) |
4787 | { |
4788 | lpPathFileName.CloseBuffer(0); |
4789 | ASSERT("WideCharToMultiByte failure!\n" ); |
4790 | return FALSE; |
4791 | } |
4792 | |
4793 | lpPathFileName.CloseBuffer(n - 1); |
4794 | |
4795 | lpPathFileName.Append("/" , 1); |
4796 | lpPathFileName.Append(lpFileNameString); |
4797 | |
4798 | if (access(lpPathFileName, F_OK) == 0) |
4799 | { |
4800 | TRACE("found %s in application directory (%s)\n" , lpFileName, lpPathFileName.GetString()); |
4801 | return TRUE; |
4802 | } |
4803 | } |
4804 | |
4805 | /* then try the current directory */ |
4806 | if (!lpPathFileName.Reserve(lpFileNameString.GetCount() + 2)) |
4807 | { |
4808 | ERROR("StackString Reserve failed!\n" ); |
4809 | return FALSE; |
4810 | } |
4811 | |
4812 | lpPathFileName.Set("./" , 2); |
4813 | lpPathFileName.Append(lpFileNameString); |
4814 | |
4815 | if (access (lpPathFileName, R_OK) == 0) |
4816 | { |
4817 | TRACE("found %s in current directory.\n" , lpFileName); |
4818 | return TRUE; |
4819 | } |
4820 | |
4821 | pThread = InternalGetCurrentThread(); |
4822 | |
4823 | /* Then try to look in the path */ |
4824 | lpPath = EnvironGetenv("PATH" ); |
4825 | |
4826 | if (!lpPath) |
4827 | { |
4828 | ERROR("EnvironGetenv returned NULL for $PATH\n" ); |
4829 | return FALSE; |
4830 | } |
4831 | |
4832 | lpNext = lpPath; |
4833 | |
4834 | /* search in every path directory */ |
4835 | TRACE("looking for file %s in $PATH (%s)\n" , lpFileName, lpPath); |
4836 | while (lpNext) |
4837 | { |
4838 | /* skip all leading ':' */ |
4839 | while(*lpNext==':') |
4840 | { |
4841 | lpNext++; |
4842 | } |
4843 | |
4844 | /* search for ':' */ |
4845 | lpCurrent = strchr(lpNext, ':'); |
4846 | if (lpCurrent) |
4847 | { |
4848 | *lpCurrent++ = '\0'; |
4849 | } |
4850 | |
4851 | nextLen = strlen(lpNext); |
4852 | slashLen = (lpNext[nextLen-1] == '/') ? 0:1; |
4853 | |
4854 | if (!lpPathFileName.Reserve(nextLen + lpFileNameString.GetCount() + 1)) |
4855 | { |
4856 | free(lpPath); |
4857 | ERROR("StackString ran out of memory for full path\n" ); |
4858 | return FALSE; |
4859 | } |
4860 | |
4861 | lpPathFileName.Set(lpNext, nextLen); |
4862 | |
4863 | if( slashLen == 1) |
4864 | { |
4865 | /* append a '/' if there's no '/' at the end of the path */ |
4866 | lpPathFileName.Append("/" , 1); |
4867 | } |
4868 | |
4869 | lpPathFileName.Append(lpFileNameString); |
4870 | |
4871 | if ( access (lpPathFileName, F_OK) == 0) |
4872 | { |
4873 | TRACE("Found %s in $PATH element %s\n" , lpFileName, lpNext); |
4874 | free(lpPath); |
4875 | return TRUE; |
4876 | } |
4877 | |
4878 | lpNext = lpCurrent; /* search in the next directory */ |
4879 | } |
4880 | |
4881 | free(lpPath); |
4882 | TRACE("File %s not found in $PATH\n" , lpFileName); |
4883 | return FALSE; |
4884 | } |
4885 | |
4886 | /*++ |
4887 | Function: |
4888 | ~CProcProcessLocalData |
4889 | |
4890 | Process data destructor |
4891 | --*/ |
4892 | CorUnix::CProcProcessLocalData::~CProcProcessLocalData() |
4893 | { |
4894 | if (pProcessModules != NULL) |
4895 | { |
4896 | DestroyProcessModules(pProcessModules); |
4897 | } |
4898 | } |
4899 | |
4900 | |