/* $Id: coredumper-solaris.cpp $ */ /** @file * IPRT - Custom Core Dumper, Solaris. */ /* * Copyright (C) 2010-2023 Oracle and/or its affiliates. * * This file is part of VirtualBox base platform packages, as * available from https://www.virtualbox.org. * * 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, in version 3 of the * License. * * 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 for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . * * The contents of this file may alternatively be used under the terms * of the Common Development and Distribution License Version 1.0 * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included * in the VirtualBox distribution, in which case the provisions of the * CDDL are applicable instead of those of the GPL. * * You may elect to license modified versions of this file under the * terms and conditions of either the GPL or the CDDL or both. * * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 */ /********************************************************************************************************************************* * Header Files * *********************************************************************************************************************************/ #define LOG_GROUP RTLOGGROUP_DEFAULT #include #include #include #include #include #include #include #include #include #include #include "coredumper-solaris.h" #ifdef RT_OS_SOLARIS # include # include # include # include # include # include # include # include # include # include # include # include # include # include #endif /* RT_OS_SOLARIS */ #include #include /********************************************************************************************************************************* * Globals * *********************************************************************************************************************************/ static RTNATIVETHREAD volatile g_CoreDumpThread = NIL_RTNATIVETHREAD; static bool volatile g_fCoreDumpSignalSetup = false; static uint32_t volatile g_fCoreDumpFlags = 0; static char g_szCoreDumpDir[PATH_MAX] = { 0 }; static char g_szCoreDumpFile[PATH_MAX] = { 0 }; /********************************************************************************************************************************* * Defined Constants And Macros * *********************************************************************************************************************************/ #define CORELOG_NAME "CoreDumper: " #define CORELOG(a) Log(a) #define CORELOGRELSYS(a) \ do { \ rtCoreDumperSysLogWrapper a; \ } while (0) /** * ELFNOTEHDR: ELF NOTE header. */ typedef struct ELFNOTEHDR { Elf64_Nhdr Hdr; /* Header of NOTE section */ char achName[8]; /* Name of NOTE section */ } ELFNOTEHDR; typedef ELFNOTEHDR *PELFNOTEHDR; /** * Wrapper function to write IPRT format style string to the syslog. * * @param pszFormat Format string */ static void rtCoreDumperSysLogWrapper(const char *pszFormat, ...) { va_list va; va_start(va, pszFormat); char szBuf[1024]; RTStrPrintfV(szBuf, sizeof(szBuf), pszFormat, va); va_end(va); syslog(LOG_ERR, "%s", szBuf); } /** * Determines endianness of the system. Just for completeness. * * @return Will return false if system is little endian, true otherwise. */ static bool IsBigEndian() { const int i = 1; char *p = (char *)&i; if (p[0] == 1) return false; return true; } /** * Reads from a file making sure an interruption doesn't cause a failure. * * @param fd Handle to the file to read. * @param pv Where to store the read data. * @param cbToRead Size of data to read. * * @return IPRT status code. */ static int ReadFileNoIntr(int fd, void *pv, size_t cbToRead) { for (;;) { ssize_t cbRead = read(fd, pv, cbToRead); if (cbRead < 0) { if (errno == EINTR) continue; return RTErrConvertFromErrno(errno); } if ((size_t)cbRead == cbToRead) return VINF_SUCCESS; if ((size_t)cbRead > cbToRead) return VERR_INTERNAL_ERROR_3; if (cbRead == 0) return VERR_EOF; pv = (uint8_t *)pv + cbRead; cbToRead -= cbRead; } } /** * Writes to a file making sure an interruption doesn't cause a failure. * * @param fd Handle to the file to write to. * @param pv Pointer to what to write. * @param cbToWrite Size of data to write. * * @return IPRT status code. */ static int WriteFileNoIntr(int fd, const void *pv, size_t cbToWrite) { for (;;) { ssize_t cbWritten = write(fd, pv, cbToWrite); if (cbWritten < 0) { if (errno == EINTR) continue; return RTErrConvertFromErrno(errno); } if ((size_t)cbWritten == cbToWrite) return VINF_SUCCESS; if ((size_t)cbWritten > cbToWrite) return VERR_INTERNAL_ERROR_2; pv = (uint8_t const *)pv + cbWritten; cbToWrite -= cbWritten; } } /** * Read from a given offset in the process' address space. * * @param pSolProc Pointer to the solaris process. * @param off The offset to read from. * @param pvBuf Where to read the data into. * @param cbToRead Number of bytes to read. * * @return VINF_SUCCESS, if all the given bytes was read in, otherwise VERR_READ_ERROR. */ static ssize_t ProcReadAddrSpace(PRTSOLCOREPROCESS pSolProc, RTFOFF off, void *pvBuf, size_t cbToRead) { for (;;) { ssize_t cbRead = pread(pSolProc->fdAs, pvBuf, cbToRead, off); if (cbRead < 0) { if (errno == EINTR) continue; return RTErrConvertFromErrno(errno); } if ((size_t)cbRead == cbToRead) return VINF_SUCCESS; if ((size_t)cbRead > cbToRead) return VERR_INTERNAL_ERROR_4; if (cbRead == 0) return VERR_EOF; pvBuf = (uint8_t *)pvBuf + cbRead; cbToRead -= cbRead; off += cbRead; } } /** * Determines if the current process' architecture is suitable for dumping core. * * @param pSolProc Pointer to the solaris process. * * @return true if the architecture matches the current one. */ static inline bool IsProcessArchNative(PRTSOLCOREPROCESS pSolProc) { psinfo_t *pProcInfo = (psinfo_t *)pSolProc->pvProcInfo; return pProcInfo->pr_dmodel == PR_MODEL_NATIVE; } /** * Helper function to get the size_t compatible file size from a file * descriptor. * * @return The file size (in bytes). * @param fd The file descriptor. */ static size_t GetFileSizeByFd(int fd) { struct stat st; if (fstat(fd, &st) == 0) { if (st.st_size <= 0) return 0; size_t cbFile = (size_t)st.st_size; return (off_t)cbFile == st.st_size ? cbFile : ~(size_t)0; } CORELOGRELSYS((CORELOG_NAME "GetFileSizeByFd: fstat failed rc=%Rrc\n", RTErrConvertFromErrno(errno))); return 0; } /** * Helper function to get the size_t compatible size of a file given its path. * * @return The file size (in bytes). * @param pszPath Pointer to the full path of the file. */ static size_t GetFileSizeByName(const char *pszPath) { int fd = open(pszPath, O_RDONLY); if (fd < 0) { CORELOGRELSYS((CORELOG_NAME "GetFileSizeByName: failed to open %s rc=%Rrc\n", pszPath, RTErrConvertFromErrno(errno))); return 0; } size_t cb = GetFileSizeByFd(fd); close(fd); return cb; } /** * Pre-compute and pre-allocate sufficient memory for dumping core. * This is meant to be called once, as a single-large anonymously * mapped memory area which will be used during the core dumping routines. * * @param pSolCore Pointer to the core object. * * @return IPRT status code. */ static int AllocMemoryArea(PRTSOLCORE pSolCore) { AssertReturn(pSolCore->pvCore == NULL, VERR_ALREADY_EXISTS); static struct { const char *pszFilePath; /* Proc based path */ size_t cbHeader; /* Size of header */ size_t cbEntry; /* Size of each entry in file */ size_t cbAccounting; /* Size of each accounting entry per entry */ } const s_aPreAllocTable[] = { { "/proc/%d/psinfo", 0, 0, 0 }, { "/proc/%d/map", 0, sizeof(prmap_t), sizeof(RTSOLCOREMAPINFO) }, { "/proc/%d/auxv", 0, 0, 0 }, { "/proc/%d/lpsinfo", sizeof(prheader_t), sizeof(lwpsinfo_t), sizeof(RTSOLCORETHREADINFO) }, { "/proc/%d/lstatus", 0, 0, 0 }, { "/proc/%d/ldt", 0, 0, 0 }, { "/proc/%d/cred", sizeof(prcred_t), sizeof(gid_t), 0 }, { "/proc/%d/priv", sizeof(prpriv_t), sizeof(priv_chunk_t), 0 }, }; size_t cb = 0; for (unsigned i = 0; i < RT_ELEMENTS(s_aPreAllocTable); i++) { char szPath[PATH_MAX]; RTStrPrintf(szPath, sizeof(szPath), s_aPreAllocTable[i].pszFilePath, (int)pSolCore->SolProc.Process); size_t cbFile = GetFileSizeByName(szPath); cb += cbFile; if ( cbFile > 0 && s_aPreAllocTable[i].cbEntry > 0) { cb += ((cbFile - s_aPreAllocTable[i].cbHeader) / s_aPreAllocTable[i].cbEntry) * (s_aPreAllocTable[i].cbAccounting > 0 ? s_aPreAllocTable[i].cbAccounting : 1); cb += s_aPreAllocTable[i].cbHeader; } } /* * Make room for our own mapping accountant entry which will also be included in the core. */ cb += sizeof(RTSOLCOREMAPINFO); /* * Allocate the required space, plus some extra room. */ cb += _128K; void *pv = mmap(NULL, cb, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1 /* fd */, 0 /* offset */); if (pv != MAP_FAILED) { CORELOG((CORELOG_NAME "AllocMemoryArea: memory area of %u bytes allocated.\n", cb)); pSolCore->pvCore = pv; pSolCore->pvFree = pv; pSolCore->cbCore = cb; return VINF_SUCCESS; } CORELOGRELSYS((CORELOG_NAME "AllocMemoryArea: failed cb=%u\n", cb)); return VERR_NO_MEMORY; } /** * Free memory area used by the core object. * * @param pSolCore Pointer to the core object. */ static void FreeMemoryArea(PRTSOLCORE pSolCore) { AssertReturnVoid(pSolCore); AssertReturnVoid(pSolCore->pvCore); AssertReturnVoid(pSolCore->cbCore > 0); munmap(pSolCore->pvCore, pSolCore->cbCore); CORELOG((CORELOG_NAME "FreeMemoryArea: memory area of %u bytes freed.\n", pSolCore->cbCore)); pSolCore->pvCore = NULL; pSolCore->pvFree= NULL; pSolCore->cbCore = 0; } /** * Get a chunk from the area of allocated memory. * * @param pSolCore Pointer to the core object. * @param cb Size of requested chunk. * * @return Pointer to allocated memory, or NULL on failure. */ static void *GetMemoryChunk(PRTSOLCORE pSolCore, size_t cb) { AssertReturn(pSolCore, NULL); AssertReturn(pSolCore->pvCore, NULL); AssertReturn(pSolCore->pvFree, NULL); size_t cbAllocated = (char *)pSolCore->pvFree - (char *)pSolCore->pvCore; if (cbAllocated < pSolCore->cbCore) { char *pb = (char *)pSolCore->pvFree; pSolCore->pvFree = pb + cb; return pb; } return NULL; } /** * Reads the proc file's content into a newly allocated buffer. * * @param pSolCore Pointer to the core object. * @param pszProcFileName Only the name of the file to read from * (/proc/\ will be prepended) * @param ppv Where to store the allocated buffer. * @param pcb Where to store size of the buffer. * * @return IPRT status code. If the proc file is 0 bytes, VINF_SUCCESS is * returned with pointed to values of @c ppv, @c pcb set to NULL and 0 * respectively. */ static int ProcReadFileInto(PRTSOLCORE pSolCore, const char *pszProcFileName, void **ppv, size_t *pcb) { AssertReturn(pSolCore, VERR_INVALID_POINTER); char szPath[PATH_MAX]; RTStrPrintf(szPath, sizeof(szPath), "/proc/%d/%s", (int)pSolCore->SolProc.Process, pszProcFileName); int rc = VINF_SUCCESS; int fd = open(szPath, O_RDONLY); if (fd >= 0) { *pcb = GetFileSizeByFd(fd); if (*pcb > 0) { *ppv = GetMemoryChunk(pSolCore, *pcb); if (*ppv) rc = ReadFileNoIntr(fd, *ppv, *pcb); else rc = VERR_NO_MEMORY; } else { *pcb = 0; *ppv = NULL; rc = VINF_SUCCESS; } close(fd); } else { rc = RTErrConvertFromErrno(fd); CORELOGRELSYS((CORELOG_NAME "ProcReadFileInto: failed to open %s. rc=%Rrc\n", szPath, rc)); } return rc; } /** * Read process information (format psinfo_t) from /proc. * * @param pSolCore Pointer to the core object. * * @return IPRT status code. */ static int ProcReadInfo(PRTSOLCORE pSolCore) { AssertReturn(pSolCore, VERR_INVALID_POINTER); PRTSOLCOREPROCESS pSolProc = &pSolCore->SolProc; return ProcReadFileInto(pSolCore, "psinfo", &pSolProc->pvProcInfo, &pSolProc->cbProcInfo); } /** * Read process status (format pstatus_t) from /proc. * * @param pSolCore Pointer to the core object. * * @return IPRT status code. */ static int ProcReadStatus(PRTSOLCORE pSolCore) { AssertReturn(pSolCore, VERR_INVALID_POINTER); PRTSOLCOREPROCESS pSolProc = &pSolCore->SolProc; char szPath[PATH_MAX]; int rc = VINF_SUCCESS; RTStrPrintf(szPath, sizeof(szPath), "/proc/%d/status", (int)pSolProc->Process); int fd = open(szPath, O_RDONLY); if (fd >= 0) { size_t cbProcStatus = sizeof(pstatus_t); AssertCompile(sizeof(pstatus_t) == sizeof(pSolProc->ProcStatus)); rc = ReadFileNoIntr(fd, &pSolProc->ProcStatus, cbProcStatus); close(fd); } else { rc = RTErrConvertFromErrno(fd); CORELOGRELSYS((CORELOG_NAME "ProcReadStatus: failed to open %s. rc=%Rrc\n", szPath, rc)); } return rc; } /** * Read process credential information (format prcred_t + array of guid_t) * * @return IPRT status code. * @param pSolCore Pointer to the core object. * * @remarks Should not be called before successful call to @see AllocMemoryArea() */ static int ProcReadCred(PRTSOLCORE pSolCore) { AssertReturn(pSolCore, VERR_INVALID_POINTER); PRTSOLCOREPROCESS pSolProc = &pSolCore->SolProc; return ProcReadFileInto(pSolCore, "cred", &pSolProc->pvCred, &pSolProc->cbCred); } /** * Read process privilege information (format prpriv_t + array of priv_chunk_t) * * @return IPRT status code. * @param pSolCore Pointer to the core object. * * @remarks Should not be called before successful call to @see AllocMemoryArea() */ static int ProcReadPriv(PRTSOLCORE pSolCore) { AssertReturn(pSolCore, VERR_INVALID_POINTER); PRTSOLCOREPROCESS pSolProc = &pSolCore->SolProc; int rc = ProcReadFileInto(pSolCore, "priv", (void **)&pSolProc->pPriv, &pSolProc->cbPriv); if (RT_FAILURE(rc)) return rc; pSolProc->pcPrivImpl = getprivimplinfo(); if (!pSolProc->pcPrivImpl) { CORELOGRELSYS((CORELOG_NAME "ProcReadPriv: getprivimplinfo returned NULL.\n")); return VERR_INVALID_STATE; } return rc; } /** * Read process LDT information (format array of struct ssd) from /proc. * * @return IPRT status code. * @param pSolCore Pointer to the core object. * * @remarks Should not be called before successful call to @see AllocMemoryArea() */ static int ProcReadLdt(PRTSOLCORE pSolCore) { AssertReturn(pSolCore, VERR_INVALID_POINTER); PRTSOLCOREPROCESS pSolProc = &pSolCore->SolProc; return ProcReadFileInto(pSolCore, "ldt", &pSolProc->pvLdt, &pSolProc->cbLdt); } /** * Read process auxiliary vectors (format auxv_t) for the process. * * @return IPRT status code. * @param pSolCore Pointer to the core object. * * @remarks Should not be called before successful call to @see AllocMemoryArea() */ static int ProcReadAuxVecs(PRTSOLCORE pSolCore) { AssertReturn(pSolCore, VERR_INVALID_POINTER); PRTSOLCOREPROCESS pSolProc = &pSolCore->SolProc; char szPath[PATH_MAX]; int rc = VINF_SUCCESS; RTStrPrintf(szPath, sizeof(szPath), "/proc/%d/auxv", (int)pSolProc->Process); int fd = open(szPath, O_RDONLY); if (fd < 0) { rc = RTErrConvertFromErrno(fd); CORELOGRELSYS((CORELOG_NAME "ProcReadAuxVecs: failed to open %s rc=%Rrc\n", szPath, rc)); return rc; } size_t cbAuxFile = GetFileSizeByFd(fd); if (cbAuxFile >= sizeof(auxv_t)) { pSolProc->pAuxVecs = (auxv_t*)GetMemoryChunk(pSolCore, cbAuxFile + sizeof(auxv_t)); if (pSolProc->pAuxVecs) { rc = ReadFileNoIntr(fd, pSolProc->pAuxVecs, cbAuxFile); if (RT_SUCCESS(rc)) { /* Terminate list of vectors */ pSolProc->cAuxVecs = cbAuxFile / sizeof(auxv_t); CORELOG((CORELOG_NAME "ProcReadAuxVecs: cbAuxFile=%u auxv_t size %d cAuxVecs=%u\n", cbAuxFile, sizeof(auxv_t), pSolProc->cAuxVecs)); if (pSolProc->cAuxVecs > 0) { pSolProc->pAuxVecs[pSolProc->cAuxVecs].a_type = AT_NULL; pSolProc->pAuxVecs[pSolProc->cAuxVecs].a_un.a_val = 0L; close(fd); return VINF_SUCCESS; } CORELOGRELSYS((CORELOG_NAME "ProcReadAuxVecs: Invalid vector count %u\n", pSolProc->cAuxVecs)); rc = VERR_READ_ERROR; } else CORELOGRELSYS((CORELOG_NAME "ProcReadAuxVecs: ReadFileNoIntr failed. rc=%Rrc cbAuxFile=%u\n", rc, cbAuxFile)); pSolProc->pAuxVecs = NULL; pSolProc->cAuxVecs = 0; } else { CORELOGRELSYS((CORELOG_NAME "ProcReadAuxVecs: no memory for %u bytes\n", cbAuxFile + sizeof(auxv_t))); rc = VERR_NO_MEMORY; } } else { CORELOGRELSYS((CORELOG_NAME "ProcReadAuxVecs: aux file too small %u, expecting %u or more\n", cbAuxFile, sizeof(auxv_t))); rc = VERR_READ_ERROR; } close(fd); return rc; } /* * Find an element in the process' auxiliary vector. */ static long GetAuxVal(PRTSOLCOREPROCESS pSolProc, int Type) { AssertReturn(pSolProc, -1); if (pSolProc->pAuxVecs) { auxv_t *pAuxVec = pSolProc->pAuxVecs; for (; pAuxVec->a_type != AT_NULL; pAuxVec++) { if (pAuxVec->a_type == Type) return pAuxVec->a_un.a_val; } } return -1; } /** * Read the process mappings (format prmap_t array). * * @return IPRT status code. * @param pSolCore Pointer to the core object. * * @remarks Should not be called before successful call to @see AllocMemoryArea() */ static int ProcReadMappings(PRTSOLCORE pSolCore) { AssertReturn(pSolCore, VERR_INVALID_POINTER); PRTSOLCOREPROCESS pSolProc = &pSolCore->SolProc; char szPath[PATH_MAX]; int rc = VINF_SUCCESS; RTStrPrintf(szPath, sizeof(szPath), "/proc/%d/map", (int)pSolProc->Process); int fdMap = open(szPath, O_RDONLY); if (fdMap < 0) { rc = RTErrConvertFromErrno(errno); CORELOGRELSYS((CORELOG_NAME "ProcReadMappings: failed to open %s. rc=%Rrc\n", szPath, rc)); return rc; } RTStrPrintf(szPath, sizeof(szPath), "/proc/%d/as", (int)pSolProc->Process); pSolProc->fdAs = open(szPath, O_RDONLY); if (pSolProc->fdAs >= 0) { /* * Allocate and read all the prmap_t objects from proc. */ size_t cbMapFile = GetFileSizeByFd(fdMap); if (cbMapFile >= sizeof(prmap_t)) { prmap_t *pMap = (prmap_t*)GetMemoryChunk(pSolCore, cbMapFile); if (pMap) { rc = ReadFileNoIntr(fdMap, pMap, cbMapFile); if (RT_SUCCESS(rc)) { pSolProc->cMappings = cbMapFile / sizeof(prmap_t); if (pSolProc->cMappings > 0) { /* * Allocate for each prmap_t object, a corresponding RTSOLCOREMAPINFO object. */ pSolProc->pMapInfoHead = (PRTSOLCOREMAPINFO)GetMemoryChunk(pSolCore, pSolProc->cMappings * sizeof(RTSOLCOREMAPINFO)); if (pSolProc->pMapInfoHead) { /* * Associate the prmap_t with the mapping info object. */ /*Assert(pSolProc->pMapInfoHead == NULL); - does not make sense */ PRTSOLCOREMAPINFO pCur = pSolProc->pMapInfoHead; PRTSOLCOREMAPINFO pPrev = NULL; for (uint64_t i = 0; i < pSolProc->cMappings; i++, pMap++, pCur++) { memcpy(&pCur->pMap, pMap, sizeof(pCur->pMap)); if (pPrev) pPrev->pNext = pCur; pCur->fError = 0; /* * Make sure we can read the mapping, otherwise mark them to be skipped. */ char achBuf[PAGE_SIZE]; uint64_t k = 0; while (k < pCur->pMap.pr_size) { size_t cb = RT_MIN(sizeof(achBuf), pCur->pMap.pr_size - k); int rc2 = ProcReadAddrSpace(pSolProc, pCur->pMap.pr_vaddr + k, &achBuf, cb); if (RT_FAILURE(rc2)) { CORELOGRELSYS((CORELOG_NAME "ProcReadMappings: skipping mapping. vaddr=%#x rc=%Rrc\n", pCur->pMap.pr_vaddr, rc2)); /* * Instead of storing the actual mapping data which we failed to read, the core * will contain an errno in place. So we adjust the prmap_t's size field too * so the program header offsets match. */ pCur->pMap.pr_size = RT_ALIGN_Z(sizeof(int), 8); pCur->fError = errno; if (pCur->fError == 0) /* huh!? somehow errno got reset? fake one! EFAULT is nice. */ pCur->fError = EFAULT; break; } k += cb; } pPrev = pCur; } if (pPrev) pPrev->pNext = NULL; close(fdMap); close(pSolProc->fdAs); pSolProc->fdAs = -1; CORELOG((CORELOG_NAME "ProcReadMappings: successfully read in %u mappings\n", pSolProc->cMappings)); return VINF_SUCCESS; } CORELOGRELSYS((CORELOG_NAME "ProcReadMappings: GetMemoryChunk failed %u\n", pSolProc->cMappings * sizeof(RTSOLCOREMAPINFO))); rc = VERR_NO_MEMORY; } else { CORELOGRELSYS((CORELOG_NAME "ProcReadMappings: Invalid mapping count %u\n", pSolProc->cMappings)); rc = VERR_READ_ERROR; } } else { CORELOGRELSYS((CORELOG_NAME "ProcReadMappings: FileReadNoIntr failed. rc=%Rrc cbMapFile=%u\n", rc, cbMapFile)); } } else { CORELOGRELSYS((CORELOG_NAME "ProcReadMappings: GetMemoryChunk failed. cbMapFile=%u\n", cbMapFile)); rc = VERR_NO_MEMORY; } } close(pSolProc->fdAs); pSolProc->fdAs = -1; } else CORELOGRELSYS((CORELOG_NAME "ProcReadMappings: failed to open %s. rc=%Rrc\n", szPath, rc)); close(fdMap); return rc; } /** * Reads the thread information for all threads in the process. * * @return IPRT status code. * @param pSolCore Pointer to the core object. * * @remarks Should not be called before successful call to @see AllocMemoryArea() */ static int ProcReadThreads(PRTSOLCORE pSolCore) { AssertReturn(pSolCore, VERR_INVALID_POINTER); PRTSOLCOREPROCESS pSolProc = &pSolCore->SolProc; AssertReturn(pSolProc->pCurThreadCtx, VERR_NO_DATA); /* * Read the information for threads. * Format: prheader_t + array of lwpsinfo_t's. */ size_t cbInfoHdrAndData; void *pvInfoHdr = NULL; int rc = ProcReadFileInto(pSolCore, "lpsinfo", &pvInfoHdr, &cbInfoHdrAndData); if (RT_SUCCESS(rc)) { /* * Read the status of threads. * Format: prheader_t + array of lwpstatus_t's. */ void *pvStatusHdr = NULL; size_t cbStatusHdrAndData; rc = ProcReadFileInto(pSolCore, "lstatus", &pvStatusHdr, &cbStatusHdrAndData); if (RT_SUCCESS(rc)) { prheader_t *pInfoHdr = (prheader_t *)pvInfoHdr; prheader_t *pStatusHdr = (prheader_t *)pvStatusHdr; lwpstatus_t *pStatus = (lwpstatus_t *)((uintptr_t)pStatusHdr + sizeof(prheader_t)); lwpsinfo_t *pInfo = (lwpsinfo_t *)((uintptr_t)pInfoHdr + sizeof(prheader_t)); uint64_t cStatus = pStatusHdr->pr_nent; uint64_t cInfo = pInfoHdr->pr_nent; CORELOG((CORELOG_NAME "ProcReadThreads: read info(%u) status(%u), threads:cInfo=%u cStatus=%u\n", cbInfoHdrAndData, cbStatusHdrAndData, cInfo, cStatus)); /* * Minor sanity size check (remember sizeof lwpstatus_t & lwpsinfo_t is <= size in file per entry). */ if ( (cbStatusHdrAndData - sizeof(prheader_t)) % pStatusHdr->pr_entsize == 0 && (cbInfoHdrAndData - sizeof(prheader_t)) % pInfoHdr->pr_entsize == 0) { /* * Make sure we have a matching lstatus entry for an lpsinfo entry unless * it is a zombie thread, in which case we will not have a matching lstatus entry. */ for (; cInfo != 0; cInfo--) { if (pInfo->pr_sname != 'Z') /* zombie */ { if ( cStatus == 0 || pStatus->pr_lwpid != pInfo->pr_lwpid) { CORELOGRELSYS((CORELOG_NAME "ProcReadThreads: cStatus = %u pStatuslwpid=%d infolwpid=%d\n", cStatus, pStatus->pr_lwpid, pInfo->pr_lwpid)); rc = VERR_INVALID_STATE; break; } pStatus = (lwpstatus_t *)((uintptr_t)pStatus + pStatusHdr->pr_entsize); cStatus--; } pInfo = (lwpsinfo_t *)((uintptr_t)pInfo + pInfoHdr->pr_entsize); } if (RT_SUCCESS(rc)) { /* * There can still be more lwpsinfo_t's than lwpstatus_t's, build the * lists accordingly. */ pStatus = (lwpstatus_t *)((uintptr_t)pStatusHdr + sizeof(prheader_t)); pInfo = (lwpsinfo_t *)((uintptr_t)pInfoHdr + sizeof(prheader_t)); cInfo = pInfoHdr->pr_nent; cStatus = pInfoHdr->pr_nent; size_t cbThreadInfo = RT_MAX(cStatus, cInfo) * sizeof(RTSOLCORETHREADINFO); pSolProc->pThreadInfoHead = (PRTSOLCORETHREADINFO)GetMemoryChunk(pSolCore, cbThreadInfo); if (pSolProc->pThreadInfoHead) { PRTSOLCORETHREADINFO pCur = pSolProc->pThreadInfoHead; PRTSOLCORETHREADINFO pPrev = NULL; for (uint64_t i = 0; i < cInfo; i++, pCur++) { pCur->Info = *pInfo; if ( pInfo->pr_sname != 'Z' && pInfo->pr_lwpid == pStatus->pr_lwpid) { /* * Adjust the context of the dumping thread to reflect the context * when the core dump got initiated before whatever signal caused it. */ if ( pStatus /* noid droid */ && pStatus->pr_lwpid == (id_t)pSolProc->hCurThread) { AssertCompile(sizeof(pStatus->pr_reg) == sizeof(pSolProc->pCurThreadCtx->uc_mcontext.gregs)); AssertCompile(sizeof(pStatus->pr_fpreg) == sizeof(pSolProc->pCurThreadCtx->uc_mcontext.fpregs)); memcpy(&pStatus->pr_reg, &pSolProc->pCurThreadCtx->uc_mcontext.gregs, sizeof(pStatus->pr_reg)); memcpy(&pStatus->pr_fpreg, &pSolProc->pCurThreadCtx->uc_mcontext.fpregs, sizeof(pStatus->pr_fpreg)); AssertCompile(sizeof(pStatus->pr_lwphold) == sizeof(pSolProc->pCurThreadCtx->uc_sigmask)); memcpy(&pStatus->pr_lwphold, &pSolProc->pCurThreadCtx->uc_sigmask, sizeof(pStatus->pr_lwphold)); pStatus->pr_ustack = (uintptr_t)&pSolProc->pCurThreadCtx->uc_stack; CORELOG((CORELOG_NAME "ProcReadThreads: patched dumper thread with pre-dump time context.\n")); } pCur->pStatus = pStatus; pStatus = (lwpstatus_t *)((uintptr_t)pStatus + pStatusHdr->pr_entsize); } else { CORELOGRELSYS((CORELOG_NAME "ProcReadThreads: missing status for lwp %d\n", pInfo->pr_lwpid)); pCur->pStatus = NULL; } if (pPrev) pPrev->pNext = pCur; pPrev = pCur; pInfo = (lwpsinfo_t *)((uintptr_t)pInfo + pInfoHdr->pr_entsize); } if (pPrev) pPrev->pNext = NULL; CORELOG((CORELOG_NAME "ProcReadThreads: successfully read %u threads.\n", cInfo)); pSolProc->cThreads = cInfo; return VINF_SUCCESS; } else { CORELOGRELSYS((CORELOG_NAME "ProcReadThreads: GetMemoryChunk failed for %u bytes\n", cbThreadInfo)); rc = VERR_NO_MEMORY; } } else CORELOGRELSYS((CORELOG_NAME "ProcReadThreads: Invalid state information for threads. rc=%Rrc\n", rc)); } else { CORELOGRELSYS((CORELOG_NAME "ProcReadThreads: huh!? cbStatusHdrAndData=%u prheader_t=%u entsize=%u\n", cbStatusHdrAndData, sizeof(prheader_t), pStatusHdr->pr_entsize)); CORELOGRELSYS((CORELOG_NAME "ProcReadThreads: huh!? cbInfoHdrAndData=%u entsize=%u\n", cbInfoHdrAndData, pStatusHdr->pr_entsize)); rc = VERR_INVALID_STATE; } } else CORELOGRELSYS((CORELOG_NAME "ProcReadThreads: ReadFileNoIntr failed for \"lpsinfo\" rc=%Rrc\n", rc)); } else CORELOGRELSYS((CORELOG_NAME "ProcReadThreads: ReadFileNoIntr failed for \"lstatus\" rc=%Rrc\n", rc)); return rc; } /** * Reads miscellaneous information that is collected as part of a core file. * This may include platform name, zone name and other OS-specific information. * * @param pSolCore Pointer to the core object. * * @return IPRT status code. */ static int ProcReadMiscInfo(PRTSOLCORE pSolCore) { AssertReturn(pSolCore, VERR_INVALID_POINTER); PRTSOLCOREPROCESS pSolProc = &pSolCore->SolProc; #ifdef RT_OS_SOLARIS /* * Read the platform name, uname string and zone name. */ int rc = sysinfo(SI_PLATFORM, pSolProc->szPlatform, sizeof(pSolProc->szPlatform)); if (rc == -1) { CORELOGRELSYS((CORELOG_NAME "ProcReadMiscInfo: sysinfo failed. rc=%d errno=%d\n", rc, errno)); return VERR_GENERAL_FAILURE; } pSolProc->szPlatform[sizeof(pSolProc->szPlatform) - 1] = '\0'; rc = uname(&pSolProc->UtsName); if (rc == -1) { CORELOGRELSYS((CORELOG_NAME "ProcReadMiscInfo: uname failed. rc=%d errno=%d\n", rc, errno)); return VERR_GENERAL_FAILURE; } /* * See comment in GetOldProcessInfo() for why we need to verify the offset here. * It's not perfect, but it should be fine unless they really mess up the structure * layout in the future. See @bugref{8479}. */ size_t const offZoneId = RT_UOFFSETOF(psinfo_t, pr_zoneid); if (pSolProc->cbProcInfo < offZoneId) { CORELOGRELSYS((CORELOG_NAME "ProcReadMiscInfo: psinfo size mismatch. cbProcInfo=%u expected >= %u\n", pSolProc->cbProcInfo, offZoneId)); return VERR_GENERAL_FAILURE; } psinfo_t *pProcInfo = (psinfo_t *)pSolProc->pvProcInfo; rc = getzonenamebyid(pProcInfo->pr_zoneid, pSolProc->szZoneName, sizeof(pSolProc->szZoneName)); if (rc < 0) { CORELOGRELSYS((CORELOG_NAME "ProcReadMiscInfo: getzonenamebyid failed. rc=%d errno=%d zoneid=%d\n", rc, errno, pProcInfo->pr_zoneid)); return VERR_GENERAL_FAILURE; } pSolProc->szZoneName[sizeof(pSolProc->szZoneName) - 1] = '\0'; rc = VINF_SUCCESS; #else # error Port Me! #endif return rc; } /** * On Solaris use the old-style procfs interfaces but the core file still should have this * info. for backward and GDB compatibility, hence the need for this ugly function. * * @returns IPRT status code. * * @param pSolCore Pointer to the core object. * @param pInfo Pointer to the old prpsinfo_t structure to update. */ static int GetOldProcessInfo(PRTSOLCORE pSolCore, prpsinfo_t *pInfo) { AssertReturn(pSolCore, VERR_INVALID_PARAMETER); AssertReturn(pInfo, VERR_INVALID_PARAMETER); /* * The psinfo_t and the size of /proc//psinfo varies both within the same Solaris system * and across Solaris major versions. However, manual dumping of the structure and offsets shows * that they changed the size of lwpsinfo_t and the size of the lwpsinfo_t::pr_filler. * * The proc psinfo file will be what gets dumped ultimately in the core file but we still need * to read the fields to translate to the older process info structure here. * * See @bugref{8479}. */ PRTSOLCOREPROCESS pSolProc = &pSolCore->SolProc; size_t offLwp = RT_UOFFSETOF(psinfo_t, pr_lwp); /* last member we care about in lwpsinfo_t is pr_bindpset which is also present on ancient Solaris version we use for building the additions. Should be safe enough as we don't need/access members upto or beyond that point anyway. */ size_t offLastOnProc = RT_UOFFSETOF(lwpsinfo_t, pr_bindpset); if (pSolProc->cbProcInfo >= offLwp + offLastOnProc) { psinfo_t *pSrc = (psinfo_t *)pSolProc->pvProcInfo; memset(pInfo, 0, sizeof(prpsinfo_t)); pInfo->pr_state = pSrc->pr_lwp.pr_state; pInfo->pr_zomb = (pInfo->pr_state == SZOMB); RTStrCopy(pInfo->pr_clname, sizeof(pInfo->pr_clname), pSrc->pr_lwp.pr_clname); RTStrCopy(pInfo->pr_fname, sizeof(pInfo->pr_fname), pSrc->pr_fname); memcpy(&pInfo->pr_psargs, &pSrc->pr_psargs, sizeof(pInfo->pr_psargs)); pInfo->pr_nice = pSrc->pr_lwp.pr_nice; pInfo->pr_flag = pSrc->pr_lwp.pr_flag; pInfo->pr_uid = pSrc->pr_uid; pInfo->pr_gid = pSrc->pr_gid; pInfo->pr_pid = pSrc->pr_pid; pInfo->pr_ppid = pSrc->pr_ppid; pInfo->pr_pgrp = pSrc->pr_pgid; pInfo->pr_sid = pSrc->pr_sid; pInfo->pr_addr = (caddr_t)pSrc->pr_addr; pInfo->pr_size = pSrc->pr_size; pInfo->pr_rssize = pSrc->pr_rssize; pInfo->pr_wchan = (caddr_t)pSrc->pr_lwp.pr_wchan; pInfo->pr_start = pSrc->pr_start; pInfo->pr_time = pSrc->pr_time; pInfo->pr_pri = pSrc->pr_lwp.pr_pri; pInfo->pr_oldpri = pSrc->pr_lwp.pr_oldpri; pInfo->pr_cpu = pSrc->pr_lwp.pr_cpu; pInfo->pr_ottydev = cmpdev(pSrc->pr_ttydev); pInfo->pr_lttydev = pSrc->pr_ttydev; pInfo->pr_syscall = pSrc->pr_lwp.pr_syscall; pInfo->pr_ctime = pSrc->pr_ctime; pInfo->pr_bysize = pSrc->pr_size * PAGESIZE; pInfo->pr_byrssize = pSrc->pr_rssize * PAGESIZE; pInfo->pr_argc = pSrc->pr_argc; pInfo->pr_argv = (char **)pSrc->pr_argv; pInfo->pr_envp = (char **)pSrc->pr_envp; pInfo->pr_wstat = pSrc->pr_wstat; pInfo->pr_pctcpu = pSrc->pr_pctcpu; pInfo->pr_pctmem = pSrc->pr_pctmem; pInfo->pr_euid = pSrc->pr_euid; pInfo->pr_egid = pSrc->pr_egid; pInfo->pr_aslwpid = 0; pInfo->pr_dmodel = pSrc->pr_dmodel; return VINF_SUCCESS; } CORELOGRELSYS((CORELOG_NAME "GetOldProcessInfo: Size/offset mismatch. offLwp=%u offLastOnProc=%u cbProcInfo=%u\n", offLwp, offLastOnProc, pSolProc->cbProcInfo)); return VERR_MISMATCH; } /** * On Solaris use the old-style procfs interfaces but the core file still should have this * info. for backward and GDB compatibility, hence the need for this ugly function. * * @param pSolCore Pointer to the core object. * @param pInfo Pointer to the thread info. * @param pStatus Pointer to the thread status. * @param pDst Pointer to the old-style status structure to update. * */ static void GetOldProcessStatus(PRTSOLCORE pSolCore, lwpsinfo_t *pInfo, lwpstatus_t *pStatus, prstatus_t *pDst) { AssertReturnVoid(pSolCore); AssertReturnVoid(pInfo); AssertReturnVoid(pStatus); AssertReturnVoid(pDst); PRTSOLCOREPROCESS pSolProc = &pSolCore->SolProc; memset(pDst, 0, sizeof(prstatus_t)); if (pStatus->pr_flags & PR_STOPPED) pDst->pr_flags = 0x0001; if (pStatus->pr_flags & PR_ISTOP) pDst->pr_flags = 0x0002; if (pStatus->pr_flags & PR_DSTOP) pDst->pr_flags = 0x0004; if (pStatus->pr_flags & PR_ASLEEP) pDst->pr_flags = 0x0008; if (pStatus->pr_flags & PR_FORK) pDst->pr_flags = 0x0010; if (pStatus->pr_flags & PR_RLC) pDst->pr_flags = 0x0020; /* PR_PTRACE is never set */ if (pStatus->pr_flags & PR_PCINVAL) pDst->pr_flags = 0x0080; if (pStatus->pr_flags & PR_ISSYS) pDst->pr_flags = 0x0100; if (pStatus->pr_flags & PR_STEP) pDst->pr_flags = 0x0200; if (pStatus->pr_flags & PR_KLC) pDst->pr_flags = 0x0400; if (pStatus->pr_flags & PR_ASYNC) pDst->pr_flags = 0x0800; if (pStatus->pr_flags & PR_PTRACE) pDst->pr_flags = 0x1000; if (pStatus->pr_flags & PR_MSACCT) pDst->pr_flags = 0x2000; if (pStatus->pr_flags & PR_BPTADJ) pDst->pr_flags = 0x4000; if (pStatus->pr_flags & PR_ASLWP) pDst->pr_flags = 0x8000; pDst->pr_who = pStatus->pr_lwpid; pDst->pr_why = pStatus->pr_why; pDst->pr_what = pStatus->pr_what; pDst->pr_info = pStatus->pr_info; pDst->pr_cursig = pStatus->pr_cursig; pDst->pr_sighold = pStatus->pr_lwphold; pDst->pr_altstack = pStatus->pr_altstack; pDst->pr_action = pStatus->pr_action; pDst->pr_syscall = pStatus->pr_syscall; pDst->pr_nsysarg = pStatus->pr_nsysarg; pDst->pr_lwppend = pStatus->pr_lwppend; pDst->pr_oldcontext = (ucontext_t *)pStatus->pr_oldcontext; memcpy(pDst->pr_reg, pStatus->pr_reg, sizeof(pDst->pr_reg)); memcpy(pDst->pr_sysarg, pStatus->pr_sysarg, sizeof(pDst->pr_sysarg)); RTStrCopy(pDst->pr_clname, sizeof(pDst->pr_clname), pStatus->pr_clname); pDst->pr_nlwp = pSolProc->ProcStatus.pr_nlwp; pDst->pr_sigpend = pSolProc->ProcStatus.pr_sigpend; pDst->pr_pid = pSolProc->ProcStatus.pr_pid; pDst->pr_ppid = pSolProc->ProcStatus.pr_ppid; pDst->pr_pgrp = pSolProc->ProcStatus.pr_pgid; pDst->pr_sid = pSolProc->ProcStatus.pr_sid; pDst->pr_utime = pSolProc->ProcStatus.pr_utime; pDst->pr_stime = pSolProc->ProcStatus.pr_stime; pDst->pr_cutime = pSolProc->ProcStatus.pr_cutime; pDst->pr_cstime = pSolProc->ProcStatus.pr_cstime; pDst->pr_brkbase = (caddr_t)pSolProc->ProcStatus.pr_brkbase; pDst->pr_brksize = pSolProc->ProcStatus.pr_brksize; pDst->pr_stkbase = (caddr_t)pSolProc->ProcStatus.pr_stkbase; pDst->pr_stksize = pSolProc->ProcStatus.pr_stksize; pDst->pr_processor = (short)pInfo->pr_onpro; pDst->pr_bind = (short)pInfo->pr_bindpro; pDst->pr_instr = pStatus->pr_instr; } /** * Callback for rtCoreDumperForEachThread to suspend a thread. * * @param pSolCore Pointer to the core object. * @param pvThreadInfo Opaque pointer to thread information. * * @return IPRT status code. */ static int suspendThread(PRTSOLCORE pSolCore, void *pvThreadInfo) { AssertPtrReturn(pvThreadInfo, VERR_INVALID_POINTER); NOREF(pSolCore); lwpsinfo_t *pThreadInfo = (lwpsinfo_t *)pvThreadInfo; CORELOG((CORELOG_NAME ":suspendThread %d\n", (lwpid_t)pThreadInfo->pr_lwpid)); if ((lwpid_t)pThreadInfo->pr_lwpid != pSolCore->SolProc.hCurThread) _lwp_suspend(pThreadInfo->pr_lwpid); return VINF_SUCCESS; } /** * Callback for rtCoreDumperForEachThread to resume a thread. * * @param pSolCore Pointer to the core object. * @param pvThreadInfo Opaque pointer to thread information. * * @return IPRT status code. */ static int resumeThread(PRTSOLCORE pSolCore, void *pvThreadInfo) { AssertPtrReturn(pvThreadInfo, VERR_INVALID_POINTER); NOREF(pSolCore); lwpsinfo_t *pThreadInfo = (lwpsinfo_t *)pvThreadInfo; CORELOG((CORELOG_NAME ":resumeThread %d\n", (lwpid_t)pThreadInfo->pr_lwpid)); if ((lwpid_t)pThreadInfo->pr_lwpid != (lwpid_t)pSolCore->SolProc.hCurThread) _lwp_continue(pThreadInfo->pr_lwpid); return VINF_SUCCESS; } /** * Calls a thread worker function for all threads in the process as described by /proc * * @param pSolCore Pointer to the core object. * @param pcThreads Number of threads read. * @param pfnWorker Callback function for each thread. * * @return IPRT status code. */ static int rtCoreDumperForEachThread(PRTSOLCORE pSolCore, uint64_t *pcThreads, PFNRTSOLCORETHREADWORKER pfnWorker) { AssertPtrReturn(pSolCore, VERR_INVALID_POINTER); PRTSOLCOREPROCESS pSolProc = &pSolCore->SolProc; /* * Read the information for threads. * Format: prheader_t + array of lwpsinfo_t's. */ char szLpsInfoPath[PATH_MAX]; RTStrPrintf(szLpsInfoPath, sizeof(szLpsInfoPath), "/proc/%d/lpsinfo", (int)pSolProc->Process); int rc = VINF_SUCCESS; int fd = open(szLpsInfoPath, O_RDONLY); if (fd >= 0) { size_t cbInfoHdrAndData = GetFileSizeByFd(fd); void *pvInfoHdr = mmap(NULL, cbInfoHdrAndData, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1 /* fd */, 0 /* offset */); if (pvInfoHdr != MAP_FAILED) { rc = ReadFileNoIntr(fd, pvInfoHdr, cbInfoHdrAndData); if (RT_SUCCESS(rc)) { prheader_t *pHeader = (prheader_t *)pvInfoHdr; lwpsinfo_t *pThreadInfo = (lwpsinfo_t *)((uintptr_t)pvInfoHdr + sizeof(prheader_t)); for (long i = 0; i < pHeader->pr_nent; i++) { pfnWorker(pSolCore, pThreadInfo); pThreadInfo = (lwpsinfo_t *)((uintptr_t)pThreadInfo + pHeader->pr_entsize); } if (pcThreads) *pcThreads = pHeader->pr_nent; } munmap(pvInfoHdr, cbInfoHdrAndData); } else rc = VERR_NO_MEMORY; close(fd); } else rc = RTErrConvertFromErrno(rc); return rc; } /** * Resume all threads of this process. * * @param pSolCore Pointer to the core object. * * @return IPRT status code.. */ static int rtCoreDumperResumeThreads(PRTSOLCORE pSolCore) { AssertReturn(pSolCore, VERR_INVALID_POINTER); uint64_t cThreads; return rtCoreDumperForEachThread(pSolCore, &cThreads, resumeThread); } /** * Stop all running threads of this process except the current one. * * @param pSolCore Pointer to the core object. * * @return IPRT status code. */ static int rtCoreDumperSuspendThreads(PRTSOLCORE pSolCore) { AssertPtrReturn(pSolCore, VERR_INVALID_POINTER); /* * This function tries to ensures while we suspend threads, no newly spawned threads * or a combination of spawning and terminating threads can cause any threads to be left running. * The assumption here is that threads can only increase not decrease across iterations. */ uint16_t cTries = 0; uint64_t aThreads[4]; RT_ZERO(aThreads); int rc = VERR_GENERAL_FAILURE; void *pv = NULL; size_t cb = 0; for (cTries = 0; cTries < RT_ELEMENTS(aThreads); cTries++) { rc = rtCoreDumperForEachThread(pSolCore, &aThreads[cTries], suspendThread); if (RT_FAILURE(rc)) break; } if ( RT_SUCCESS(rc) && aThreads[cTries - 1] != aThreads[cTries - 2]) { CORELOGRELSYS((CORELOG_NAME "rtCoreDumperSuspendThreads: possible thread bomb!?\n")); rc = VERR_TIMEOUT; } return rc; } /** * Returns size of an ELF NOTE header given the size of data the NOTE section will contain. * * @param cb Size of the data. * * @return Size of data actually used for NOTE header and section. */ static inline size_t ElfNoteHeaderSize(size_t cb) { return sizeof(ELFNOTEHDR) + RT_ALIGN_Z(cb, 4); } /** * Write an ELF NOTE header into the core file. * * @param pSolCore Pointer to the core object. * @param Type Type of this NOTE section. * @param pcv Opaque pointer to the data, if NULL only computes size. * @param cb Size of the data. * * @return IPRT status code. */ static int ElfWriteNoteHeader(PRTSOLCORE pSolCore, uint_t Type, const void *pcv, size_t cb) { AssertReturn(pSolCore, VERR_INVALID_POINTER); AssertReturn(pcv, VERR_INVALID_POINTER); AssertReturn(cb > 0, VERR_NO_DATA); AssertReturn(pSolCore->pfnWriter, VERR_WRITE_ERROR); AssertReturn(pSolCore->fdCoreFile >= 0, VERR_INVALID_HANDLE); int rc = VERR_GENERAL_FAILURE; #ifdef RT_OS_SOLARIS ELFNOTEHDR ElfNoteHdr; RT_ZERO(ElfNoteHdr); ElfNoteHdr.achName[0] = 'C'; ElfNoteHdr.achName[1] = 'O'; ElfNoteHdr.achName[2] = 'R'; ElfNoteHdr.achName[3] = 'E'; /* * This is a known violation of the 64-bit ELF spec., see xTracker @bugref{5211} * for the historic reasons as to the padding and 'namesz' anomalies. */ static const char s_achPad[3] = { 0, 0, 0 }; size_t cbAlign = RT_ALIGN_Z(cb, 4); ElfNoteHdr.Hdr.n_namesz = 5; ElfNoteHdr.Hdr.n_type = Type; ElfNoteHdr.Hdr.n_descsz = cbAlign; /* * Write note header and description. */ rc = pSolCore->pfnWriter(pSolCore->fdCoreFile, &ElfNoteHdr, sizeof(ElfNoteHdr)); if (RT_SUCCESS(rc)) { rc = pSolCore->pfnWriter(pSolCore->fdCoreFile, pcv, cb); if (RT_SUCCESS(rc)) { if (cbAlign > cb) rc = pSolCore->pfnWriter(pSolCore->fdCoreFile, s_achPad, cbAlign - cb); } } if (RT_FAILURE(rc)) CORELOGRELSYS((CORELOG_NAME "ElfWriteNote: pfnWriter failed. Type=%d rc=%Rrc\n", Type, rc)); #else #error Port Me! #endif return rc; } /** * Computes the size of NOTE section for the given core type. * Solaris has two types of program header information (new and old). * * @param pSolCore Pointer to the core object. * @param enmType Type of core file information required. * * @return Size of NOTE section. */ static size_t ElfNoteSectionSize(PRTSOLCORE pSolCore, RTSOLCORETYPE enmType) { PRTSOLCOREPROCESS pSolProc = &pSolCore->SolProc; size_t cb = 0; switch (enmType) { case enmOldEra: { cb += ElfNoteHeaderSize(sizeof(prpsinfo_t)); cb += ElfNoteHeaderSize(pSolProc->cAuxVecs * sizeof(auxv_t)); cb += ElfNoteHeaderSize(strlen(pSolProc->szPlatform)); PRTSOLCORETHREADINFO pThreadInfo = pSolProc->pThreadInfoHead; while (pThreadInfo) { if (pThreadInfo->pStatus) { cb += ElfNoteHeaderSize(sizeof(prstatus_t)); cb += ElfNoteHeaderSize(sizeof(prfpregset_t)); } pThreadInfo = pThreadInfo->pNext; } break; } case enmNewEra: { cb += ElfNoteHeaderSize(sizeof(psinfo_t)); cb += ElfNoteHeaderSize(sizeof(pstatus_t)); cb += ElfNoteHeaderSize(pSolProc->cAuxVecs * sizeof(auxv_t)); cb += ElfNoteHeaderSize(strlen(pSolProc->szPlatform) + 1); cb += ElfNoteHeaderSize(sizeof(struct utsname)); cb += ElfNoteHeaderSize(sizeof(core_content_t)); cb += ElfNoteHeaderSize(pSolProc->cbCred); if (pSolProc->pPriv) cb += ElfNoteHeaderSize(PRIV_PRPRIV_SIZE(pSolProc->pPriv)); /* Ought to be same as cbPriv!? */ if (pSolProc->pcPrivImpl) cb += ElfNoteHeaderSize(PRIV_IMPL_INFO_SIZE(pSolProc->pcPrivImpl)); cb += ElfNoteHeaderSize(strlen(pSolProc->szZoneName) + 1); if (pSolProc->cbLdt > 0) cb += ElfNoteHeaderSize(pSolProc->cbLdt); PRTSOLCORETHREADINFO pThreadInfo = pSolProc->pThreadInfoHead; while (pThreadInfo) { cb += ElfNoteHeaderSize(sizeof(lwpsinfo_t)); if (pThreadInfo->pStatus) cb += ElfNoteHeaderSize(sizeof(lwpstatus_t)); pThreadInfo = pThreadInfo->pNext; } break; } default: { CORELOGRELSYS((CORELOG_NAME "ElfNoteSectionSize: Unknown segment era %d\n", enmType)); break; } } return cb; } /** * Write the note section for the given era into the core file. * Solaris has two types of program header information (new and old). * * @param pSolCore Pointer to the core object. * @param enmType Type of core file information required. * * @return IPRT status code. */ static int ElfWriteNoteSection(PRTSOLCORE pSolCore, RTSOLCORETYPE enmType) { AssertReturn(pSolCore, VERR_INVALID_POINTER); PRTSOLCOREPROCESS pSolProc = &pSolCore->SolProc; int rc = VERR_GENERAL_FAILURE; #ifdef RT_OS_SOLARIS typedef int (*PFNELFWRITENOTEHDR)(PRTSOLCORE pSolCore, uint_t, const void *pcv, size_t cb); typedef struct { const char *pszType; uint_t Type; const void *pcv; size_t cb; } ELFWRITENOTE; switch (enmType) { case enmOldEra: { ELFWRITENOTE aElfNotes[] = { { "NT_PRPSINFO", NT_PRPSINFO, &pSolProc->ProcInfoOld, sizeof(prpsinfo_t) }, { "NT_AUXV", NT_AUXV, pSolProc->pAuxVecs, pSolProc->cAuxVecs * sizeof(auxv_t) }, { "NT_PLATFORM", NT_PLATFORM, pSolProc->szPlatform, strlen(pSolProc->szPlatform) + 1 } }; for (unsigned i = 0; i < RT_ELEMENTS(aElfNotes); i++) { rc = ElfWriteNoteHeader(pSolCore, aElfNotes[i].Type, aElfNotes[i].pcv, aElfNotes[i].cb); if (RT_FAILURE(rc)) { CORELOGRELSYS((CORELOG_NAME "ElfWriteNoteSection: ElfWriteNoteHeader failed for %s. rc=%Rrc\n", aElfNotes[i].pszType, rc)); break; } } /* * Write old-style thread info., they contain nothing about zombies, * so we just skip if there is no status information for them. */ PRTSOLCORETHREADINFO pThreadInfo = pSolProc->pThreadInfoHead; for (; pThreadInfo; pThreadInfo = pThreadInfo->pNext) { if (!pThreadInfo->pStatus) continue; prstatus_t OldProcessStatus; GetOldProcessStatus(pSolCore, &pThreadInfo->Info, pThreadInfo->pStatus, &OldProcessStatus); rc = ElfWriteNoteHeader(pSolCore, NT_PRSTATUS, &OldProcessStatus, sizeof(prstatus_t)); if (RT_SUCCESS(rc)) { rc = ElfWriteNoteHeader(pSolCore, NT_PRFPREG, &pThreadInfo->pStatus->pr_fpreg, sizeof(prfpregset_t)); if (RT_FAILURE(rc)) { CORELOGRELSYS((CORELOG_NAME "ElfWriteSegment: ElfWriteNote failed for NT_PRFPREF. rc=%Rrc\n", rc)); break; } } else { CORELOGRELSYS((CORELOG_NAME "ElfWriteSegment: ElfWriteNote failed for NT_PRSTATUS. rc=%Rrc\n", rc)); break; } } break; } case enmNewEra: { ELFWRITENOTE aElfNotes[] = { { "NT_PSINFO", NT_PSINFO, pSolProc->pvProcInfo, pSolProc->cbProcInfo }, { "NT_PSTATUS", NT_PSTATUS, &pSolProc->ProcStatus, sizeof(pstatus_t) }, { "NT_AUXV", NT_AUXV, pSolProc->pAuxVecs, pSolProc->cAuxVecs * sizeof(auxv_t) }, { "NT_PLATFORM", NT_PLATFORM, pSolProc->szPlatform, strlen(pSolProc->szPlatform) + 1 }, { "NT_UTSNAME", NT_UTSNAME, &pSolProc->UtsName, sizeof(struct utsname) }, { "NT_CONTENT", NT_CONTENT, &pSolProc->CoreContent, sizeof(core_content_t) }, { "NT_PRCRED", NT_PRCRED, pSolProc->pvCred, pSolProc->cbCred }, { "NT_PRPRIV", NT_PRPRIV, pSolProc->pPriv, PRIV_PRPRIV_SIZE(pSolProc->pPriv) }, { "NT_PRPRIVINFO", NT_PRPRIVINFO, pSolProc->pcPrivImpl, PRIV_IMPL_INFO_SIZE(pSolProc->pcPrivImpl) }, { "NT_ZONENAME", NT_ZONENAME, pSolProc->szZoneName, strlen(pSolProc->szZoneName) + 1 } }; for (unsigned i = 0; i < RT_ELEMENTS(aElfNotes); i++) { rc = ElfWriteNoteHeader(pSolCore, aElfNotes[i].Type, aElfNotes[i].pcv, aElfNotes[i].cb); if (RT_FAILURE(rc)) { CORELOGRELSYS((CORELOG_NAME "ElfWriteNoteSection: ElfWriteNoteHeader failed for %s. rc=%Rrc\n", aElfNotes[i].pszType, rc)); break; } } /* * Write new-style thread info., missing lwpstatus_t indicates it's a zombie thread * we only dump the lwpsinfo_t in that case. */ PRTSOLCORETHREADINFO pThreadInfo = pSolProc->pThreadInfoHead; for (; pThreadInfo; pThreadInfo = pThreadInfo->pNext) { rc = ElfWriteNoteHeader(pSolCore, NT_LWPSINFO, &pThreadInfo->Info, sizeof(lwpsinfo_t)); if (RT_FAILURE(rc)) { CORELOGRELSYS((CORELOG_NAME "ElfWriteNoteSection: ElfWriteNoteHeader for NT_LWPSINFO failed. rc=%Rrc\n", rc)); break; } if (pThreadInfo->pStatus) { rc = ElfWriteNoteHeader(pSolCore, NT_LWPSTATUS, pThreadInfo->pStatus, sizeof(lwpstatus_t)); if (RT_FAILURE(rc)) { CORELOGRELSYS((CORELOG_NAME "ElfWriteNoteSection: ElfWriteNoteHeader for NT_LWPSTATUS failed. rc=%Rrc\n", rc)); break; } } } break; } default: { CORELOGRELSYS((CORELOG_NAME "ElfWriteNoteSection: Invalid type %d\n", enmType)); rc = VERR_GENERAL_FAILURE; break; } } #else # error Port Me! #endif return rc; } /** * Write mappings into the core file. * * @param pSolCore Pointer to the core object. * * @return IPRT status code. */ static int ElfWriteMappings(PRTSOLCORE pSolCore) { PRTSOLCOREPROCESS pSolProc = &pSolCore->SolProc; int rc = VERR_GENERAL_FAILURE; PRTSOLCOREMAPINFO pMapInfo = pSolProc->pMapInfoHead; while (pMapInfo) { if (!pMapInfo->fError) { uint64_t k = 0; char achBuf[PAGE_SIZE]; while (k < pMapInfo->pMap.pr_size) { size_t cb = RT_MIN(sizeof(achBuf), pMapInfo->pMap.pr_size - k); int rc2 = ProcReadAddrSpace(pSolProc, pMapInfo->pMap.pr_vaddr + k, &achBuf, cb); if (RT_FAILURE(rc2)) { CORELOGRELSYS((CORELOG_NAME "ElfWriteMappings: Failed to read mapping, can't recover. Bye. rc=%Rrc\n", rc)); return VERR_INVALID_STATE; } rc = pSolCore->pfnWriter(pSolCore->fdCoreFile, achBuf, sizeof(achBuf)); if (RT_FAILURE(rc)) { CORELOGRELSYS((CORELOG_NAME "ElfWriteMappings: pfnWriter failed. rc=%Rrc\n", rc)); return rc; } k += cb; } } else { char achBuf[RT_ALIGN_Z(sizeof(int), 8)]; RT_ZERO(achBuf); memcpy(achBuf, &pMapInfo->fError, sizeof(pMapInfo->fError)); if (sizeof(achBuf) != pMapInfo->pMap.pr_size) CORELOGRELSYS((CORELOG_NAME "ElfWriteMappings: Huh!? something is wrong!\n")); rc = pSolCore->pfnWriter(pSolCore->fdCoreFile, &achBuf, sizeof(achBuf)); if (RT_FAILURE(rc)) { CORELOGRELSYS((CORELOG_NAME "ElfWriteMappings: pfnWriter(2) failed. rc=%Rrc\n", rc)); return rc; } } pMapInfo = pMapInfo->pNext; } return VINF_SUCCESS; } /** * Write program headers for all mappings into the core file. * * @param pSolCore Pointer to the core object. * * @return IPRT status code. */ static int ElfWriteMappingHeaders(PRTSOLCORE pSolCore) { AssertReturn(pSolCore, VERR_INVALID_POINTER); PRTSOLCOREPROCESS pSolProc = &pSolCore->SolProc; Elf_Phdr ProgHdr; RT_ZERO(ProgHdr); ProgHdr.p_type = PT_LOAD; int rc = VERR_GENERAL_FAILURE; PRTSOLCOREMAPINFO pMapInfo = pSolProc->pMapInfoHead; while (pMapInfo) { ProgHdr.p_vaddr = pMapInfo->pMap.pr_vaddr; /* Virtual address of this mapping in the process address space */ ProgHdr.p_offset = pSolCore->offWrite; /* Where this mapping is located in the core file */ ProgHdr.p_memsz = pMapInfo->pMap.pr_size; /* Size of the memory image of the mapping */ ProgHdr.p_filesz = pMapInfo->pMap.pr_size; /* Size of the file image of the mapping */ ProgHdr.p_flags = 0; /* Reset fields in a loop when needed! */ if (pMapInfo->pMap.pr_mflags & MA_READ) ProgHdr.p_flags |= PF_R; if (pMapInfo->pMap.pr_mflags & MA_WRITE) ProgHdr.p_flags |= PF_W; if (pMapInfo->pMap.pr_mflags & MA_EXEC) ProgHdr.p_flags |= PF_X; if (pMapInfo->fError) ProgHdr.p_flags |= PF_SUNW_FAILURE; rc = pSolCore->pfnWriter(pSolCore->fdCoreFile, &ProgHdr, sizeof(ProgHdr)); if (RT_FAILURE(rc)) { CORELOGRELSYS((CORELOG_NAME "ElfWriteMappingHeaders: pfnWriter failed. rc=%Rrc\n", rc)); return rc; } pSolCore->offWrite += ProgHdr.p_filesz; pMapInfo = pMapInfo->pNext; } return rc; } /** * Inner worker for rtCoreDumperWriteCore, which purpose is to * squash cleanup gotos. */ static int rtCoreDumperWriteCoreDoIt(PRTSOLCORE pSolCore, PFNRTCOREWRITER pfnWriter, PRTSOLCOREPROCESS pSolProc) { pSolCore->offWrite = 0; uint32_t cProgHdrs = pSolProc->cMappings + 2; /* two PT_NOTE program headers (old, new style) */ /* * Write the ELF header. */ Elf_Ehdr ElfHdr; RT_ZERO(ElfHdr); ElfHdr.e_ident[EI_MAG0] = ELFMAG0; ElfHdr.e_ident[EI_MAG1] = ELFMAG1; ElfHdr.e_ident[EI_MAG2] = ELFMAG2; ElfHdr.e_ident[EI_MAG3] = ELFMAG3; ElfHdr.e_ident[EI_DATA] = IsBigEndian() ? ELFDATA2MSB : ELFDATA2LSB; ElfHdr.e_type = ET_CORE; ElfHdr.e_version = EV_CURRENT; #ifdef RT_ARCH_AMD64 ElfHdr.e_machine = EM_AMD64; ElfHdr.e_ident[EI_CLASS] = ELFCLASS64; #else ElfHdr.e_machine = EM_386; ElfHdr.e_ident[EI_CLASS] = ELFCLASS32; #endif if (cProgHdrs >= PN_XNUM) ElfHdr.e_phnum = PN_XNUM; else ElfHdr.e_phnum = cProgHdrs; ElfHdr.e_ehsize = sizeof(ElfHdr); ElfHdr.e_phoff = sizeof(ElfHdr); ElfHdr.e_phentsize = sizeof(Elf_Phdr); ElfHdr.e_shentsize = sizeof(Elf_Shdr); int rc = pSolCore->pfnWriter(pSolCore->fdCoreFile, &ElfHdr, sizeof(ElfHdr)); if (RT_FAILURE(rc)) { CORELOGRELSYS((CORELOG_NAME "WriteCore: pfnWriter failed writing ELF header. rc=%Rrc\n", rc)); return rc; } /* * Setup program header. */ Elf_Phdr ProgHdr; RT_ZERO(ProgHdr); ProgHdr.p_type = PT_NOTE; ProgHdr.p_flags = PF_R; /* * Write old-style NOTE program header. */ pSolCore->offWrite += sizeof(ElfHdr) + cProgHdrs * sizeof(ProgHdr); ProgHdr.p_offset = pSolCore->offWrite; ProgHdr.p_filesz = ElfNoteSectionSize(pSolCore, enmOldEra); rc = pSolCore->pfnWriter(pSolCore->fdCoreFile, &ProgHdr, sizeof(ProgHdr)); if (RT_FAILURE(rc)) { CORELOGRELSYS((CORELOG_NAME "WriteCore: pfnWriter failed writing old-style ELF program Header. rc=%Rrc\n", rc)); return rc; } /* * Write new-style NOTE program header. */ pSolCore->offWrite += ProgHdr.p_filesz; ProgHdr.p_offset = pSolCore->offWrite; ProgHdr.p_filesz = ElfNoteSectionSize(pSolCore, enmNewEra); rc = pSolCore->pfnWriter(pSolCore->fdCoreFile, &ProgHdr, sizeof(ProgHdr)); if (RT_FAILURE(rc)) { CORELOGRELSYS((CORELOG_NAME "WriteCore: pfnWriter failed writing new-style ELF program header. rc=%Rrc\n", rc)); return rc; } /* * Write program headers per mapping. */ pSolCore->offWrite += ProgHdr.p_filesz; rc = ElfWriteMappingHeaders(pSolCore); if (RT_FAILURE(rc)) { CORELOGRELSYS((CORELOG_NAME "Write: ElfWriteMappings failed. rc=%Rrc\n", rc)); return rc; } /* * Write old-style note section. */ rc = ElfWriteNoteSection(pSolCore, enmOldEra); if (RT_FAILURE(rc)) { CORELOGRELSYS((CORELOG_NAME "WriteCore: ElfWriteNoteSection old-style failed. rc=%Rrc\n", rc)); return rc; } /* * Write new-style section. */ rc = ElfWriteNoteSection(pSolCore, enmNewEra); if (RT_FAILURE(rc)) { CORELOGRELSYS((CORELOG_NAME "WriteCore: ElfWriteNoteSection new-style failed. rc=%Rrc\n", rc)); return rc; } /* * Write all mappings. */ rc = ElfWriteMappings(pSolCore); if (RT_FAILURE(rc)) { CORELOGRELSYS((CORELOG_NAME "WriteCore: ElfWriteMappings failed. rc=%Rrc\n", rc)); return rc; } return rc; } /** * Write a prepared core file using a user-passed in writer function, requires all threads * to be in suspended state (i.e. called after CreateCore). * * @return IPRT status code. * @param pSolCore Pointer to the core object. * @param pfnWriter Pointer to the writer function to override default writer (NULL uses default). * * @remarks Resumes all suspended threads, unless it's an invalid core. This * function must be called only -after- rtCoreDumperCreateCore(). */ static int rtCoreDumperWriteCore(PRTSOLCORE pSolCore, PFNRTCOREWRITER pfnWriter) { AssertReturn(pSolCore, VERR_INVALID_POINTER); if (!pSolCore->fIsValid) return VERR_INVALID_STATE; if (pfnWriter) pSolCore->pfnWriter = pfnWriter; PRTSOLCOREPROCESS pSolProc = &pSolCore->SolProc; char szPath[PATH_MAX]; int rc; /* * Open the process address space file. */ RTStrPrintf(szPath, sizeof(szPath), "/proc/%d/as", (int)pSolProc->Process); int fd = open(szPath, O_RDONLY); if (fd >= 0) { pSolProc->fdAs = fd; /* * Create the core file. */ fd = open(pSolCore->szCorePath, O_CREAT | O_TRUNC | O_RDWR, S_IRUSR); if (fd >= 0) { pSolCore->fdCoreFile = fd; /* * Do the actual writing. */ rc = rtCoreDumperWriteCoreDoIt(pSolCore, pfnWriter, pSolProc); close(pSolCore->fdCoreFile); pSolCore->fdCoreFile = -1; } else { rc = RTErrConvertFromErrno(fd); CORELOGRELSYS((CORELOG_NAME "WriteCore: failed to open %s. rc=%Rrc\n", pSolCore->szCorePath, rc)); } close(pSolProc->fdAs); pSolProc->fdAs = -1; } else { rc = RTErrConvertFromErrno(fd); CORELOGRELSYS((CORELOG_NAME "WriteCore: Failed to open address space, %s. rc=%Rrc\n", szPath, rc)); } rtCoreDumperResumeThreads(pSolCore); return rc; } /** * Takes a process snapshot into a passed-in core object. It has the side-effect of halting * all threads which can lead to things like spurious wakeups of threads (if and when threads * are ultimately resumed en-masse) already suspended while calling this function. * * @return IPRT status code. * @param pSolCore Pointer to a core object. * @param pContext Pointer to the caller context thread. * @param pszCoreFilePath Path to the core file. If NULL is passed, the global * path specified in RTCoreDumperSetup() would be used. * * @remarks Halts all threads. */ static int rtCoreDumperCreateCore(PRTSOLCORE pSolCore, ucontext_t *pContext, const char *pszCoreFilePath) { AssertReturn(pSolCore, VERR_INVALID_POINTER); AssertReturn(pContext, VERR_INVALID_POINTER); /* * Initialize core structures. */ memset(pSolCore, 0, sizeof(RTSOLCORE)); pSolCore->pfnReader = &ReadFileNoIntr; pSolCore->pfnWriter = &WriteFileNoIntr; pSolCore->fIsValid = false; pSolCore->fdCoreFile = -1; PRTSOLCOREPROCESS pSolProc = &pSolCore->SolProc; pSolProc->Process = RTProcSelf(); pSolProc->hCurThread = _lwp_self(); /* thr_self() */ pSolProc->fdAs = -1; pSolProc->pCurThreadCtx = pContext; pSolProc->CoreContent = CC_CONTENT_DEFAULT; RTProcGetExecutablePath(pSolProc->szExecPath, sizeof(pSolProc->szExecPath)); /* this gets full path not just name */ pSolProc->pszExecName = RTPathFilename(pSolProc->szExecPath); /* * If a path has been specified, use it. Otherwise use the global path. */ if (!pszCoreFilePath) { /* * If no output directory is specified, use current directory. */ if (g_szCoreDumpDir[0] == '\0') g_szCoreDumpDir[0] = '.'; if (g_szCoreDumpFile[0] == '\0') { /* We cannot call RTPathAbs*() as they call getcwd() which calls malloc. */ RTStrPrintf(pSolCore->szCorePath, sizeof(pSolCore->szCorePath), "%s/core.vb.%s.%d", g_szCoreDumpDir, pSolProc->pszExecName, (int)pSolProc->Process); } else RTStrPrintf(pSolCore->szCorePath, sizeof(pSolCore->szCorePath), "%s/core.vb.%s", g_szCoreDumpDir, g_szCoreDumpFile); } else RTStrCopy(pSolCore->szCorePath, sizeof(pSolCore->szCorePath), pszCoreFilePath); CORELOG((CORELOG_NAME "CreateCore: Taking Core %s from Thread %d\n", pSolCore->szCorePath, (int)pSolProc->hCurThread)); /* * Quiesce the process. */ int rc = rtCoreDumperSuspendThreads(pSolCore); if (RT_SUCCESS(rc)) { rc = AllocMemoryArea(pSolCore); if (RT_SUCCESS(rc)) { rc = ProcReadInfo(pSolCore); if (RT_SUCCESS(rc)) { rc = GetOldProcessInfo(pSolCore, &pSolProc->ProcInfoOld); if (RT_SUCCESS(rc)) { if (IsProcessArchNative(pSolProc)) { /* * Read process status, information such as number of active LWPs will be * invalid since we just quiesced the process. */ rc = ProcReadStatus(pSolCore); if (RT_SUCCESS(rc)) { struct COREACCUMULATOR { const char *pszName; PFNRTSOLCOREACCUMULATOR pfnAcc; bool fOptional; } aAccumulators[] = { { "ProcReadLdt", &ProcReadLdt, false }, { "ProcReadCred", &ProcReadCred, false }, { "ProcReadPriv", &ProcReadPriv, false }, { "ProcReadAuxVecs", &ProcReadAuxVecs, false }, { "ProcReadMappings", &ProcReadMappings, false }, { "ProcReadThreads", &ProcReadThreads, false }, { "ProcReadMiscInfo", &ProcReadMiscInfo, false } }; for (unsigned i = 0; i < RT_ELEMENTS(aAccumulators); i++) { rc = aAccumulators[i].pfnAcc(pSolCore); if (RT_FAILURE(rc)) { CORELOGRELSYS((CORELOG_NAME "CreateCore: %s failed. rc=%Rrc\n", aAccumulators[i].pszName, rc)); if (!aAccumulators[i].fOptional) break; } } if (RT_SUCCESS(rc)) { pSolCore->fIsValid = true; return VINF_SUCCESS; } FreeMemoryArea(pSolCore); } else CORELOGRELSYS((CORELOG_NAME "CreateCore: ProcReadStatus failed. rc=%Rrc\n", rc)); } else { CORELOGRELSYS((CORELOG_NAME "CreateCore: IsProcessArchNative failed.\n")); rc = VERR_BAD_EXE_FORMAT; } } else CORELOGRELSYS((CORELOG_NAME "CreateCore: GetOldProcessInfo failed. rc=%Rrc\n", rc)); } else CORELOGRELSYS((CORELOG_NAME "CreateCore: ProcReadInfo failed. rc=%Rrc\n", rc)); } else CORELOGRELSYS((CORELOG_NAME "CreateCore: AllocMemoryArea failed. rc=%Rrc\n", rc)); /* * Resume threads on failure. */ rtCoreDumperResumeThreads(pSolCore); } else CORELOG((CORELOG_NAME "CreateCore: SuspendAllThreads failed. Thread bomb!?! rc=%Rrc\n", rc)); return rc; } /** * Destroy an existing core object. * * @param pSolCore Pointer to the core object. * * @return IPRT status code. */ static int rtCoreDumperDestroyCore(PRTSOLCORE pSolCore) { AssertReturn(pSolCore, VERR_INVALID_POINTER); if (!pSolCore->fIsValid) return VERR_INVALID_STATE; FreeMemoryArea(pSolCore); pSolCore->fIsValid = false; return VINF_SUCCESS; } /** * Takes a core dump. * * @param pContext The context of the caller. * @param pszOutputFile Path of the core file. If NULL is passed, the * global path passed in RTCoreDumperSetup will * be used. * @returns IPRT status code. */ static int rtCoreDumperTakeDump(ucontext_t *pContext, const char *pszOutputFile) { if (!pContext) { CORELOGRELSYS((CORELOG_NAME "TakeDump: Missing context.\n")); return VERR_INVALID_POINTER; } /* * Take a snapshot, then dump core to disk, all threads except this one are halted * from before taking the snapshot until writing the core is completely finished. * Any errors would resume all threads if they were halted. */ RTSOLCORE SolCore; RT_ZERO(SolCore); int rc = rtCoreDumperCreateCore(&SolCore, pContext, pszOutputFile); if (RT_SUCCESS(rc)) { rc = rtCoreDumperWriteCore(&SolCore, &WriteFileNoIntr); if (RT_SUCCESS(rc)) CORELOGRELSYS((CORELOG_NAME "Core dumped in %s\n", SolCore.szCorePath)); else CORELOGRELSYS((CORELOG_NAME "TakeDump: WriteCore failed. szCorePath=%s rc=%Rrc\n", SolCore.szCorePath, rc)); rtCoreDumperDestroyCore(&SolCore); } else CORELOGRELSYS((CORELOG_NAME "TakeDump: CreateCore failed. rc=%Rrc\n", rc)); return rc; } /** * The signal handler that will be invoked to take core dumps. * * @param Sig The signal that invoked us. * @param pSigInfo The signal information. * @param pvArg Opaque pointer to the caller context structure, * this cannot be NULL. */ static void rtCoreDumperSignalHandler(int Sig, siginfo_t *pSigInfo, void *pvArg) { CORELOG((CORELOG_NAME "SignalHandler Sig=%d pvArg=%p\n", Sig, pvArg)); RTNATIVETHREAD hCurNativeThread = RTThreadNativeSelf(); int rc = VERR_GENERAL_FAILURE; bool fCallSystemDump = false; bool fRc; ASMAtomicCmpXchgHandle(&g_CoreDumpThread, hCurNativeThread, NIL_RTNATIVETHREAD, fRc); if (fRc) { rc = rtCoreDumperTakeDump((ucontext_t *)pvArg, NULL /* Use Global Core filepath */); ASMAtomicWriteHandle(&g_CoreDumpThread, NIL_RTNATIVETHREAD); if (RT_FAILURE(rc)) CORELOGRELSYS((CORELOG_NAME "TakeDump failed! rc=%Rrc\n", rc)); } else if (Sig == SIGSEGV || Sig == SIGBUS || Sig == SIGTRAP) { /* * Core dumping is already in progress and we've somehow ended up being * signalled again. */ rc = VERR_INTERNAL_ERROR; /* * If our dumper has crashed. No point in waiting, trigger the system one. * Wait only when the dumping thread is not the one generating this signal. */ RTNATIVETHREAD hNativeDumperThread; ASMAtomicReadHandle(&g_CoreDumpThread, &hNativeDumperThread); if (hNativeDumperThread == RTThreadNativeSelf()) { CORELOGRELSYS((CORELOG_NAME "SignalHandler: Core dumper (thread %u) crashed Sig=%d. Triggering system dump\n", RTThreadSelf(), Sig)); fCallSystemDump = true; } else { /* * Some other thread in the process is triggering a crash, wait a while * to let our core dumper finish, on timeout trigger system dump. */ CORELOGRELSYS((CORELOG_NAME "SignalHandler: Core dump already in progress! Waiting a while for completion Sig=%d.\n", Sig)); int64_t iTimeout = 16000; /* timeout (ms) */ for (;;) { ASMAtomicReadHandle(&g_CoreDumpThread, &hNativeDumperThread); if (hNativeDumperThread == NIL_RTNATIVETHREAD) break; RTThreadSleep(200); iTimeout -= 200; if (iTimeout <= 0) break; } if (iTimeout <= 0) { fCallSystemDump = true; CORELOGRELSYS((CORELOG_NAME "SignalHandler: Core dumper seems to be stuck. Signalling new signal %d\n", Sig)); } } } if (Sig == SIGSEGV || Sig == SIGBUS || Sig == SIGTRAP) { /* * Reset signal handlers, we're not a live core we will be blown away * one way or another. */ signal(SIGSEGV, SIG_DFL); signal(SIGBUS, SIG_DFL); signal(SIGTRAP, SIG_DFL); /* * Hard terminate the process if this is not a live dump without invoking * the system core dumping behaviour. */ if (RT_SUCCESS(rc)) raise(SIGKILL); /* * Something went wrong, fall back to the system core dumper. */ if (fCallSystemDump) abort(); } } RTDECL(int) RTCoreDumperTakeDump(const char *pszOutputFile, bool fLiveCore) { ucontext_t Context; int rc = getcontext(&Context); if (!rc) { /* * Block SIGSEGV and co. while we write the core. */ sigset_t SigSet, OldSigSet; sigemptyset(&SigSet); sigaddset(&SigSet, SIGSEGV); sigaddset(&SigSet, SIGBUS); sigaddset(&SigSet, SIGTRAP); sigaddset(&SigSet, SIGUSR2); pthread_sigmask(SIG_BLOCK, &SigSet, &OldSigSet); rc = rtCoreDumperTakeDump(&Context, pszOutputFile); if (RT_FAILURE(rc)) CORELOGRELSYS(("RTCoreDumperTakeDump: rtCoreDumperTakeDump failed rc=%Rrc\n", rc)); if (!fLiveCore) { signal(SIGSEGV, SIG_DFL); signal(SIGBUS, SIG_DFL); signal(SIGTRAP, SIG_DFL); if (RT_SUCCESS(rc)) raise(SIGKILL); else abort(); } pthread_sigmask(SIG_SETMASK, &OldSigSet, NULL); } else { CORELOGRELSYS(("RTCoreDumperTakeDump: getcontext failed rc=%d.\n", rc)); rc = VERR_INVALID_CONTEXT; } return rc; } RTDECL(int) RTCoreDumperSetup(const char *pszOutputDir, uint32_t fFlags) { /* * Validate flags. */ AssertReturn(fFlags, VERR_INVALID_PARAMETER); AssertReturn(!(fFlags & ~( RTCOREDUMPER_FLAGS_REPLACE_SYSTEM_DUMP | RTCOREDUMPER_FLAGS_LIVE_CORE)), VERR_INVALID_PARAMETER); /* * Setup/change the core dump directory if specified. */ RT_ZERO(g_szCoreDumpDir); if (pszOutputDir) { if (!RTDirExists(pszOutputDir)) return VERR_NOT_A_DIRECTORY; RTStrCopy(g_szCoreDumpDir, sizeof(g_szCoreDumpDir), pszOutputDir); } /* * Install core dump signal handler only if the flags changed or if it's the first time. */ if ( ASMAtomicReadBool(&g_fCoreDumpSignalSetup) == false || ASMAtomicReadU32(&g_fCoreDumpFlags) != fFlags) { struct sigaction sigAct; RT_ZERO(sigAct); sigAct.sa_sigaction = &rtCoreDumperSignalHandler; if ( (fFlags & RTCOREDUMPER_FLAGS_REPLACE_SYSTEM_DUMP) && !(g_fCoreDumpFlags & RTCOREDUMPER_FLAGS_REPLACE_SYSTEM_DUMP)) { sigemptyset(&sigAct.sa_mask); sigAct.sa_flags = SA_RESTART | SA_SIGINFO | SA_NODEFER; sigaction(SIGSEGV, &sigAct, NULL); sigaction(SIGBUS, &sigAct, NULL); sigaction(SIGTRAP, &sigAct, NULL); } if ( fFlags & RTCOREDUMPER_FLAGS_LIVE_CORE && !(g_fCoreDumpFlags & RTCOREDUMPER_FLAGS_LIVE_CORE)) { sigfillset(&sigAct.sa_mask); /* Block all signals while in it's signal handler */ sigAct.sa_flags = SA_RESTART | SA_SIGINFO; sigaction(SIGUSR2, &sigAct, NULL); } ASMAtomicWriteU32(&g_fCoreDumpFlags, fFlags); ASMAtomicWriteBool(&g_fCoreDumpSignalSetup, true); } return VINF_SUCCESS; } RTDECL(int) RTCoreDumperDisable(void) { /* * Remove core dump signal handler & reset variables. */ if (ASMAtomicReadBool(&g_fCoreDumpSignalSetup) == true) { signal(SIGSEGV, SIG_DFL); signal(SIGBUS, SIG_DFL); signal(SIGTRAP, SIG_DFL); signal(SIGUSR2, SIG_DFL); ASMAtomicWriteBool(&g_fCoreDumpSignalSetup, false); } RT_ZERO(g_szCoreDumpDir); RT_ZERO(g_szCoreDumpFile); ASMAtomicWriteU32(&g_fCoreDumpFlags, 0); return VINF_SUCCESS; }