/* $Id: $ */ /** @file * * kLdr - The Dynamic Loader, Dyld module methods. * * 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 * *******************************************************************************/ #include #include "kLdrInternal.h" #include "kLdrHlp.h" /******************************************************************************* * Defined Constants And Macros * *******************************************************************************/ /** @def KLDRDYLDMOD_ASSERT * Assert that an expression is true when KLDRDYLD_STRICT is defined. */ #ifdef KLDRDYLD_STRICT # define KLDRDYLDMOD_ASSERT(expr) kldrHlpAssert(expr) #else # define KLDRDYLDMOD_ASSERT(expr) do {} while (0) #endif /** * Decrement the dynamic load count of the module and unload the module * if the total reference count reaches zero. * * This may cause a cascade of unloading to occure. See kldrDyldModUnloadPrerequisites(). * * @returns status code. * @retval 0 on success. * @retval KLDR_ERR_NOT_LOADED_DYNAMICALLY if the module wasn't loaded dynamically. * @param pMod The module to unload. */ int kldrDyldModDynamicUnload(PKLDRDYLDMOD pMod) { if (pMod->cDynRefs == 0) return KLDR_ERR_NOT_LOADED_DYNAMICALLY; KLDRDYLDMOD_ASSERT(pMod->cDynRefs <= pMod->cRefs); KLDRDYLDMOD_ASSERT(pMod->enmState == KLDRSTATE_GOOD); pMod->cRefs--; pMod->cDynRefs--; if (pMod->cRefs > 0) return 0; /* * The module should be unloaded. */ kldrDyldModUnloadPrerequisites(pMod); return 0; } /** * Worker for kldrDyldModUnloadPrerequisites. * * @returns The number of modules that now can be unloaded. * @param pMod The module in question. */ static uint32_t kldrDyldModUnloadPrerequisitesOne(PKLDRDYLDMOD pMod) { PKLDRDYLDMOD pMod2; uint32_t cToUnload = 0; uint32_t i; KLDRDYLDMOD_ASSERT(pMod->papPrereqs); /* * Release the one in this module. */ for (i = 0; i < pMod->cPrereqs; i++) { pMod2 = pMod->papPrereqs[i]; if (pMod2) { /* do the derefering ourselves or we'll end up in a recursive loop here. */ KLDRDYLDMOD_ASSERT(pMod2->cDepRefs > 0); KLDRDYLDMOD_ASSERT(pMod2->cRefs >= pMod2->cDepRefs); pMod2->cDepRefs--; pMod2->cRefs--; cToUnload += !pMod2->cRefs; } } /* * Change the state */ switch (pMod->enmState) { case KLDRSTATE_LOADED_PREREQUISITES: case KLDRSTATE_FIXED_UP: pMod->enmState = KLDRSTATE_PENDING_DESTROY; kldrDyldModUnlink(pMod); break; case KLDRSTATE_PENDING_INITIALIZATION: pMod->enmState = KLDRSTATE_PENDING_GC; break; case KLDRSTATE_RELOADED_FIXED_UP: case KLDRSTATE_RELOADED_LOADED_PREREQUISITES: case KLDRSTATE_GOOD: pMod->enmState = KLDRSTATE_PENDING_TERMINATION; break; case KLDRSTATE_INITIALIZATION_FAILED: break; default: KLDRDYLDMOD_ASSERT(!"invalid state"); break; } return cToUnload; } /** * This is the heart of the unload code. * * It will recursivly (using the load list) initiate module unloading * of all affected modules. * * This function will cause a state transition ot PENDING_DESTROY, PENDING_GC * or PENDING_TERMINATION depending on the module state. There is one exception * to this, and that's INITIALIZATION_FAILED, where the state will not be changed. * * @param pMod The module which prerequisites should be unloaded. */ void kldrDyldModUnloadPrerequisites(PKLDRDYLDMOD pMod) { uint32_t cToUnload; /* sanity */ #ifdef KLDRDYLD_STRICT { PKLDRDYLDMOD pMod2; for (pMod2 = kLdrDyldHead; pMod2; pMod2 = pMod2->Load.pNext) KLDRDYLDMOD_ASSERT(pMod2->enmState != KLDRSTATE_GOOD || pMod2->cRefs); } #endif KLDRDYLDMOD_ASSERT(pMod->papPrereqs); /* * Unload prereqs of the module we're called on first. */ cToUnload = kldrDyldModUnloadPrerequisitesOne(pMod); /* * Iterate the load list in a cyclic manner until there are no more * modules that can be pushed on into unloading. */ while (cToUnload) { cToUnload = 0; for (pMod = kLdrDyldHead; pMod; pMod = pMod->Load.pNext) { if ( pMod->cRefs || pMod->enmState >= KLDRSTATE_PENDING_TERMINATION || pMod->enmState < KLDRSTATE_LOADED_PREREQUISITES) continue; cToUnload += kldrDyldModUnloadPrerequisitesOne(pMod); } } } void kldrDyldModDeref(PKLDRDYLDMOD pMod) { /* validate input */ KLDRDYLDMOD_ASSERT(pMod->enmState > KLDRSTATE_INVALID && pMod->enmState < KLDRSTATE_END); KLDRDYLDMOD_ASSERT(pMod->cRefs > 0); KLDRDYLDMOD_ASSERT(pMod->cRefs >= pMod->cDepRefs + pMod->cDynRefs); /* decrement. */ if (pMod->cRefs > 0) pMod->cRefs--; /* * Drop prerequisites for stats that implies that no recursion can have taken place yet. * This ASSUMES that we're only dereferencing modules when an operation completes. * (This is *required* for the reloaded state.) */ switch (pMod->enmState) { case KLDRSTATE_MAPPED: case KLDRSTATE_LOADED_PREREQUISITES: case KLDRSTATE_FIXED_UP: case KLDRSTATE_RELOADED: kldrDyldModUnloadPrerequisites(pMod); pMod->enmState = KLDRSTATE_PENDING_GC; break; case KLDRSTATE_PENDING_INITIALIZATION: case KLDRSTATE_INITIALIZING: case KLDRSTATE_GOOD: case KLDRSTATE_PENDING_TERMINATION: case KLDRSTATE_TERMINATING: case KLDRSTATE_PENDING_GC: case KLDRSTATE_GC: case KLDRSTATE_PENDING_DESTROY: break; default: KLDRDYLDMOD_ASSERT(!"Invalid deref state"); break; } /* * Also drop prerequisites if noone is referencing the module. */ if (!pMod->cDepRefs && !pMod->cDynRefs) { switch (pMod->enmState) { case KLDRSTATE_MAPPED: case KLDRSTATE_LOADED_PREREQUISITES: case KLDRSTATE_FIXED_UP: case KLDRSTATE_RELOADED: /* already dropped. */ break; case KLDRSTATE_PENDING_INITIALIZATION: kldrDyldModUnloadPrerequisites(pMod); pMod->enmState = KLDRSTATE_PENDING_GC; break; case KLDRSTATE_GOOD: pMod->enmState = KLDRSTATE_PENDING_TERMINATION; case KLDRSTATE_PENDING_TERMINATION: case KLDRSTATE_PENDING_GC: kldrDyldModUnloadPrerequisites(pMod); break; case KLDRSTATE_TERMINATING: case KLDRSTATE_GC: case KLDRSTATE_PENDING_DESTROY: break; default: KLDRDYLDMOD_ASSERT(!"Invalid deref state (b)"); break; } } /* * If there are no references whatsoever now and the module * is pending destruction, destroy it. */ if ( !pMod->cRefs && ( pMod->enmState == KLDRSTATE_PENDING_DESTROY || pMod->enmState == KLDRSTATE_GC)) kldrDyldModDestroy(pMod); }