/* $Id: kLdrHlp.c 2854 2006-11-03 03:39:12Z bird $ */ /** @file * * kLdr - The Dynamic Loader, Helper Functions. * * Copyright (c) 2006 knut st. osmundsen * * * This file is part of kLdr. * * kLdr 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 of the License, or * (at your option) any later version. * * kLdr 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 kLdr; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ /******************************************************************************* * Header Files * *******************************************************************************/ #ifdef __OS2__ # define INCL_BASE # define INCL_ERRORS # include #elif defined(__WIN__) # include #else # error "port me" #endif #include #include "kLdrHlp.h" /******************************************************************************* * Global Variables * *******************************************************************************/ #ifdef __OS2__ /** The loader sempahore. */ static HMTX g_hmtx; /** The base of the stub object. * The OS/2 exe stub consists of a single data object. When allocating memory * for an executable, we'll have to reuse this. */ static void *g_pvStub = NULL; /** The size of the stub object - 0 if no stub. */ static size_t g_cbStub = 0; #elif defined(__WIN__) /** The loader sempahore. */ static CRITICAL_SECTION g_CritSect; /** The system info. */ static SYSTEM_INFO g_SystemInfo; #else # error "port me" #endif /** * Initializes the loader semaphore. * * @returns 0 on success, non-zero OS status code on failure. */ int kldrHlpSemInit(void) { #ifdef __OS2__ APIRET rc; g_hmtx = NULLHANDLE; rc = DosCreateMutexSem(NULL, &g_hmtx, 0, FALSE); if (rc) return rc; #elif defined(__WIN__) InitializeCriticalSection(&g_CritSect); #else # error "port me" #endif return 0; } /** * Terminates the loader semaphore. */ void kldrHlpSemTerm(void) { #ifdef __OS2__ APIRET rc; HMTX hmtx = g_hmtx; g_hmtx = NULLHANDLE; rc = DosCloseMutexSem(hmtx); if (rc) return rc; #elif defined(__WIN__) DeleteCriticalSection(&g_CritSect); #else # error "port me" #endif } /** * Requests the loader sempahore ownership. * This can be done recursivly. * * @returns 0 on success, non-zero OS status code on failure. */ int kldrHlpSemRequest(void) { #ifdef __OS2__ APIRET rc = DosRequestMutexSem(g_hmtx, 5000); if (rc == ERROR_TIMEOUT || rc == ERROR_SEM_TIMEOUT || rc == ERROR_INTERRUPT) { unsigned i = 0; do { /** @todo check for deadlocks etc. */ rc = DosRequestMutexSem(g_hmtx, 1000); } while ( ( rc == ERROR_TIMEOUT || rc == ERROR_SEM_TIMEOUT || rc == ERROR_INTERRUPT) && i++ < 120); } return rc; #elif defined(__WIN__) EnterCriticalSection(&g_CritSect); return 0; #else # error "port me" #endif } /** * Releases the loader semaphore ownership. * The caller is responsible for making sure it's the semaphore owner! */ void kldrHlpSemRelease(void) { #ifdef __OS2__ APIRET rc = DosReleaseMutexSem(g_hmtx); kldrHlpAssert(!rc); (void)rc; #elif defined(__WIN__) LeaveCriticalSection(&g_CritSect); #else # error "port me" #endif } #ifdef __OS2__ static ULONG kldrHlpPageProtToNative(KLDRPROT enmProt) { switch (enmProt) { case KLDRPROT_NOACCESS: return PAG_EXECUTE | PAG_READ | PAG_WRITE; case KLDRPROT_READONLY: return PAG_COMMIT | PAG_READ; case KLDRPROT_READWRITE: return PAG_COMMIT | PAG_READ | PAG_WRITE; case KLDRPROT_EXECUTE: return PAG_COMMIT | PAG_EXECUTE; case KLDRPROT_EXECUTE_READ: return PAG_COMMIT | PAG_EXECUTE | PAG_READ; case KLDRPROT_EXECUTE_READWRITE: return PAG_COMMIT | PAG_EXECUTE | PAG_READ | PAG_WRITE; default: kldrHlpAssert(0); return ~0U; } } #elif defined(__WIN__) static DWORD kldrHlpPageProtToNative(KLDRPROT enmProt) { switch (enmProt) { case KLDRPROT_NOACCESS: return PAGE_NOACCESS; case KLDRPROT_READONLY: return PAGE_READONLY; case KLDRPROT_READWRITE: return PAGE_READWRITE; case KLDRPROT_EXECUTE: return PAGE_EXECUTE; case KLDRPROT_EXECUTE_READ: return PAGE_EXECUTE_READ; case KLDRPROT_EXECUTE_READWRITE: return PAGE_EXECUTE_READWRITE; default: kldrHlpAssert(0); return ~0U; } } #endif /** * Allocate a chunk of memory with page granularity. * * @returns 0 on success, non-zero OS status code on failure. * @param ppv Where to store the address of the allocated memory. * If fFixed is set, *ppv will on entry contain the desired address (page aligned). * @param cb Number of bytes. Page aligned. * @param enmProt The new protection. Copy-on-write is invalid. */ int kldrHlpPageAlloc(void **ppv, size_t cb, KLDRPROT enmProt, unsigned fFixed) { #ifdef __OS2__ APIRET rc; ULONG fFlags = kldrHlpPageProtToNative(enmProt);; if (!fFixed) { /* simple */ rc = DosAllocMem(ppv, cb, fFlags | OBJ_ANY); if (rc == ERROR_INVALID_PARAMETER) rc = DosAllocMem(ppv, cb, fFlags); } else { /* not so simple. */ /** @todo I've got code for this in libc somewhere. */ } if (!rc) return 0; kldrHlpAssert(0); return rc; #elif defined(__WIN__) /* (We don't have to care about the stub here, because the stub will be unmapped before we get here.) */ int rc; DWORD fProt = kldrHlpPageProtToNative(enmProt); if (!g_SystemInfo.dwPageSize) GetSystemInfo(&g_SystemInfo); *ppv = VirtualAlloc(fFixed ? *ppv : NULL, cb, MEM_COMMIT, fProt); if (*ppv == NULL) { rc = GetLastError(); kldrHlpAssert(0); } return rc; #else # error "port me" #endif } /** * Change the protection of one or more pages in an allocation. * * (This will of course only work correctly on memory allocated by kldrHlpPageAlloc().) * * @returns 0 on success, non-zero OS status code on failure. * @param pv First page. Page aligned. * @param cb Number of bytes. Page aligned. * @param enmProt The new protection. Copy-on-write is invalid. */ int kldrHlpPageProtect(void *pv, size_t cb, KLDRPROT enmProt) { #ifdef __OS2__ APIRET rc; uintptr_t offStub; ULONG fFlags = kldrHlpPageProtToNative(enmProt);; /* * The non-stub pages. */ rc = DosSetMem(pv, cb, fFlags); if (rc && fFlags != PAG_DECOMMIT) rc = DosSetMem(pv, cb, fFlags | PAG_COMMIT); if (rc) { /* Try page by page. */ while (cb > 0) { rc = DosSetMem(pv, 0x1000, fFlags); if (rc && fFlags != PAG_DECOMMIT) rc = DosSetMem(pv, 0x1000, fFlags | PAG_COMMIT); if (rc) return rc; pv = (void *)((uintptr_t)pv + 0x1000); cb -= 0x1000; } } kldrHlpAssert(!rc); return rc; #elif defined(__WIN__) DWORD fOldProt = 0; DWORD fProt = kldrHlpPageProtToNative(enmProt); int rc = 0; if (!VirtualProtect(pv, cb, fProt, &fOldProt)) { rc = GetLastError(); kldrHlpAssert(0); } return rc; #else # error "port me" #endif } /** * Free memory allocated by kldrHlpPageAlloc(). * * @returns 0 on success, non-zero OS status code on failure. * @param pv The address returned by kldrHlpPageAlloc(). * @param cb The byte count requested from kldrHlpPageAlloc(). */ int kldrHlpPageFree(void *pv, size_t cb) { #ifdef __OS2__ APIRET rc; /* * Deal with any portion overlapping with the stub. */ uintptr_t offStub = (uintptr_t)pv - (uintptr_t)g_pvStub; if (offStub < g_cbStub) { /* decommit the pages in the stub. */ size_t cbSub = KLDR_MIN(g_cbStub - offStub, cb); rc = DosSetMem(pv, cbStub, PAG_DECOMMIT); if (rc) { /* Page by page, ignoring errors after the first success. */ while (cbSub > 0) { if (!DosSetMem(pv, 0x1000, PAG_DECOMMIT)) rc = 0; pv = (void *)((uintptr_t)pv + 0x1000); cbSub -= 0x1000; cb -= 0x1000; } if (rc) { kldrHlpAssert(!rc); return rc; } } else { cb -= cbSub; if (!cb) return 0; pv = (void *)((uintptr_t)pv + cbSub); } } /* * Free the object. */ rc = DosFreeMem(pv); kldrHlpAssert(!rc); return rc; #elif defined(__WIN__) /* * Free the object. */ int rc = 0; if (!VirtualFree(pv, 0 /*cb*/, MEM_RELEASE)) { rc = GetLastError(); kldrHlpAssert(0); } return rc; #else # error "port me" #endif } /** * Get an environment variable. * * @returns 0 on success, on failure an OS specific status code is returned. * @param pszVar The variable name. * @param pszVal Where to store the value. NULL is allowed if *pcchVal is 0. * @param pcchVal On input the size of the buffer pointed to by pszVal. * On output this contains the string length on success, while on * failure (including buffer overflow) the require buffer size. * If the variable wasn't found, it's set to 0. */ int kldrHlpGetEnv(const char *pszVar, char *pszVal, size_t *pcchVal) { #ifdef __OS2__ PSZ pszValue = NULL; int rc = DosScanEnv((PCSZ)pszVar, &pszValue); if (!rc) { size_t cch = kLdrHlpStrLen(pszValue); if (pszVal) { if (*pcchVal > cch) { kLdrHlpMemCopy(pszVal, pszValue, cch + 1); *pcchVal = cch; } else if (*pcchVal) { kLdrHlpMemCopy(pszVal, pszValue, *pcchVal); pszVal[*pcchVal - 1] = '\0'; rc = ERROR_BUFFER_OVERFLOW; *pcchVal = cch + 1; } } else { rc = ERROR_BUFFER_OVERFLOW; *pcchVal = cch + 1; } } else { if (pszVal) *pszVal = '\0'; *pcchVal = 0; } return rc; #elif defined(__WIN__) DWORD cch; SetLastError(0); cch = GetEnvironmentVariable(pszVar, pszVal, *pcchVal); if (cch) { *pcchVal = cch; return 0; } if (!GetLastError() == ERROR_ENVVAR_NOT_FOUND) { *pcchVal = 0; return ERROR_ENVVAR_NOT_FOUND; } *pcchVal = cch; return ERROR_BUFFER_OVERFLOW; #else # error "Port me" #endif } /** * Gets an environment variable and converts it to a size_t. * * @returns 0 and *pcb on success. * Some non-zero OS or kLdr status code on failure. * @param pszVar The name of the variable. * @param pcb Where to put the value. */ int kldrHlpGetEnvUZ(const char *pszVar, size_t *pcb) { size_t cb; unsigned uBase; char szVal[64]; size_t cchVal = sizeof(szVal); const char *psz; int rc; *pcb = 0; rc = kldrHlpGetEnv(pszVar, szVal, &cchVal); if (rc) return rc; /* figure out the base. */ uBase = 10; psz = szVal; if ( *psz == '0' && (psz[1] == 'x' || psz[1] == 'X')) { uBase = 16; psz += 2; } /* convert it up to the first unknown char. */ cb = 0; for(;;) { const char ch = *psz; unsigned uDigit; if (!ch) break; else if (ch >= '0' && ch <= '9') uDigit = ch - '0'; else if (ch >= 'a' && ch <= 'z') uDigit = ch - 'a' + 10; else if (ch >= 'A' && ch <= 'Z') uDigit = ch - 'A' + 10; else break; if (uDigit >= uBase) break; /* add the digit */ cb *= uBase; cb += uDigit; psz++; } /* check for unit */ if (*psz == 'm' || *psz == 'M') cb *= 1024*1024; else if (*psz == 'k' ||*psz == 'K') cb *= 1024; else if (*psz == 'g' || *psz == 'G') cb *= 1024*1024*1024; *pcb = cb; return 0; } /** * Get the pointer to the filename part of the name. * * @returns Pointer to where the filename starts within the string pointed to by pszFilename. * @returns Pointer to the terminator char if no filename. * @param pszFilename The filename to parse. */ char *kldrHlpGetFilename(const char *pszFilename) { const char *pszLast = NULL; for (;;) { char ch = *pszFilename; #if defined(__OS2__) || defined(__WIN__) if (ch == '/' || ch == '\\' || ch == ':') { while ((ch = *++pszFilename) == '/' || ch == '\\' || ch == ':') /* nothing */; pszLast = pszFilename; } #else if (ch == '/') { while ((ch = *++pszFilename) == '/') /* betsuni */; pszLast = pszFilename; } #endif if (!ch) return (char *)(pszLast ? pszLast : pszFilename); pszFilename++; } } /** * Gets the filename suffix. * * @returns Pointer to where the suffix starts within the string pointed to by pszFilename. * @returns Pointer to the terminator char if no suffix. * @param pszFilename The filename to parse. */ char *kldrHlpGetSuff(const char *pszFilename) { const char *pszDot = NULL; pszFilename = kldrHlpGetFilename(pszFilename); for (;;) { char ch = *pszFilename; if (ch == '.') { while ((ch = *++pszFilename) == '.') /* nothing */; if (ch) pszDot = pszFilename - 1; } if (!ch) return (char *)(pszDot ? pszDot : pszFilename); pszFilename++; } } /** * Gets the filename extention. * * @returns Pointer to where the extension starts within the string pointed to by pszFilename. * @returns Pointer to the terminator char if no extension. * @param pszFilename The filename to parse. */ char *kldrHlpGetExt(const char *pszFilename) { char *psz = kldrHlpGetSuff(pszFilename); return *psz ? psz + 1 : psz; } /** * Terminate the process. * * @param rc The exit status. */ void kldrHlpExit(int rc) { for (;;) { #ifdef __OS2__ DosExit(EXIT_PROCESS, rc); #elif defined(__WIN__) TerminateProcess(GetCurrentProcess(), rc); #else # error "Port me" #endif kldrHlpAssert(!"Impossible"); } } /** * Sleep for a number of milliseconds. * @param cMillies Number of milliseconds to sleep. */ void kldrHlpSleep(unsigned cMillies) { #ifdef __OS2__ DosSleep(cMillies); #elif defined(__WIN__) Sleep(cMillies); #else usleep(cMillies * 1000); #endif } /** * Converts an signed integer to an ascii string. * * @returns psz. * @param psz Pointer to the output buffer. * @param cch The size of the output buffer. * @param lVal The value. * @param iBase The base to format it. (2,8,10 or 16) */ char *kldrHlpInt2Ascii(char *psz, size_t cch, long lVal, unsigned iBase) { static const char s_szDigits[] = "0123456789abcdefghijklmnopqrstuvwxyz"; char *pszRet = psz; if (cch >= (lVal < 0 ? 3U : 2U) && psz) { /* prefix */ if (lVal < 0) { *psz++ = '-'; cch--; lVal = -lVal; } /* the digits */ do { *psz++ = s_szDigits[lVal % iBase]; cch--; lVal /= iBase; } while (lVal && cch > 1); /* overflow indicator */ if (lVal) psz[-1] = '+'; } else if (!pszRet) return pszRet; else if (cch < 1 || !pszRet) return pszRet; else *psz++ = '+'; *psz = '\0'; return pszRet; } /** * Writes a assert string with unix lineendings. * * @param pszMsg The string. */ static void kldrHlpAssertWrite(const char *pszMsg) { #if defined(__OS2__) || defined(__WIN__) /* * Line by line. */ ULONG cbWritten; const char *pszNl = kLdrHlpStrChr(pszMsg, '\n'); while (pszNl) { cbWritten = pszNl - pszMsg; #ifdef __OS2__ if (cbWritten) DosWrite((HFILE)2, pszMsg, cbWritten, &cbWritten); DosWrite((HFILE)2, "\r\n", 2, &cbWritten); #else /* __WIN32__ */ if (cbWritten) WriteFile((HANDLE)STD_ERROR_HANDLE, pszMsg, cbWritten, &cbWritten, NULL); WriteFile((HANDLE)STD_ERROR_HANDLE, "\r\n", 2, &cbWritten, NULL); #endif /* next */ pszMsg = pszNl + 1; pszNl = kLdrHlpStrChr(pszMsg, '\n'); } /* * Remaining incomplete line. */ if (*pszMsg) { cbWritten = kLdrHlpStrLen(pszMsg); #ifdef __OS2__ DosWrite((HFILE)2, pszMsg, cbWritten, &cbWritten); #else /* __WIN32__ */ WriteFile((HANDLE)STD_ERROR_HANDLE, pszMsg, cbWritten, &cbWritten, NULL); #endif } #else # error "port me" #endif } /** * Internal worker for the kLdrHlpAssert() macro. * * @param pszExpr The assert expression. * @param pszFile The filename. * @param iLine The line number. * @param pszFunction The function name. */ void kldrHlpAssertMsg(const char *pszExpr, const char *pszFile, unsigned iLine, const char *pszFunction) { char szLine[16]; kldrHlpAssertWrite("\n!!!kLdr Assertion Failed!!!\nExpression: "); kldrHlpAssertWrite(pszExpr); kldrHlpAssertWrite("\nAt: "); kldrHlpAssertWrite(pszFile); kldrHlpAssertWrite("("); kldrHlpAssertWrite(kldrHlpInt2Ascii(szLine, sizeof(szLine), iLine, 10)); kldrHlpAssertWrite(") "); kldrHlpAssertWrite(pszFunction); kldrHlpAssertWrite("\n"); } #ifdef kLdrHlpStrChr_needed char *kLdrHlpStrChr(const char *psz, int ch) { while (*psz) { if (*psz == ch) return (char *)psz; psz++; } return NULL; } #endif #ifdef kLdrHlpStrChr_needed void *kLdrHlpMemChr(const void *pv, int ch, size_t cb) { const uint8_t *pb = (const uint8_t *)pv; const uint8_t b = (uint8_t)ch; while (cb) { if (*pb == b) return (void *)pb; pb++; } return NULL; } #endif int kLdrHlpMemIComp(const void *pv1, const void *pv2, size_t cb) { const uint8_t *pb1 = (const uint8_t *)pv1; const uint8_t *pb2 = (const uint8_t *)pv2; while (cb) { if (*pb1 != *pb2) { const uint8_t u1 = *pb1 >= 'a' && *pb1 <= 'z' ? *pb1 - 'a' : *pb1; const uint8_t u2 = *pb2 >= 'a' && *pb2 <= 'z' ? *pb2 - 'a' : *pb2; if (u1 != u2) return (int)*pb1 - (int)*pb2; } pb1++; pb2++; } return 0; } int kLdrHlpStrIComp(const char *pv1, const char *pv2) { const uint8_t *pb1 = (const uint8_t *)pv1; const uint8_t *pb2 = (const uint8_t *)pv2; for (;;) { if (*pb1 != *pb2) { const uint8_t u1 = *pb1 >= 'a' && *pb1 <= 'z' ? *pb1 - 'a' : *pb1; const uint8_t u2 = *pb2 >= 'a' && *pb2 <= 'z' ? *pb2 - 'a' : *pb2; if (u1 != u2) return (int)*pb1 - (int)*pb2; } if (!*pb1) break; pb1++; pb2++; } return 0; }