diff options
Diffstat (limited to 'winsup/utils/dumper.cc')
-rw-r--r-- | winsup/utils/dumper.cc | 754 |
1 files changed, 754 insertions, 0 deletions
diff --git a/winsup/utils/dumper.cc b/winsup/utils/dumper.cc new file mode 100644 index 000000000..6ccf2f1c9 --- /dev/null +++ b/winsup/utils/dumper.cc @@ -0,0 +1,754 @@ +/* dumper.cc + + Copyright 1999 Cygnus Solutions. + + Written by Egor Duda <deo@logos-m.ru> + +This file is part of Cygwin. + +This software is a copyrighted work licensed under the terms of the +Cygwin license. Please consult the file "CYGWIN_LICENSE" for +details. */ + +#include <bfd.h> +#include <elf/common.h> +#include <elf/external.h> +#include <sys/procfs.h> +#include <sys/cygwin.h> +#include <getopt.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <windows.h> + +#include "dumper.h" + +#define NOTE_NAME_SIZE 16 + +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 #%lu\n", pid ); + 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; +} + +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 ) ) return 0; + + deb_printf ( "added thread %u\n", tid ); + return 1; +} + +int +dumper::add_mem_region ( LPBYTE base, DWORD 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 %08x-%08x\n", (DWORD)base, (DWORD)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, DWORD 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, (DWORD) 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 %08x %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; + DWORD last_size = 0; + DWORD 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 ) ) + { + 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 %08x-%08x. protect = %s\n", + (DWORD)current_page_address, + (DWORD)current_page_address + mbi.RegionSize, + buf ); + 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; + + DWORD size = memory->size; + DWORD todo; + DWORD 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 ( "Error reading process memory at %x(%x) %u\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 ); + strncpy ( (char*) & header.elf_note_header.name, "win32thread", NOTE_NAME_SIZE ); + + 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 ); + strncpy ( (char*) & header.elf_note_header.name, "win32module", NOTE_NAME_SIZE ); + + 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 () +{ + if ( ! sane () ) return 0; + + if ( ! DebugActiveProcess ( pid ) ) + { + fprintf ( stderr, "Cannot attach to process #%lu", pid ); + return 0; + } + + char event_name [ sizeof ( "cygwin_error_start_event" ) + 20 ]; + sprintf ( event_name, "cygwin_error_start_event%16lx", pid ); + HANDLE sync_with_debugee = OpenEvent ( EVENT_MODIFY_STATE, FALSE, event_name ); + + DEBUG_EVENT current_event; + + while (1) + { + if ( ! WaitForDebugEvent ( ¤t_event, 20000 ) ) return 0; + + 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: + + 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; + } + + 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; + DWORD sect_size; + bfd_vma sect_vma; + + asection* new_section; + + for ( process_entity* p = list; p != NULL; p = p->next ) + { + sect_no++; + + 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); + + 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; + 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; + break; + + default: + continue; + } + + if ( p->type == pr_ent_module && status_section != NULL ) + { + if ( ! bfd_set_section_size ( core_bfd, + status_section, + status_section->_raw_size + 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 ); + + char* buf = strdup ( sect_name ); + new_section = bfd_make_section ( core_bfd, buf ); + + if ( new_section == NULL || + ! bfd_set_section_flags ( core_bfd, new_section, sect_flags ) || + ! bfd_set_section_size ( core_bfd, new_section, sect_size ) ) + { + bfd_perror ( "creating section" ); + goto failed; + }; + + new_section->vma = sect_vma; + new_section->output_section = new_section; + new_section->output_offset = 0; + p->section = new_section; + } + + 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=%08x size=%08x flags=%08x\n", + p->type, + p->section->vma, + p->section->_raw_size, + 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 () +{ + fprintf ( stderr, "Usage: dumper [-v] [-c filename] pid\n" ); + fprintf ( stderr, "-c filename -- dump core to filename.core\n" ); + fprintf ( stderr, "-d -- print some debugging info while dumping\n" ); + fprintf ( stderr, "pid -- win32-pid of process to dump\n" ); +} + +int +main( int argc, char** argv ) +{ + int opt; + char* p = ""; + DWORD pid; + + while ((opt = getopt (argc, argv, "dc:")) != EOF) + switch (opt) + { + case 'd': + verbose = TRUE; + break; + case 'c': + char win32_name [ MAX_PATH ]; + cygwin_conv_to_win32_path ( optarg, win32_name ); + if ( ( p = strrchr ( win32_name, '\\' ) ) ) + p++; + else + p = win32_name; + break; + } + + 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 ); + + if ( argv && *(argv+optind) ) + pid = atoi ( *(argv+optind) ); + else + { + usage (); + return -1; + } + + DWORD tid = 0; + + if ( verbose ) + printf ( "dumping process #%lu to %s\n", pid, core_file ); + + dumper d ( pid, tid, core_file ); + if ( ! d.sane () ) + return -1; + d.collect_process_information (); + free ( core_file ); + + return 0; +}; |