newlib/winsup/utils/dumper.cc
Jon Turney ba2f251d43
Cygwin: Update dumper for bfd API changes
Update dumper for bfd API changes in binutils 2.34

libbfd doesn't guarantee API stability, so we've just been lucky this
hasn't broken more often.

See binutils commit fd361982.
2020-02-27 17:37:49 +00:00

971 lines
22 KiB
C++

/* dumper.cc
Written by Egor Duda <deo@logos-m.ru>
This file is part of Cygwin.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License (file COPYING.dumper) for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA. */
#include <ansidecl.h>
#define PACKAGE
#include <bfd.h>
#include <elf/common.h>
#include <elf/external.h>
#include <sys/procfs.h>
#include <sys/cygwin.h>
#include <cygwin/version.h>
#include <getopt.h>
#include <stdarg.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/param.h>
#include <windows.h>
#include "dumper.h"
#define NOTE_NAME_SIZE 16
#ifdef bfd_get_section_size
/* for bfd < 2.34 */
#define get_section_name(abfd, sect) bfd_get_section_name (abfd, sect)
#define get_section_size(sect) bfd_get_section_size(sect)
#define set_section_size(abfd, sect, size) bfd_set_section_size(abfd, sect, size)
#define set_section_flags(abfd, sect, flags) bfd_set_section_flags(abfd, sect, flags)
#else
/* otherwise bfd >= 2.34 */
#define get_section_name(afbd, sect) bfd_section_name (sect)
#define get_section_size(sect) bfd_section_size(sect)
#define set_section_size(abfd, sect, size) bfd_set_section_size(sect, size)
#define set_section_flags(abfd, sect, flags) bfd_set_section_flags(sect, flags)
#endif
typedef struct _note_header
{
Elf_External_Note elf_note_header;
char name[NOTE_NAME_SIZE - 1]; /* external note contains first byte of data */
}
#ifdef __GNUC__
__attribute__ ((packed))
#endif
note_header;
BOOL verbose = FALSE;
int deb_printf (const char *format,...)
{
if (!verbose)
return 0;
va_list va;
va_start (va, format);
int ret_val = vprintf (format, va);
va_end (va);
return ret_val;
}
dumper::dumper (DWORD pid, DWORD tid, const char *file_name)
{
this->file_name = strdup (file_name);
this->pid = pid;
this->tid = tid;
core_bfd = NULL;
excl_list = new exclusion (20);
list = last = NULL;
status_section = NULL;
memory_num = module_num = thread_num = 0;
hProcess = OpenProcess (PROCESS_ALL_ACCESS,
FALSE, /* no inheritance */
pid);
if (!hProcess)
{
fprintf (stderr, "Failed to open process #%u, error %ld\n",
(unsigned int) pid, (long) GetLastError ());
return;
}
init_core_dump ();
if (!sane ())
dumper_abort ();
}
dumper::~dumper ()
{
close ();
free (file_name);
}
void
dumper::dumper_abort ()
{
close ();
unlink (file_name);
}
void
dumper::close ()
{
if (core_bfd)
bfd_close (core_bfd);
if (excl_list)
delete excl_list;
if (hProcess)
CloseHandle (hProcess);
core_bfd = NULL;
hProcess = NULL;
excl_list = NULL;
}
int
dumper::sane ()
{
if (hProcess == NULL || core_bfd == NULL || excl_list == NULL)
return 0;
return 1;
}
void
print_section_name (bfd* abfd, asection* sect, PTR obj)
{
deb_printf (" %s", get_section_name (abfd, sect));
}
void
dumper::print_core_section_list ()
{
deb_printf ("current sections:");
bfd_map_over_sections (core_bfd, &print_section_name, NULL);
deb_printf ("\n");
}
process_entity *
dumper::add_process_entity_to_list (process_entity_type type)
{
if (!sane ())
return NULL;
process_entity *new_entity = (process_entity *) malloc (sizeof (process_entity));
if (new_entity == NULL)
return NULL;
new_entity->next = NULL;
new_entity->section = NULL;
if (last == NULL)
list = new_entity;
else
last->next = new_entity;
last = new_entity;
return new_entity;
}
int
dumper::add_thread (DWORD tid, HANDLE hThread)
{
if (!sane ())
return 0;
CONTEXT *pcontext;
process_entity *new_entity = add_process_entity_to_list (pr_ent_thread);
if (new_entity == NULL)
return 0;
new_entity->type = pr_ent_thread;
thread_num++;
new_entity->u.thread.tid = tid;
new_entity->u.thread.hThread = hThread;
pcontext = &(new_entity->u.thread.context);
pcontext->ContextFlags = CONTEXT_FULL | CONTEXT_FLOATING_POINT;
if (!GetThreadContext (hThread, pcontext))
{
deb_printf ("Failed to read thread context (tid=%x), error %ld\n", tid, GetLastError ());
return 0;
}
deb_printf ("added thread %u\n", tid);
return 1;
}
int
dumper::add_mem_region (LPBYTE base, SIZE_T size)
{
if (!sane ())
return 0;
if (base == NULL || size == 0)
return 1; // just ignore empty regions
process_entity *new_entity = add_process_entity_to_list (pr_ent_memory);
if (new_entity == NULL)
return 0;
new_entity->type = pr_ent_memory;
memory_num++;
new_entity->u.memory.base = base;
new_entity->u.memory.size = size;
deb_printf ("added memory region %p-%p\n", base, base + size);
return 1;
}
/* split_add_mem_region scans list of regions to be excluded from dumping process
(excl_list) and removes all "excluded" parts from given region. */
int
dumper::split_add_mem_region (LPBYTE base, SIZE_T size)
{
if (!sane ())
return 0;
if (base == NULL || size == 0)
return 1; // just ignore empty regions
LPBYTE last_base = base;
for (process_mem_region * p = excl_list->region;
p < excl_list->region + excl_list->last;
p++)
{
if (p->base >= base + size || p->base + p->size <= base)
continue;
if (p->base <= base)
{
last_base = p->base + p->size;
continue;
}
add_mem_region (last_base, p->base - last_base);
last_base = p->base + p->size;
}
if (last_base < base + size)
add_mem_region (last_base, base + size - last_base);
return 1;
}
int
dumper::add_module (LPVOID base_address)
{
if (!sane ())
return 0;
char *module_name = psapi_get_module_name (hProcess, base_address);
if (module_name == NULL)
return 1;
process_entity *new_entity = add_process_entity_to_list (pr_ent_module);
if (new_entity == NULL)
return 0;
new_entity->type = pr_ent_module;
module_num++;
new_entity->u.module.base_address = base_address;
new_entity->u.module.name = module_name;
parse_pe (module_name, excl_list);
deb_printf ("added module %p %s\n", base_address, module_name);
return 1;
}
#define PAGE_BUFFER_SIZE 4096
int
dumper::collect_memory_sections ()
{
if (!sane ())
return 0;
LPBYTE current_page_address;
LPBYTE last_base = (LPBYTE) 0xFFFFFFFF;
SIZE_T last_size = (SIZE_T) 0;
SIZE_T done;
char mem_buf[PAGE_BUFFER_SIZE];
MEMORY_BASIC_INFORMATION mbi;
if (hProcess == NULL)
return 0;
for (current_page_address = 0; current_page_address < (LPBYTE) 0xFFFF0000;)
{
if (!VirtualQueryEx (hProcess, current_page_address, &mbi, sizeof (mbi)))
break;
int skip_region_p = 0;
if (mbi.Protect & (PAGE_NOACCESS | PAGE_GUARD) ||
mbi.State != MEM_COMMIT)
skip_region_p = 1;
if (!skip_region_p)
{
/* just to make sure that later we'll be able to read it.
According to MS docs either region is all-readable or
all-nonreadable */
if (!ReadProcessMemory (hProcess, current_page_address, mem_buf, sizeof (mem_buf), &done))
{
DWORD err = GetLastError ();
const char *pt[10];
pt[0] = (mbi.Protect & PAGE_READONLY) ? "RO " : "";
pt[1] = (mbi.Protect & PAGE_READWRITE) ? "RW " : "";
pt[2] = (mbi.Protect & PAGE_WRITECOPY) ? "WC " : "";
pt[3] = (mbi.Protect & PAGE_EXECUTE) ? "EX " : "";
pt[4] = (mbi.Protect & PAGE_EXECUTE_READ) ? "EXRO " : "";
pt[5] = (mbi.Protect & PAGE_EXECUTE_READWRITE) ? "EXRW " : "";
pt[6] = (mbi.Protect & PAGE_EXECUTE_WRITECOPY) ? "EXWC " : "";
pt[7] = (mbi.Protect & PAGE_GUARD) ? "GRD " : "";
pt[8] = (mbi.Protect & PAGE_NOACCESS) ? "NA " : "";
pt[9] = (mbi.Protect & PAGE_NOCACHE) ? "NC " : "";
char buf[10 * 6];
buf[0] = '\0';
for (int i = 0; i < 10; i++)
strcat (buf, pt[i]);
deb_printf ("warning: failed to read memory at %p-%p (protect = %s), error %ld.\n",
current_page_address,
current_page_address + mbi.RegionSize,
buf, err);
skip_region_p = 1;
}
}
if (!skip_region_p)
{
if (last_base + last_size == current_page_address)
last_size += mbi.RegionSize;
else
{
split_add_mem_region (last_base, last_size);
last_base = (LPBYTE) mbi.BaseAddress;
last_size = mbi.RegionSize;
}
}
else
{
split_add_mem_region (last_base, last_size);
last_base = NULL;
last_size = 0;
}
current_page_address += mbi.RegionSize;
}
/* dump last sections, if any */
split_add_mem_region (last_base, last_size);
return 1;
};
int
dumper::dump_memory_region (asection * to, process_mem_region * memory)
{
if (!sane ())
return 0;
SIZE_T size = memory->size;
SIZE_T todo;
SIZE_T done;
LPBYTE pos = memory->base;
DWORD sect_pos = 0;
if (to == NULL || memory == NULL)
return 0;
char mem_buf[PAGE_BUFFER_SIZE];
while (size > 0)
{
todo = MIN (size, PAGE_BUFFER_SIZE);
if (!ReadProcessMemory (hProcess, pos, mem_buf, todo, &done))
{
deb_printf ("Failed to read process memory at %x(%x), error %ld\n", pos, todo, GetLastError ());
return 0;
}
size -= done;
pos += done;
if (!bfd_set_section_contents (core_bfd, to, mem_buf, sect_pos, done))
{
bfd_perror ("writing memory region to bfd");
dumper_abort ();
return 0;
};
sect_pos += done;
}
return 1;
}
int
dumper::dump_thread (asection * to, process_thread * thread)
{
if (!sane ())
return 0;
if (to == NULL || thread == NULL)
return 0;
win32_pstatus thread_pstatus;
note_header header;
bfd_putl32 (NOTE_NAME_SIZE, header.elf_note_header.namesz);
bfd_putl32 (sizeof (thread_pstatus), header.elf_note_header.descsz);
bfd_putl32 (NT_WIN32PSTATUS, header.elf_note_header.type);
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wstringop-overflow"
#pragma GCC diagnostic ignored "-Warray-bounds"
strncpy (header.elf_note_header.name, "win32thread", NOTE_NAME_SIZE);
#pragma GCC diagnostic pop
thread_pstatus.data_type = NOTE_INFO_THREAD;
thread_pstatus.data.thread_info.tid = thread->tid;
if (tid == 0)
{
/* this is a special case. we don't know, which thread
was active when exception occured, so let's blame
the first one */
thread_pstatus.data.thread_info.is_active_thread = TRUE;
tid = (DWORD) - 1;
}
else if (tid > 0 && thread->tid == tid)
thread_pstatus.data.thread_info.is_active_thread = TRUE;
else
thread_pstatus.data.thread_info.is_active_thread = FALSE;
memcpy (&(thread_pstatus.data.thread_info.thread_context),
&(thread->context),
sizeof (thread->context));
if (!bfd_set_section_contents (core_bfd, to, &header,
0,
sizeof (header)) ||
!bfd_set_section_contents (core_bfd, to, &thread_pstatus,
sizeof (header),
sizeof (thread_pstatus)))
{
bfd_perror ("writing thread info to bfd");
dumper_abort ();
return 0;
};
return 1;
}
int
dumper::dump_module (asection * to, process_module * module)
{
if (!sane ())
return 0;
if (to == NULL || module == NULL)
return 0;
struct win32_pstatus *module_pstatus_ptr;
int note_length = sizeof (struct win32_pstatus) + strlen (module->name);
char *buf = (char *) malloc (note_length);
if (!buf)
{
fprintf (stderr, "Error alloating memory. Dumping aborted.\n");
goto out;
};
module_pstatus_ptr = (struct win32_pstatus *) buf;
note_header header;
bfd_putl32 (NOTE_NAME_SIZE, header.elf_note_header.namesz);
bfd_putl32 (note_length, header.elf_note_header.descsz);
bfd_putl32 (NT_WIN32PSTATUS, header.elf_note_header.type);
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wstringop-overflow"
#pragma GCC diagnostic ignored "-Warray-bounds"
strncpy (header.elf_note_header.name, "win32module", NOTE_NAME_SIZE);
#pragma GCC diagnostic pop
module_pstatus_ptr->data_type = NOTE_INFO_MODULE;
module_pstatus_ptr->data.module_info.base_address = module->base_address;
module_pstatus_ptr->data.module_info.module_name_size = strlen (module->name) + 1;
strcpy (module_pstatus_ptr->data.module_info.module_name, module->name);
if (!bfd_set_section_contents (core_bfd, to, &header,
0,
sizeof (header)) ||
!bfd_set_section_contents (core_bfd, to, module_pstatus_ptr,
sizeof (header),
note_length))
{
bfd_perror ("writing module info to bfd");
goto out;
};
return 1;
out:
if (buf)
free (buf);
dumper_abort ();
return 0;
}
int
dumper::collect_process_information ()
{
int exception_level = 0;
if (!sane ())
return 0;
if (!DebugActiveProcess (pid))
{
fprintf (stderr, "Cannot attach to process #%u, error %ld",
(unsigned int) pid, (long) GetLastError ());
return 0;
}
char event_name[sizeof ("cygwin_error_start_event") + 20];
sprintf (event_name, "cygwin_error_start_event%16x", (unsigned int) pid);
HANDLE sync_with_debugee = OpenEvent (EVENT_MODIFY_STATE, FALSE, event_name);
DEBUG_EVENT current_event;
while (1)
{
if (!WaitForDebugEvent (&current_event, 20000))
return 0;
deb_printf ("got debug event %d\n", current_event.dwDebugEventCode);
switch (current_event.dwDebugEventCode)
{
case CREATE_THREAD_DEBUG_EVENT:
if (!add_thread (current_event.dwThreadId,
current_event.u.CreateThread.hThread))
goto failed;
break;
case CREATE_PROCESS_DEBUG_EVENT:
if (!add_module (current_event.u.CreateProcessInfo.lpBaseOfImage) ||
!add_thread (current_event.dwThreadId,
current_event.u.CreateProcessInfo.hThread))
goto failed;
break;
case EXIT_PROCESS_DEBUG_EVENT:
deb_printf ("debugee quits");
ContinueDebugEvent (current_event.dwProcessId,
current_event.dwThreadId,
DBG_CONTINUE);
return 1;
break;
case LOAD_DLL_DEBUG_EVENT:
if (!add_module (current_event.u.LoadDll.lpBaseOfDll))
goto failed;
break;
case EXCEPTION_DEBUG_EVENT:
exception_level++;
if (exception_level == 2)
break;
else if (exception_level > 2)
return 0;
collect_memory_sections ();
/* got all info. time to dump */
if (!prepare_core_dump ())
{
fprintf (stderr, "Failed to prepare core dump\n");
goto failed;
};
if (!write_core_dump ())
{
fprintf (stderr, "Failed to write core dump\n");
goto failed;
};
/* signal a debugee that we've finished */
if (sync_with_debugee)
SetEvent (sync_with_debugee);
break;
default:
break;
}
ContinueDebugEvent (current_event.dwProcessId,
current_event.dwThreadId,
DBG_CONTINUE);
}
failed:
/* set debugee free */
if (sync_with_debugee)
SetEvent (sync_with_debugee);
return 0;
}
int
dumper::init_core_dump ()
{
bfd_init ();
core_bfd = bfd_openw (file_name, "elf32-i386");
if (core_bfd == NULL)
{
bfd_perror ("opening bfd");
goto failed;
}
if (!bfd_set_format (core_bfd, bfd_core))
{
bfd_perror ("setting bfd format");
goto failed;
}
if (!bfd_set_arch_mach (core_bfd, bfd_arch_i386, 0))
{
bfd_perror ("setting bfd architecture");
goto failed;
}
return 1;
failed:
dumper_abort ();
return 0;
}
int
dumper::prepare_core_dump ()
{
if (!sane ())
return 0;
int sect_no = 0;
char sect_name[50];
flagword sect_flags;
SIZE_T sect_size;
bfd_vma sect_vma;
asection *new_section;
for (process_entity * p = list; p != NULL; p = p->next)
{
sect_no++;
unsigned long phdr_type = PT_LOAD;
switch (p->type)
{
case pr_ent_memory:
sprintf (sect_name, ".mem/%u", sect_no);
sect_flags = SEC_HAS_CONTENTS | SEC_ALLOC | SEC_LOAD;
sect_size = p->u.memory.size;
sect_vma = (bfd_vma) (p->u.memory.base);
phdr_type = PT_LOAD;
break;
case pr_ent_thread:
sprintf (sect_name, ".note/%u", sect_no);
sect_flags = SEC_HAS_CONTENTS | SEC_LOAD;
sect_size = sizeof (note_header) + sizeof (struct win32_pstatus);
sect_vma = 0;
phdr_type = PT_NOTE;
break;
case pr_ent_module:
sprintf (sect_name, ".note/%u", sect_no);
sect_flags = SEC_HAS_CONTENTS | SEC_LOAD;
sect_size = sizeof (note_header) + sizeof (struct win32_pstatus) +
(bfd_size_type) (strlen (p->u.module.name));
sect_vma = 0;
phdr_type = PT_NOTE;
break;
default:
continue;
}
if (p->type == pr_ent_module && status_section != NULL)
{
if (!set_section_size (core_bfd,
status_section,
(get_section_size (status_section)
+ sect_size)))
{
bfd_perror ("resizing status section");
goto failed;
};
continue;
}
deb_printf ("creating section (type%u) %s(%u), flags=%08x\n",
p->type, sect_name, sect_size, sect_flags);
bfd_set_error (bfd_error_no_error);
char *buf = strdup (sect_name);
new_section = bfd_make_section (core_bfd, buf);
if (new_section == NULL)
{
if (bfd_get_error () == bfd_error_no_error)
fprintf (stderr, "error creating new section (%s), section already exists.\n", buf);
else
bfd_perror ("creating section");
goto failed;
}
if (!set_section_flags (core_bfd, new_section, sect_flags) ||
!set_section_size (core_bfd, new_section, sect_size))
{
bfd_perror ("setting section attributes");
goto failed;
};
new_section->vma = sect_vma;
new_section->lma = 0;
new_section->output_section = new_section;
new_section->output_offset = 0;
p->section = new_section;
int section_count = 1;
bfd_boolean filehdr = 0;
bfd_boolean phdrs = 0;
bfd_vma at = 0;
bfd_boolean valid_at = 0;
flagword flags = 0;
bfd_boolean valid_flags = 1;
if (p->type == pr_ent_memory)
{
MEMORY_BASIC_INFORMATION mbi;
if (!VirtualQueryEx (hProcess, (LPVOID)sect_vma, &mbi, sizeof (mbi)))
{
bfd_perror ("getting mem region flags");
goto failed;
}
static const struct
{
DWORD protect;
flagword flags;
} mappings[] =
{
{ PAGE_READONLY, PF_R },
{ PAGE_READWRITE, PF_R | PF_W },
{ PAGE_WRITECOPY, PF_W },
{ PAGE_EXECUTE, PF_X },
{ PAGE_EXECUTE_READ, PF_X | PF_R },
{ PAGE_EXECUTE_READWRITE, PF_X | PF_R | PF_W },
{ PAGE_EXECUTE_WRITECOPY, PF_X | PF_W }
};
for (size_t i = 0;
i < sizeof (mappings) / sizeof (mappings[0]);
i++)
if ((mbi.Protect & mappings[i].protect) != 0)
flags |= mappings[i].flags;
}
if (!bfd_record_phdr (core_bfd, phdr_type,
valid_flags, flags,
valid_at, at,
filehdr, phdrs,
section_count, &new_section))
{
bfd_perror ("recording program headers");
goto failed;
}
}
return 1;
failed:
dumper_abort ();
return 0;
}
int
dumper::write_core_dump ()
{
if (!sane ())
return 0;
for (process_entity * p = list; p != NULL; p = p->next)
{
if (p->section == NULL)
continue;
deb_printf ("writing section type=%u base=%p size=%p flags=%08x\n",
p->type,
p->section->vma,
get_section_size (p->section),
p->section->flags);
switch (p->type)
{
case pr_ent_memory:
dump_memory_region (p->section, &(p->u.memory));
break;
case pr_ent_thread:
dump_thread (p->section, &(p->u.thread));
break;
case pr_ent_module:
dump_module (p->section, &(p->u.module));
break;
default:
continue;
}
}
return 1;
}
static void
usage (FILE *stream, int status)
{
fprintf (stream, "\
Usage: %s [OPTION] FILENAME WIN32PID\n\
\n\
Dump core from WIN32PID to FILENAME.core\n\
\n\
-d, --verbose be verbose while dumping\n\
-h, --help output help information and exit\n\
-q, --quiet be quiet while dumping (default)\n\
-V, --version output version information and exit\n\
\n", program_invocation_short_name);
exit (status);
}
struct option longopts[] = {
{"verbose", no_argument, NULL, 'd'},
{"help", no_argument, NULL, 'h'},
{"quiet", no_argument, NULL, 'q'},
{"version", no_argument, 0, 'V'},
{0, no_argument, NULL, 0}
};
const char *opts = "dhqV";
static void
print_version ()
{
printf ("dumper (cygwin) %d.%d.%d\n"
"Core Dumper for Cygwin\n"
"Copyright (C) 1999 - %s Cygwin Authors\n"
"This is free software; see the source for copying conditions. There is NO\n"
"warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n",
CYGWIN_VERSION_DLL_MAJOR / 1000,
CYGWIN_VERSION_DLL_MAJOR % 1000,
CYGWIN_VERSION_DLL_MINOR,
strrchr (__DATE__, ' ') + 1);
}
int
main (int argc, char **argv)
{
int opt;
const char *p = "";
DWORD pid;
while ((opt = getopt_long (argc, argv, opts, longopts, NULL) ) != EOF)
switch (opt)
{
case 'd':
verbose = TRUE;
break;
case 'q':
verbose = FALSE;
break;
case 'h':
usage (stdout, 0);
case 'V':
print_version ();
exit (0);
default:
fprintf (stderr, "Try `%s --help' for more information.\n",
program_invocation_short_name);
exit (1);
}
if (argv && *(argv + optind) && *(argv + optind +1))
{
ssize_t len = cygwin_conv_path (CCP_POSIX_TO_WIN_A | CCP_RELATIVE,
*(argv + optind), NULL, 0);
char *win32_name = (char *) alloca (len);
cygwin_conv_path (CCP_POSIX_TO_WIN_A | CCP_RELATIVE, *(argv + optind),
win32_name, len);
if ((p = strrchr (win32_name, '\\')))
p++;
else
p = win32_name;
pid = strtoul (*(argv + optind + 1), NULL, 10);
}
else
{
usage (stderr, 1);
return -1;
}
char *core_file = (char *) malloc (strlen (p) + sizeof (".core"));
if (!core_file)
{
fprintf (stderr, "error allocating memory\n");
return -1;
}
sprintf (core_file, "%s.core", p);
DWORD tid = 0;
if (verbose)
printf ("dumping process #%u to %s\n", (unsigned int) pid, core_file);
dumper d (pid, tid, core_file);
if (!d.sane ())
return -1;
d.collect_process_information ();
free (core_file);
return 0;
};