diff options
Diffstat (limited to 'exec/elfcore.c')
-rw-r--r-- | exec/elfcore.c | 630 |
1 files changed, 548 insertions, 82 deletions
diff --git a/exec/elfcore.c b/exec/elfcore.c index 4388a135..f953cc76 100644 --- a/exec/elfcore.c +++ b/exec/elfcore.c @@ -1,100 +1,566 @@ +/* Write ELF core dump files for GNU Hurd. + Copyright (C) 2002, 2004, 2008 Free Software Foundation, Inc. + Written by Roland McGrath. + +This file is part of the GNU Hurd. + +The GNU Hurd 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 2, or (at your option) +any later version. + +The GNU Hurd 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 for more details. + +You should have received a copy of the GNU General Public License +along with the GNU Hurd; see the file COPYING. If not, write to +the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */ + +#include <hurd.h> +#include <elf.h> +#include <link.h> +#include <string.h> +#include <argz.h> +#include <sys/mman.h> +#include <sys/utsname.h> +#include <sys/procfs.h> +#include <stddef.h> + +#define ELF_CLASS PASTE (ELFCLASS, __ELF_NATIVE_CLASS) +#define PASTE(a, b) PASTE_1 (a, b) +#define PASTE_1(a, b) a##b + +#include <endian.h> +#if BYTE_ORDER == BIG_ENDIAN +#define ELF_DATA ELFDATA2MSB +#elif BYTE_ORDER == LITTLE_ENDIAN +#define ELF_DATA ELFDATA2LSB +#endif + +#include <mach/thread_status.h> +#include <assert.h> + +#ifdef i386_THREAD_STATE +# define ELF_MACHINE EM_386 + +/* The gregset_t format (compatible with Linux/x86) almost fits + the Mach i386_thread_state. */ +static inline void +fetch_thread_regset (thread_t thread, prgregset_t *gregs) +{ + union + { + struct i386_thread_state state; + prgregset_t gregs; + } *u = (void *) gregs; + mach_msg_type_number_t count = i386_THREAD_STATE_COUNT; + assert (sizeof (struct i386_thread_state) < sizeof (prgregset_t)); + assert (offsetof (struct i386_thread_state, gs) == REG_GS * 4); + assert (offsetof (struct i386_thread_state, eax) == REG_EAX * 4); + + (void) thread_get_state (thread, i386_THREAD_STATE, + (thread_state_t) &u->state, &count); + + u->gregs[REG_EIP] = u->state.eip; + u->gregs[REG_CS] = u->state.cs; + u->gregs[REG_EFL] = u->state.efl; + u->gregs[REG_UESP] = u->state.uesp; + u->gregs[REG_SS] = u->state.ss; + + /* These are the extra words that don't exist in prgregset_t. */ + u->gregs[REG_ERR] = u->gregs[REG_TRAPNO] = 0; +} + +static inline void +fetch_thread_fpregset (thread_t thread, prfpregset_t *fpregs) +{ + struct i386_float_state st; + mach_msg_type_number_t count = i386_FLOAT_STATE_COUNT; + error_t err = thread_get_state (thread, i386_FLOAT_STATE, + (thread_state_t) &st, &count); + if (err == 0 && st.initialized) + { + assert (sizeof *fpregs >= sizeof st.hw_state); + memcpy (fpregs, st.hw_state, sizeof st.hw_state); + } +} + +#elif defined ALPHA_THREAD_STATE +# define ELF_MACHINE EM_ALPHA + +/* The gregset_t format (compatible with Linux/Alpha) almost fits + the Mach alpha_thread_state. */ +static inline void +fetch_thread_regset (thread_t thread, prgregset_t *gregs) { - processor_set_name_t pset; - host_t host; - processor_set_basic_info_data_t pinfo; + mach_msg_type_number_t count = ALPHA_THREAD_STATE_COUNT; + assert (sizeof (struct alpha_thread_state) <= sizeof (prgregset_t)); + (void) thread_get_state (thread, ALPHA_THREAD_STATE, + (thread_state_t) gregs, &count); + /* XXX + gregs[32] is process-status word: Mach doesn't return it! + It's already zero'd. + */ +} +/* The FPU state matches exactly. */ +static inline void +fetch_thread_fpregset (thread_t thread, prfpregset_t *fpregs) +{ + mach_msg_type_number_t count = ALPHA_FLOAT_STATE_COUNT; + assert (sizeof (struct alpha_float_state) == sizeof *fpregs); + (void) thread_get_state (thread, ALPHA_FLOAT_STATE, + (thread_state_t) fpregs, &count); +} + +#else +# warning "do not understand this machine flavor, no registers in dumps" +# define ELF_MACHINE EM_NONE +#endif + + +#define TIME_VALUE_TO_TIMESPEC(tv, ts) { \ + (ts)->tv_sec = (tv)->seconds; \ + (ts)->tv_nsec = (tv)->microseconds * 1000; \ +} + +#define PAGES_TO_KB(x) ((x) * (vm_page_size / 1024)) +#define ENCODE_PCT(f) ((uint16_t) ((f) * 32768.0)) + +extern process_t procserver; /* crash.c */ + +error_t +dump_core (task_t task, file_t file, off_t corelimit, + int signo, long int sigcode, int sigerror) +{ + static float host_memory_size = -1.0; + error_t err; + ElfW(Phdr) *phdrs, *ph; + ElfW(Ehdr) hdr = /* ELF header for the core file. */ + { + e_ident: + { + [EI_MAG0] = ELFMAG0, + [EI_MAG1] = ELFMAG1, + [EI_MAG2] = ELFMAG2, + [EI_MAG3] = ELFMAG3, + [EI_CLASS] = ELF_CLASS, + [EI_DATA] = ELF_DATA, + [EI_VERSION] = EV_CURRENT, + [EI_OSABI] = ELFOSABI_SYSV, + [EI_ABIVERSION] = 0 + }, + e_type: ET_CORE, + e_version: EV_CURRENT, + e_machine: ELF_MACHINE, + e_ehsize: sizeof hdr, + e_phentsize: sizeof phdrs[0], + e_phoff: sizeof hdr, /* Fill in e_phnum later. */ + }; + off_t offset; + size_t wrote; + + pid_t pid; thread_t *threads; - size_t nthreads; - - vm_address_t addr; - vm_size_t size; - vm_prot_t prot, maxprot; - vm_inherit_t inherit; - boolean_t shared; - memory_object_name_t objname; - vm_offset_t offset; - - /* Figure out what flavor of machine the task is on. */ - if (err = task_get_assignment (task, &pset)) - goto lose; - err = processor_set_info (pset, PROCESSOR_SET_BASIC_INFO, &host, - &pinfo, PROCESSOR_SET_BASIC_INFO_COUNT); - mach_port_deallocate (mach_task_self (), pset); - if (err) - goto lose; - err = bfd_mach_host_arch_mach (host, &arch, &machine, &e_machine); - mach_port_deallocate (mach_task_self (), host); + size_t nthreads, i; + off_t notestart; + + /* Helper macros for writing notes. */ +#define DEFINE_NOTE(typename) struct { struct note_header hdr; typename data; } +#define WRITE_NOTE(type, var) ({ \ + (var).hdr = NOTE_HEADER ((type), sizeof (var).data); \ + write_note (&(var).hdr); \ +}) + struct note_header + { + ElfW(Nhdr) note; + char name[(sizeof "CORE" + 3) &~ 3]; + } __attribute__ ((packed)); +#define NOTE_HEADER(type, size) \ + ((struct note_header) { { sizeof "CORE", (size), (type) }, "CORE" }) + inline error_t write_note (struct note_header *hdr) + { + error_t err = 0; + char *data = (char *) hdr; + size_t size = sizeof *hdr + hdr->note.n_descsz; + if (corelimit >= 0 && offset + size > corelimit) + size = corelimit - offset; + while (size > 0) + { + err = io_write (file, data, size, offset, &wrote); + if (err) + return err; + if (wrote > size) + return EGRATUITOUS; + data += wrote; + size -= wrote; + } + offset = (offset + wrote + 3) &~ 3; /* Pad it to word alignment. */ + return 0; + } + + struct vm_region_list + { + struct vm_region_list *next; + vm_prot_t protection; + vm_address_t start; + vm_size_t length; + }; + struct vm_region_list *regions = NULL, **tailp = ®ions, *r; + unsigned int nregions = 0; + + if (corelimit >= 0 && corelimit < sizeof hdr) + return EFBIG; + + { + /* Examine the task and record the locations of contiguous memory + segments that we will dump into the core file. */ + + vm_address_t region_address, last_region_address, last_region_end; + vm_prot_t last_protection; +#define RECORD_LAST_REGION do { \ + if (last_region_end > last_region_address \ + && last_protection != VM_PROT_NONE) \ + record_last_region (alloca (sizeof (struct vm_region_list))); } while (0) + inline void record_last_region (struct vm_region_list *region) + { + *tailp = region; + tailp = ®ion->next; + region->next = NULL; + region->start = last_region_address; + region->length = last_region_end - last_region_address; + region->protection = last_protection; + ++nregions; + } + + region_address = last_region_address = last_region_end = VM_MIN_ADDRESS; + last_protection = VM_PROT_NONE; + while (region_address < VM_MAX_ADDRESS) + { + vm_prot_t protection; + vm_prot_t max_protection; + vm_inherit_t inheritance; + boolean_t shared; + mach_port_t object_name; + vm_offset_t offset; + vm_size_t region_length = VM_MAX_ADDRESS - region_address; + + err = vm_region (task, + ®ion_address, + ®ion_length, + &protection, + &max_protection, + &inheritance, + &shared, + &object_name, + &offset); + if (err == KERN_NO_SPACE) + break; + if (err != KERN_SUCCESS) + return err; + + if (protection == last_protection && region_address == last_region_end) + /* This region is contiguous with and indistinguishable from + the previous one, so we just extend that one. */ + last_region_end = region_address += region_length; + else + { + /* This region is distinct from the last one we saw, + so record that previous one. */ + RECORD_LAST_REGION; + last_region_address = region_address; + last_region_end = region_address += region_length; + last_protection = protection; + } + } + /* Record the final region. */ + RECORD_LAST_REGION; + } + + /* Now we start laying out the file. */ + offset = sizeof hdr + ((nregions + 1) * sizeof *phdrs); + + /* Final check for tiny core limit. From now on, we will simply truncate + the file at CORELIMIT but not change the contents of what we write. */ + if (corelimit >= 0 && corelimit < offset) + return EFBIG; + + /* Now we can complete the file header and write it. */ + hdr.e_phnum = nregions + 1; + err = io_write (file, (char *) &hdr, sizeof hdr, 0, &wrote); if (err) - goto lose; + return err; + if (wrote < sizeof hdr) + return EGRATUITOUS; /* XXX */ + + /* Now we can write the various notes. */ + notestart = offset; - if (err = task_threads (task, &threads, &nthreads)) - goto lose; + /* First a dull note containing the results of `uname', a la Solaris. */ + { + DEFINE_NOTE (struct utsname) note; + if (uname (¬e.data) == 0) /* XXX Use proc_uname on task's proc port? */ + err = WRITE_NOTE (NT_UTSNAME, note); + } + if (err || (corelimit >= 0 && corelimit <= offset)) + return err; - /* Create a BFD section to describe each contiguous chunk - of the task's address space with the same stats. */ - sec = NULL; - addr = 0; - while (!vm_region (task, &addr, &size, &prot, &maxprot, - &inherit, &shared, &objname, &offset)) + err = proc_task2pid (procserver, task, &pid); + if (err) + return err; + + /* Make sure we have the total RAM size of the host. + We only do this once, assuming that it won't change. + XXX this could use the task's host-self port instead. */ + if (host_memory_size <= 0.0) { - mach_port_deallocate (mach_task_self (), objname); + host_basic_info_data_t hostinfo; + mach_msg_type_number_t size = sizeof hostinfo; + error_t err = host_info (mach_host_self (), HOST_BASIC_INFO, + (host_info_t) &hostinfo, &size); + if (err == 0) + host_memory_size = hostinfo.memory_size; + } + + /* The psinfo_t note contains some process-global info we should get from + the proc server, but no thread-specific info like register state. */ + { + DEFINE_NOTE (psinfo_t) psinfo; + DEFINE_NOTE (pstatus_t) pstatus; + int flags = PI_FETCH_TASKINFO | PI_FETCH_THREADS | PI_FETCH_THREAD_BASIC; + char *waits = 0; + mach_msg_type_number_t num_waits = 0; + char pibuf[offsetof (struct procinfo, threadinfos[2])]; + struct procinfo *pi = (void *) &pibuf; + mach_msg_type_number_t pi_size = sizeof pibuf; + + memset (&pstatus.data, 0, sizeof pstatus.data); + memset (&psinfo.data, 0, sizeof psinfo.data); + pstatus.data.pr_pid = psinfo.data.pr_pid = pid; + + err = proc_getprocinfo (procserver, pid, &flags, + (procinfo_t *) &pi, &pi_size, + &waits, &num_waits); + if (err == 0) + { + if (num_waits != 0) + munmap (waits, num_waits); + + pstatus.data.pr_flags = psinfo.data.pr_flag = pi->state; + pstatus.data.pr_nlwp = psinfo.data.pr_nlwp = pi->nthreads; + pstatus.data.pr_ppid = psinfo.data.pr_ppid = pi->ppid; + pstatus.data.pr_pgid = psinfo.data.pr_pgid = pi->pgrp; + pstatus.data.pr_sid = psinfo.data.pr_sid = pi->session; + + psinfo.data.pr_euid = pi->owner; + /* XXX struct procinfo should have these */ + psinfo.data.pr_egid = psinfo.data.pr_gid = psinfo.data.pr_uid = -1; + + psinfo.data.pr_size = PAGES_TO_KB (pi->taskinfo.virtual_size); + psinfo.data.pr_rssize = PAGES_TO_KB (pi->taskinfo.resident_size); + + { + /* Sum all the threads' cpu_usage fields. */ + integer_t cpu_usage = 0; + for (i = 0; i < pi->nthreads; ++i) + cpu_usage += pi->threadinfos[i].pis_bi.cpu_usage; + psinfo.data.pr_pctcpu = ENCODE_PCT ((float) cpu_usage + / (float) TH_USAGE_SCALE); + } + if (host_memory_size > 0.0) + psinfo.data.pr_pctmem + = ENCODE_PCT + ((float) pi->taskinfo.resident_size / host_memory_size); + + TIME_VALUE_TO_TIMESPEC (&pi->taskinfo.creation_time, + &psinfo.data.pr_start); - if (prot != VM_PROT_NONE) + TIME_VALUE_TO_TIMESPEC (&pi->taskinfo.user_time, + &pstatus.data.pr_utime); + TIME_VALUE_TO_TIMESPEC (&pi->taskinfo.system_time, + &pstatus.data.pr_stime); + /* Sum the user and system time for pr_time. */ + pi->taskinfo.user_time.seconds += pi->taskinfo.system_time.seconds; + pi->taskinfo.user_time.microseconds += pi->taskinfo.system_time.microseconds; + if (pi->taskinfo.user_time.microseconds >= 1000000) + { + ++pi->taskinfo.user_time.seconds; + pi->taskinfo.user_time.microseconds -= 1000000; + } + TIME_VALUE_TO_TIMESPEC (&pi->taskinfo.user_time, &psinfo.data.pr_time); + /* XXX struct procinfo should have dead child info for pr_c[us]?time */ + + psinfo.data.pr_wstat = pi->exitstatus; + + if ((void *) pi != &pibuf) + munmap (pi, pi_size); + } + if (err == 0) + { + /* We have to nab the process's own proc port to get the + proc server to tell us its registered arg locations. */ + process_t proc; + err = proc_task2proc (procserver, task, &proc); + if (err == 0) + { + err = proc_get_arg_locations (proc, + &psinfo.data.pr_argv, + &psinfo.data.pr_envp); + mach_port_deallocate (mach_task_self (), proc); + } { - flagword flags = SEC_NO_FLAGS; - - if (!(prot & VM_PROT_WRITE)) - flags |= SEC_READONLY; - if (!(prot & VM_PROT_EXECUTE)) - flags |= SEC_DATA; - - if (sec != NULL && - (vm_address_t) (bfd_section_vma (bfd, sec) + - bfd_section_size (bfd, sec)) == addr && - flags == (bfd_get_section_flags (bfd, sec) & - (SEC_READONLY|SEC_DATA))) - /* Coalesce with the previous section. */ - bfd_set_section_size (bfd, sec, - bfd_section_size (bfd, sec) + size); - else + /* Now fetch the arguments. We could do this directly from the + task given the locations we now have. But we are lazy and have + the proc server do it for us. */ + char *data = psinfo.data.pr_psargs; + size_t datalen = sizeof psinfo.data.pr_psargs; + err = proc_getprocargs (procserver, pid, &data, &datalen); + if (err == 0) { - /* Make a new section (which might grow by - the next region being coalesced onto it). */ - char *name = bfd_intuit_section_name (addr, size, &flags); - if (name == NULL) + psinfo.data.pr_argc = argz_count (data, datalen); + argz_stringify (data, datalen, ' '); + if (data != psinfo.data.pr_psargs) { - /* No guess from BFD. */ - if (asprintf (&name, "[%p,%p) %c%c%c", - (void *) addr, (void *) (addr + size), - (prot & VM_PROT_READ) ? 'r' : '-', - (prot & VM_PROT_WRITE) ? 'w' : '-', - (prot & VM_PROT_EXECUTE) ? 'x' : '-') == -1) - goto lose; + memcpy (psinfo.data.pr_psargs, data, + sizeof psinfo.data.pr_psargs); + munmap (data, datalen); } - sec = bfd_make_section (name); - bfd_set_section_flags (bfd, sec, flags); - bfd_set_section_vma (bfd, sec, addr); - bfd_set_section_size (bfd, sec, size); } } + err = WRITE_NOTE (NT_PSINFO, psinfo); + } + + err = WRITE_NOTE (NT_PSTATUS, pstatus) ?: err; + } + if (err || (corelimit >= 0 && corelimit <= offset)) + return err; + + /* Now examine all the threads in the task. + For each thread we produce one or more notes. */ + err = task_threads (task, &threads, &nthreads); + if (err) + return err; + for (i = 0; i < nthreads; ++i) + { + DEFINE_NOTE (lwpstatus_t) note; + memset (¬e.data, 0, sizeof note.data); + note.data.pr_lwpid = i; + + /* We have to write the death signal into every thread's record, even + though only one thread really took the signal. This is both because + we don't know which thread it was, and because GDB blindly uses the + value from each record to clobber the last (even if it's zero). */ + note.data.pr_cursig = signo; + note.data.pr_info.si_signo = signo; + note.data.pr_info.si_code = sigcode; + note.data.pr_info.si_errno = sigerror; + + fetch_thread_regset (threads[i], ¬e.data.pr_reg); + fetch_thread_fpregset (threads[i], ¬e.data.pr_fpreg); + + err = WRITE_NOTE (NT_LWPSTATUS, note); + if (err || (corelimit >= 0 && corelimit <= offset)) + break; + + mach_port_deallocate (mach_task_self (), threads[i]); } + /* If we broke out of the loop early, deallocate remaining thread ports. */ + while (i < nthreads) + mach_port_deallocate (mach_task_self (), threads[i++]); + munmap (threads, nthreads * sizeof *threads); + if (err || (corelimit >= 0 && corelimit <= offset)) + return err; - /* Write all the sections' data. */ - for (sec = bfd->sections; sec != NULL; sec = sec->next) + /* Make an array of program headers and fill them in. + The first one describes the note segment. */ + ph = phdrs = alloca ((nregions + 1) * sizeof *phdrs); + + memset (ph, 0, sizeof *ph); + ph->p_type = PT_NOTE; + ph->p_offset = notestart; + ph->p_filesz = offset - notestart; + ++ph; + + /* Now make ELF program headers for each of the recorded memory regions. + Consistent with the Linux kernel, we create PT_LOAD headers with + p_filesz = 0 for the read-only segments that we are not dumping + into the file. */ + for (r = regions; r != NULL; r = r->next) { - void *data; - err = vm_read (task, bfd_section_vma (bfd, sec), - bfd_section_size (bfd, sec), &data); - if (err) - /* XXX What to do? - 1. lose - 2. remove this section - 3. mark this section as having ungettable contents (how?) - */ - goto lose; - err = bfd_set_section_contents (bfd, sec, data, 0, - bfd_section_size (bfd, sec)); - vm_deallocate (mach_task_self (), data, bfd_section_size (bfd, sec)); - if (err) - goto bfdlose; + memset (ph, 0, sizeof *ph); + ph->p_type = PT_LOAD; + ph->p_align = vm_page_size; + ph->p_flags = (((r->protection & VM_PROT_READ) ? PF_R : 0) + | ((r->protection & VM_PROT_WRITE) ? PF_W : 0) + | ((r->protection & VM_PROT_EXECUTE) ? PF_X : 0)); + ph->p_vaddr = r->start; + ph->p_memsz = r->length; + ph->p_filesz = (r->protection & VM_PROT_WRITE) ? ph->p_memsz : 0; + ph->p_offset = round_page (offset); + offset += ph->p_filesz; + ++ph; } + + /* Now write the memory segment data. */ + for (ph = &phdrs[1]; ph < &phdrs[nregions + 1]; ++ph) + if (ph->p_filesz > 0) + { + vm_address_t va = ph->p_vaddr; + vm_size_t sz = ph->p_memsz; + off_t ofs = ph->p_offset; + int wrote_any = 0; + do + { + pointer_t copied; + size_t copy_count; + err = vm_read (task, va, sz, &copied, ©_count); + if (err == 0) + { + char *data = (void *) copied; + size_t left = copy_count, wrote; + + va += copy_count; + sz -= copy_count; + + do + { + if (corelimit >= 0 && ofs + left > corelimit) + left = corelimit - ofs; + err = io_write (file, data, left, ofs, &wrote); + if (err) + break; + ofs += wrote; + data += wrote; + left -= wrote; + if (ofs >= corelimit) + break; + } while (left > 0); + + munmap ((void *) copied, copy_count); + + if (left < copy_count) + wrote_any = 1; + } + else + { + /* Leave a hole in the file for pages we can't read. */ + va += vm_page_size; + sz -= vm_page_size; + ofs += vm_page_size; + } + } while (sz > 0 && (corelimit < 0 || ofs < corelimit)); + + if (! wrote_any) + /* If we failed to write any contents at all, + don't claim the big hole as the contents. */ + ph->p_filesz = 0; + } + + /* Finally, we go back and write the program headers. */ + err = io_write (file, (char *) phdrs, (nregions + 1) * sizeof phdrs[0], + sizeof hdr, &wrote); + + return err; +} |