2 * process.c: System.Diagnostics.Process support
5 * Dick Porter (dick@ximian.com)
7 * (C) 2002 Ximian, Inc.
14 #include <mono/metadata/object.h>
15 #include <mono/metadata/process.h>
16 #include <mono/metadata/assembly.h>
17 #include <mono/metadata/appdomain.h>
18 #include <mono/metadata/image.h>
19 #include <mono/metadata/cil-coff.h>
20 #include <mono/io-layer/io-layer.h>
24 HANDLE ves_icall_System_Diagnostics_Process_GetCurrentProcess_internal (void)
28 /* GetCurrentProcess returns a pseudo-handle, so use
31 handle=OpenProcess (PROCESS_ALL_ACCESS, TRUE, GetCurrentProcessId ());
34 /* FIXME: Throw an exception */
41 guint32 ves_icall_System_Diagnostics_Process_GetPid_internal (void)
43 return(GetCurrentProcessId ());
46 void ves_icall_System_Diagnostics_Process_Process_free_internal (MonoObject *this,
50 g_message (G_GNUC_PRETTY_FUNCTION ": Closing process %p, handle %p",
54 CloseHandle (process);
57 #define STASH_SYS_ASS(this) \
58 if(system_assembly == NULL) { \
59 system_assembly=this->vtable->klass->image; \
62 static MonoImage *system_assembly=NULL;
64 static guint32 unicode_chars (const gunichar2 *str)
76 static guint32 unicode_bytes (const gunichar2 *str)
82 /* Include the terminators */
89 static void process_set_field_object (MonoObject *obj, const guchar *fieldname,
92 MonoClassField *field;
95 g_message (G_GNUC_PRETTY_FUNCTION ": Setting field %s to object at %p",
99 field=mono_class_get_field_from_name (mono_object_class (obj),
101 *(MonoObject **)(((char *)obj) + field->offset)=data;
104 static void process_set_field_string (MonoObject *obj, const guchar *fieldname,
105 const gunichar2 *val, guint32 len)
107 MonoClassField *field;
111 g_message (G_GNUC_PRETTY_FUNCTION ": Setting field %s to [%s]",
112 fieldname, g_utf16_to_utf8 (val, len, NULL, NULL, NULL));
115 string=mono_string_new_utf16 (mono_object_domain (obj), val, len);
117 field=mono_class_get_field_from_name (mono_object_class (obj),
119 *(MonoString **)(((char *)obj) + field->offset)=string;
122 static void process_set_field_string_utf8 (MonoObject *obj,
123 const guchar *fieldname,
126 MonoClassField *field;
130 g_message (G_GNUC_PRETTY_FUNCTION ": Setting field %s to [%s]",
134 string=mono_string_new (mono_object_domain (obj), val);
136 field=mono_class_get_field_from_name (mono_object_class (obj),
138 *(MonoString **)(((char *)obj) + field->offset)=string;
141 static void process_set_field_int (MonoObject *obj, const guchar *fieldname,
144 MonoClassField *field;
147 g_message (G_GNUC_PRETTY_FUNCTION ": Setting field %s to %d",
151 field=mono_class_get_field_from_name (mono_object_class (obj),
153 *(guint32 *)(((char *)obj) + field->offset)=val;
156 static void process_set_field_bool (MonoObject *obj, const guchar *fieldname,
159 MonoClassField *field;
162 g_message (G_GNUC_PRETTY_FUNCTION ": Setting field %s to %s",
163 fieldname, val?"TRUE":"FALSE");
166 field=mono_class_get_field_from_name (mono_object_class (obj),
168 *(guint8 *)(((char *)obj) + field->offset)=val;
178 /* Returns a pointer to the value data, because theres no way to know
179 * how big that data is (value_len is set to zero for most blocks :-()
181 static gpointer process_get_versioninfo_block (gpointer data,
184 block->data_len=*(((guint16 *)data)++);
185 block->value_len=*(((guint16 *)data)++);
187 /* No idea what the type is supposed to indicate */
188 block->type=*(((guint16 *)data)++);
189 block->key=((gunichar2 *)data);
191 /* skip over the key (including the terminator) */
192 data=((gunichar2 *)data)+(unicode_chars (block->key)+1);
194 /* align on a 32-bit boundary */
195 data=(gpointer)(((unsigned)data+3) & (~3));
200 /* Returns a pointer to the byte following the Var block */
201 static gpointer process_read_var_block (MonoObject *filever, gpointer data_ptr,
204 /* Not currently interested in the VarFileInfo block. This
205 * might change if language support is needed for file version
206 * strings (VarFileInfo contains lists of supported
211 /* data_ptr is pointing at a Var block of length data_len */
212 data_ptr=process_get_versioninfo_block (data_ptr, &block);
213 data_ptr=((guchar *)data_ptr)+block.value_len;
218 /* Returns a pointer to the byte following the String block */
219 static gpointer process_read_string_block (MonoObject *filever,
225 guint16 string_len=0;
226 guchar comments_key[]= {'C', '\0', 'o', '\0', 'm', '\0',
227 'm', '\0', 'e', '\0', 'n', '\0',
228 't', '\0', 's', '\0', '\0', '\0'};
229 guchar compname_key[]= {'C', '\0', 'o', '\0', 'm', '\0',
230 'p', '\0', 'a', '\0', 'n', '\0',
231 'y', '\0', 'N', '\0', 'a', '\0',
232 'm', '\0', 'e', '\0', '\0', '\0'};
233 guchar filedesc_key[]= {'F', '\0', 'i', '\0', 'l', '\0',
234 'e', '\0', 'D', '\0', 'e', '\0',
235 's', '\0', 'c', '\0', 'r', '\0',
236 'i', '\0', 'p', '\0', 't', '\0',
237 'i', '\0', 'o', '\0', 'n', '\0',
239 guchar filever_key[]= {'F', '\0', 'i', '\0', 'l', '\0',
240 'e', '\0', 'V', '\0', 'e', '\0',
241 'r', '\0', 's', '\0', 'i', '\0',
242 'o', '\0', 'n', '\0', '\0', '\0'};
243 guchar internal_key[]= {'I', '\0', 'n', '\0', 't', '\0',
244 'e', '\0', 'r', '\0', 'n', '\0',
245 'a', '\0', 'l', '\0', 'N', '\0',
246 'a', '\0', 'm', '\0', 'e', '\0',
248 guchar legalcpy_key[]= {'L', '\0', 'e', '\0', 'g', '\0',
249 'a', '\0', 'l', '\0', 'C', '\0',
250 'o', '\0', 'p', '\0', 'y', '\0',
251 'r', '\0', 'i', '\0', 'g', '\0',
252 'h', '\0', 't', '\0', '\0', '\0'};
253 guchar legaltrade_key[]= {'L', '\0', 'e', '\0', 'g', '\0',
254 'a', '\0', 'l', '\0', 'T', '\0',
255 'r', '\0', 'a', '\0', 'd', '\0',
256 'e', '\0', 'm', '\0', 'a', '\0',
257 'r', '\0', 'k', '\0', 's', '\0',
259 guchar origfile_key[]= {'O', '\0', 'r', '\0', 'i', '\0',
260 'g', '\0', 'i', '\0', 'n', '\0',
261 'a', '\0', 'l', '\0', 'F', '\0',
262 'i', '\0', 'l', '\0', 'e', '\0',
263 'n', '\0', 'a', '\0', 'm', '\0',
264 'e', '\0', '\0', '\0'};
265 guchar privbuild_key[]= {'P', '\0', 'r', '\0', 'i', '\0',
266 'v', '\0', 'a', '\0', 't', '\0',
267 'e', '\0', 'B', '\0', 'u', '\0',
268 'i', '\0', 'l', '\0', 'd', '\0',
270 guchar prodname_key[]= {'P', '\0', 'r', '\0', 'o', '\0',
271 'd', '\0', 'u', '\0', 'c', '\0',
272 't', '\0', 'N', '\0', 'a', '\0',
273 'm', '\0', 'e', '\0', '\0', '\0'};
274 guchar prodver_key[]= {'P', '\0', 'r', '\0', 'o', '\0',
275 'd', '\0', 'u', '\0', 'c', '\0',
276 't', '\0', 'V', '\0', 'e', '\0',
277 'r', '\0', 's', '\0', 'i', '\0',
278 'o', '\0', 'n', '\0', '\0', '\0'};
279 guchar specbuild_key[]= {'S', '\0', 'p', '\0', 'e', '\0',
280 'c', '\0', 'i', '\0', 'a', '\0',
281 'l', '\0', 'B', '\0', 'u', '\0',
282 'i', '\0', 'l', '\0', 'd', '\0',
285 /* data_ptr is pointing at an array of one or more String
286 * blocks with total length (not including alignment padding)
289 while(string_len<data_len) {
292 /* align on a 32-bit boundary */
293 data_ptr=(gpointer)(((unsigned)data_ptr+3) & (~3));
295 data_ptr=process_get_versioninfo_block (data_ptr, &block);
296 string_len=string_len+block.data_len;
297 value=(gunichar2 *)data_ptr;
298 /* Skip over the value */
299 data_ptr=((gunichar2 *)data_ptr)+block.value_len;
302 if(!memcmp (block.key, &comments_key,
303 unicode_bytes (block.key))) {
304 process_set_field_string (filever, "comments", value, unicode_chars (value));
305 } else if (!memcmp (block.key, &compname_key,
306 unicode_bytes (block.key))) {
307 process_set_field_string (filever, "companyname", value, unicode_chars (value));
308 } else if (!memcmp (block.key, &filedesc_key,
309 unicode_bytes (block.key))) {
310 process_set_field_string (filever, "filedescription", value, unicode_chars (value));
311 } else if (!memcmp (block.key, &filever_key,
312 unicode_bytes (block.key))) {
313 process_set_field_string (filever, "fileversion", value, unicode_chars (value));
314 } else if (!memcmp (block.key, &internal_key,
315 unicode_bytes (block.key))) {
316 process_set_field_string (filever, "internalname", value, unicode_chars (value));
317 } else if (!memcmp (block.key, &legalcpy_key,
318 unicode_bytes (block.key))) {
319 process_set_field_string (filever, "legalcopyright", value, unicode_chars (value));
320 } else if (!memcmp (block.key, &legaltrade_key,
321 unicode_bytes (block.key))) {
322 process_set_field_string (filever, "legaltrademarks", value, unicode_chars (value));
323 } else if (!memcmp (block.key, &origfile_key,
324 unicode_bytes (block.key))) {
325 process_set_field_string (filever, "originalfilename", value, unicode_chars (value));
326 } else if (!memcmp (block.key, &privbuild_key,
327 unicode_bytes (block.key))) {
328 process_set_field_string (filever, "privatebuild", value, unicode_chars (value));
329 } else if (!memcmp (block.key, &prodname_key,
330 unicode_bytes (block.key))) {
331 process_set_field_string (filever, "productname", value, unicode_chars (value));
332 } else if (!memcmp (block.key, &prodver_key,
333 unicode_bytes (block.key))) {
334 process_set_field_string (filever, "productversion", value, unicode_chars (value));
335 } else if (!memcmp (block.key, &specbuild_key,
336 unicode_bytes (block.key))) {
337 process_set_field_string (filever, "specialbuild", value, unicode_chars (value));
339 /* Not an error, just not interesting
349 /* returns a pointer to the byte following the Stringtable block */
350 static gpointer process_read_stringtable_block (MonoObject *filever,
355 guint16 string_len=36; /* length of the StringFileInfo block */
357 /* Specifies language-neutral unicode string block */
358 guchar uni_key[]= {'0', '\0', '0', '\0', '0', '\0', '0', '\0',
359 '0', '\0', '4', '\0', 'b', '\0', '0', '\0',
362 guchar uni_key_uc[]= {'0', '\0', '0', '\0', '0', '\0', '0', '\0',
363 '0', '\0', '4', '\0', 'B', '\0', '0', '\0',
367 /* data_ptr is pointing at an array of StringTable blocks,
368 * with total length (not including alignment padding) of
372 while(string_len<data_len) {
373 /* align on a 32-bit boundary */
374 data_ptr=(gpointer)(((unsigned)data_ptr+3) & (~3));
376 data_ptr=process_get_versioninfo_block (data_ptr, &block);
377 string_len=string_len+block.data_len;
379 if(!memcmp (block.key, &uni_key, unicode_bytes (block.key)) ||
380 !memcmp (block.key, &uni_key_uc, unicode_bytes (block.key))) {
381 /* Got the one we're interested in */
382 process_set_field_string_utf8 (filever, "language",
385 data_ptr=process_read_string_block (filever, data_ptr,
389 /* Some other language. We might want to do
390 * something with this in the future.
392 data_ptr=process_read_string_block (filever, data_ptr,
401 static void process_read_fixedfileinfo_block (MonoObject *filever,
402 VS_FIXEDFILEINFO *ffi)
405 g_message (G_GNUC_PRETTY_FUNCTION ": ffi: sig 0x%x, strucver 0x%x, fileverm 0x%x, fileverl 0x%x, prodverm 0x%x, prodverl 0x%x, ffmask 0x%x, ff 0x%x, os 0x%x, type 0x%x, subtype 0x%x, datem 0x%x, datel 0x%x", ffi->dwSignature, ffi->dwStrucVersion, ffi->dwFileVersionMS, ffi->dwFileVersionLS, ffi->dwProductVersionMS, ffi->dwProductVersionLS, ffi->dwFileFlagsMask, ffi->dwFileFlags, ffi->dwFileOS, ffi->dwFileType, ffi->dwFileSubtype, ffi->dwFileDateMS, ffi->dwFileDateLS);
408 process_set_field_int (filever, "filemajorpart",
409 HIWORD (ffi->dwFileVersionMS));
410 process_set_field_int (filever, "fileminorpart",
411 LOWORD (ffi->dwFileVersionMS));
412 process_set_field_int (filever, "filebuildpart",
413 HIWORD (ffi->dwFileVersionLS));
414 process_set_field_int (filever, "fileprivatepart",
415 LOWORD (ffi->dwFileVersionLS));
417 process_set_field_int (filever, "productmajorpart",
418 HIWORD (ffi->dwProductVersionMS));
419 process_set_field_int (filever, "productminorpart",
420 LOWORD (ffi->dwProductVersionMS));
421 process_set_field_int (filever, "productbuildpart",
422 HIWORD (ffi->dwProductVersionLS));
423 process_set_field_int (filever, "productprivatepart",
424 LOWORD (ffi->dwProductVersionLS));
426 process_set_field_bool (filever, "isdebug",
427 ffi->dwFileFlags&VS_FF_DEBUG);
428 process_set_field_bool (filever, "isprerelease",
429 ffi->dwFileFlags&VS_FF_PRERELEASE);
430 process_set_field_bool (filever, "ispatched",
431 ffi->dwFileFlags&VS_FF_PATCHED);
432 process_set_field_bool (filever, "isprivatebuild",
433 ffi->dwFileFlags&VS_FF_PRIVATEBUILD);
434 process_set_field_bool (filever, "isspecialbuild",
435 ffi->dwFileFlags&VS_FF_SPECIALBUILD);
438 static void process_get_fileversion (MonoObject *filever, MonoImage *image)
440 MonoPEResourceDataEntry *version_info;
442 VS_FIXEDFILEINFO *ffi;
445 gint32 data_len; /* signed to guard against underflow */
446 guchar vs_key[]= {'V', '\0', 'S', '\0', '_', '\0', 'V', '\0',
447 'E', '\0', 'R', '\0', 'S', '\0', 'I', '\0',
448 'O', '\0', 'N', '\0', '_', '\0', 'I', '\0',
449 'N', '\0', 'F', '\0', 'O', '\0', '\0', '\0'
451 guchar var_key[]= {'V', '\0', 'a', '\0', 'r', '\0', 'F', '\0',
452 'i', '\0', 'l', '\0', 'e', '\0', 'I', '\0',
453 'n', '\0', 'f', '\0', 'o', '\0', '\0', '\0',
455 guchar str_key[]= {'S', '\0', 't', '\0', 'r', '\0', 'i', '\0',
456 'n', '\0', 'g', '\0', 'F', '\0', 'i', '\0',
457 'l', '\0', 'e', '\0', 'I', '\0', 'n', '\0',
458 'f', '\0', 'o', '\0', '\0', '\0',
461 version_info=mono_image_lookup_resource (image,
462 MONO_PE_RESOURCE_ID_VERSION,
465 g_message (G_GNUC_PRETTY_FUNCTION ": image_lookup returned %p",
469 if(version_info==NULL) {
473 data=mono_cli_rva_map (image->image_info,
474 version_info->rde_data_offset);
479 /* See io-layer/versioninfo.h for the gory details on how this
480 * data is laid out. (data should be pointing to
481 * VS_VERSIONINFO data).
484 data_ptr=process_get_versioninfo_block (data, &block);
486 data_len=block.data_len;
488 if(block.value_len!=sizeof(VS_FIXEDFILEINFO)) {
490 g_message (G_GNUC_PRETTY_FUNCTION
491 ": FIXEDFILEINFO size mismatch");
496 if(memcmp (block.key, &vs_key, unicode_bytes (block.key))) {
498 g_message (G_GNUC_PRETTY_FUNCTION
499 ": VS_VERSION_INFO mismatch");
504 ffi=(((VS_FIXEDFILEINFO *)data_ptr)++);
505 if((ffi->dwSignature!=VS_FFI_SIGNATURE) ||
506 (ffi->dwStrucVersion!=VS_FFI_STRUCVERSION)) {
508 g_message (G_GNUC_PRETTY_FUNCTION
509 ": FIXEDFILEINFO bad signature");
513 process_read_fixedfileinfo_block (filever, ffi);
515 /* Subtract the 92 bytes we've already seen */
518 /* There now follow zero or one StringFileInfo blocks and zero
519 * or one VarFileInfo blocks
521 while(data_len > 0) {
522 /* align on a 32-bit boundary */
523 data_ptr=(gpointer)(((unsigned)data_ptr+3) & (~3));
525 data_ptr=process_get_versioninfo_block (data_ptr, &block);
526 data_len=data_len-block.data_len;
528 if(!memcmp (block.key, &var_key, unicode_bytes (block.key))) {
529 data_ptr=process_read_var_block (filever, data_ptr,
531 } else if (!memcmp (block.key, &str_key,
532 unicode_bytes (block.key))) {
533 data_ptr=process_read_stringtable_block (filever, data_ptr, block.data_len);
537 g_message (G_GNUC_PRETTY_FUNCTION
538 ": Not a valid VERSIONINFO child block");
545 static void process_add_module (GPtrArray *modules, MonoAssembly *ass)
547 MonoClass *proc_class, *filever_class;
548 MonoObject *item, *filever;
549 MonoDomain *domain=mono_domain_get ();
552 /* Build a System.Diagnostics.ProcessModule with the data.
553 * Leave BaseAddress and EntryPointAddress set to NULL,
554 * FileName is ass->image->name, FileVersionInfo is an object
555 * constructed from the PE image data referenced by
556 * ass->image, ModuleMemorySize set to 0, ModuleName the last
557 * component of FileName.
559 proc_class=mono_class_from_name (system_assembly, "System.Diagnostics",
561 item=mono_object_new (domain, proc_class);
563 filever_class=mono_class_from_name (system_assembly,
564 "System.Diagnostics",
566 filever=mono_object_new (domain, filever_class);
569 g_message (G_GNUC_PRETTY_FUNCTION ": recording assembly: FileName [%s] FileVersionInfo [%d.%d.%d.%d], ModuleName [%s]", ass->image->name, ass->aname.major, ass->aname.minor, ass->aname.build, ass->aname.revision, ass->image->name);
572 process_get_fileversion (filever, ass->image);
574 process_set_field_string_utf8 (filever, "filename", ass->image->name);
575 process_set_field_string_utf8 (item, "filename", ass->image->name);
576 process_set_field_object (item, "version_info", filever);
578 modulename=g_path_get_basename (ass->image->name);
579 process_set_field_string_utf8 (item, "modulename", modulename);
582 g_ptr_array_add (modules, item);
585 static void process_scan_modules (gpointer data, gpointer user_data)
587 MonoAssembly *ass=data;
588 GPtrArray *modules=user_data;
590 /* The main assembly is already in the list */
591 if(mono_assembly_get_main () != ass) {
592 process_add_module (modules, ass);
597 /* Returns an array of System.Diagnostics.ProcessModule */
598 MonoArray *ves_icall_System_Diagnostics_Process_GetModules_internal (MonoObject *this)
600 /* I was going to use toolhelp for this, but then realised I
601 * was being an idiot :)
603 * (Toolhelp would give shared libraries open by the runtime,
604 * as well as open assemblies. On windows my tests didnt find
605 * the assemblies loaded by mono either.)
607 GPtrArray *modules_list=g_ptr_array_new ();
611 STASH_SYS_ASS (this);
613 /* Make sure the first entry is the main module */
614 process_add_module (modules_list, mono_assembly_get_main ());
616 mono_assembly_foreach (process_scan_modules, modules_list);
618 /* Build a MonoArray out of modules_list */
619 arr=mono_array_new (mono_domain_get (), mono_defaults.object_class,
622 for(i=0; i<modules_list->len; i++) {
623 mono_array_set (arr, MonoObject *, i,
624 g_ptr_array_index (modules_list, i));
627 g_ptr_array_free (modules_list, FALSE);
632 void ves_icall_System_Diagnostics_FileVersionInfo_GetVersionInfo_internal (MonoObject *this, MonoString *filename)
635 guchar *filename_utf8;
637 STASH_SYS_ASS (this);
639 filename_utf8=mono_string_to_utf8 (filename);
640 image=mono_image_open (filename_utf8, NULL);
641 g_free (filename_utf8);
644 /* FIXME: an exception might be appropriate here */
646 g_message (G_GNUC_PRETTY_FUNCTION ": Failed to load image");
652 process_get_fileversion (this, image);
653 process_set_field_string_utf8 (this, "filename", image->name);
655 mono_image_close (image);
658 MonoBoolean ves_icall_System_Diagnostics_Process_Start_internal (MonoString *filename, MonoString *args, MonoProcInfo *process_info)
661 gunichar2 *utf16_filename;
662 gunichar2 *utf16_args;
663 STARTUPINFO startinfo;
664 PROCESS_INFORMATION procinfo;
666 utf16_filename=mono_string_to_utf16 (filename);
667 utf16_args=mono_string_to_utf16 (args);
669 ret=CreateProcess (utf16_filename, utf16_args, NULL, NULL, TRUE, CREATE_UNICODE_ENVIRONMENT, NULL, NULL, &startinfo, &procinfo);
671 g_free (utf16_filename);
675 process_info->process_handle=procinfo.hProcess;
676 process_info->thread_handle=procinfo.hThread;
677 process_info->pid=procinfo.dwProcessId;
678 process_info->tid=procinfo.dwThreadId;
684 MonoBoolean ves_icall_System_Diagnostics_Process_WaitForExit_internal (MonoObject *this, HANDLE process, gint32 ms)
690 ret=WaitForSingleObject (process, INFINITE);
692 ret=WaitForSingleObject (process, ms);
695 if(ret==WAIT_OBJECT_0) {
702 gint64 ves_icall_System_Diagnostics_Process_ExitTime_internal (HANDLE process)
706 FILETIME create_time, exit_time, kernel_time, user_time;
708 ret=GetProcessTimes (process, &create_time, &exit_time, &kernel_time,
711 ticks=((guint64)exit_time.dwHighDateTime << 32) +
712 exit_time.dwLowDateTime;
720 gint64 ves_icall_System_Diagnostics_Process_StartTime_internal (HANDLE process)
724 FILETIME create_time, exit_time, kernel_time, user_time;
726 ret=GetProcessTimes (process, &create_time, &exit_time, &kernel_time,
729 ticks=((guint64)create_time.dwHighDateTime << 32) +
730 create_time.dwLowDateTime;
738 gint32 ves_icall_System_Diagnostics_Process_ExitCode_internal (HANDLE process)
742 GetExitCodeProcess (process, &code);
745 g_message (G_GNUC_PRETTY_FUNCTION ": process exit code is %d", code);