diff --git a/mps/code/arena.c b/mps/code/arena.c index a3301521fae..b73e548c78d 100644 --- a/mps/code/arena.c +++ b/mps/code/arena.c @@ -82,6 +82,14 @@ static Res ArenaNoPagesMarkAllocated(Arena arena, Chunk chunk, return ResUNIMPL; } +static Bool ArenaNoChunkPageMapped(Chunk chunk, Index index) +{ + UNUSED(chunk); + UNUSED(index); + NOTREACHED; + return FALSE; +} + static Res ArenaNoCreate(Arena *arenaReturn, ArgList args) { UNUSED(arenaReturn); @@ -122,6 +130,7 @@ DEFINE_CLASS(Arena, AbstractArena, klass) klass->chunkFinish = ArenaNoChunkFinish; klass->compact = ArenaTrivCompact; klass->pagesMarkAllocated = ArenaNoPagesMarkAllocated; + klass->chunkPageMapped = ArenaNoChunkPageMapped; klass->sig = ArenaClassSig; } @@ -144,6 +153,7 @@ Bool ArenaClassCheck(ArenaClass klass) CHECKL(FUNCHECK(klass->chunkFinish)); CHECKL(FUNCHECK(klass->compact)); CHECKL(FUNCHECK(klass->pagesMarkAllocated)); + CHECKL(FUNCHECK(klass->chunkPageMapped)); CHECKS(ArenaClass, klass); return TRUE; } diff --git a/mps/code/arenacl.c b/mps/code/arenacl.c index 72e8c4af386..757b4c7326c 100644 --- a/mps/code/arenacl.c +++ b/mps/code/arenacl.c @@ -383,6 +383,20 @@ static Res ClientArenaPagesMarkAllocated(Arena arena, Chunk chunk, } +/* ClientChunkPageMapped -- determine if a page is mapped */ + +static Bool ClientChunkPageMapped(Chunk chunk, Index index) +{ + UNUSED(chunk); + UNUSED(index); + + AVERT(Chunk, chunk); + AVER(index < chunk->pages); + + return TRUE; +} + + /* ClientArenaFree - free a region in the arena */ static void ClientArenaFree(Addr base, Size size, Pool pool) @@ -443,6 +457,7 @@ DEFINE_CLASS(Arena, ClientArena, klass) klass->free = ClientArenaFree; klass->chunkInit = ClientChunkInit; klass->chunkFinish = ClientChunkFinish; + klass->chunkPageMapped = ClientChunkPageMapped; } diff --git a/mps/code/arenavm.c b/mps/code/arenavm.c index 0168b4b6ed8..78e2a67cbf4 100644 --- a/mps/code/arenavm.c +++ b/mps/code/arenavm.c @@ -932,6 +932,16 @@ static Res VMPagesMarkAllocated(Arena arena, Chunk chunk, } +static Bool VMChunkPageMapped(Chunk chunk, Index index) +{ + VMChunk vmChunk; + AVERT(Chunk, chunk); + AVER(index < chunk->pages); + vmChunk = Chunk2VMChunk(chunk); + return BTGet(vmChunk->pages.mapped, index); +} + + /* chunkUnmapAroundPage -- unmap spare pages in a chunk including this one * * Unmap the spare page passed, and possibly other pages in the chunk, @@ -1208,6 +1218,7 @@ DEFINE_CLASS(Arena, VMArena, klass) klass->chunkFinish = VMChunkFinish; klass->compact = VMCompact; klass->pagesMarkAllocated = VMPagesMarkAllocated; + klass->chunkPageMapped = VMChunkPageMapped; } diff --git a/mps/code/global.c b/mps/code/global.c index 02b2f04928e..369eda624bb 100644 --- a/mps/code/global.c +++ b/mps/code/global.c @@ -73,14 +73,14 @@ static void arenaAnnounce(Arena arena) } -/* arenaDenounce -- remove an arena from the global ring of arenas +/* ArenaDenounce -- remove an arena from the global ring of arenas * * After this, no other thread can access the arena through ArenaAccess. * On entry, the arena should be locked. On exit, it will still be, but * the lock has been released and reacquired in the meantime, so callers * should not assume anything about the state of the arena. */ -static void arenaDenounce(Arena arena) +void ArenaDenounce(Arena arena) { Globals arenaGlobals; @@ -403,7 +403,7 @@ void GlobalsPrepareToDestroy(Globals arenaGlobals) arena = GlobalsArena(arenaGlobals); - arenaDenounce(arena); + ArenaDenounce(arena); defaultChain = arenaGlobals->defaultChain; arenaGlobals->defaultChain = NULL; @@ -574,6 +574,12 @@ void ArenaLeaveRecursive(Arena arena) ArenaLeaveLock(arena, TRUE); } +Bool ArenaBusy(Arena arena) +{ + return LockIsHeld(ArenaGlobals(arena)->lock); +} + + /* mps_exception_info -- pointer to exception info * * This is a hack to make exception info easier to find in a release diff --git a/mps/code/lock.h b/mps/code/lock.h index 35d0ed6db41..1b421d9eb40 100644 --- a/mps/code/lock.h +++ b/mps/code/lock.h @@ -78,6 +78,11 @@ extern void LockRelease(Lock lock); extern Bool LockCheck(Lock lock); +/* LockIsHeld -- test whether lock is held */ + +extern Bool LockIsHeld(Lock lock); + + /* == Global locks == */ @@ -133,6 +138,7 @@ extern void LockReleaseGlobal(void); #define LockReleaseRecursive(lock) UNUSED(lock) #define LockClaim(lock) UNUSED(lock) #define LockRelease(lock) UNUSED(lock) +#define LockIsHeld(lock) ((void)lock, FALSE) #define LockCheck(lock) ((void)lock, TRUE) #define LockClaimGlobalRecursive() #define LockReleaseGlobalRecursive() diff --git a/mps/code/lockcov.c b/mps/code/lockcov.c index 84866046d82..af225d07c40 100644 --- a/mps/code/lockcov.c +++ b/mps/code/lockcov.c @@ -39,20 +39,28 @@ int main(int argc, char *argv[]) Insist(b != NULL); LockInit(a); + Insist(!LockIsHeld(a)); LockInit(b); + Insist(!LockIsHeld(b)); LockClaimGlobal(); LockClaim(a); + Insist(LockIsHeld(a)); LockClaimRecursive(b); + Insist(LockIsHeld(b)); LockClaimGlobalRecursive(); LockReleaseGlobal(); LockClaimGlobal(); LockRelease(a); + Insist(!LockIsHeld(a)); LockClaimGlobalRecursive(); LockReleaseGlobal(); LockClaimRecursive(b); + Insist(LockIsHeld(b)); LockFinish(a); LockReleaseRecursive(b); + Insist(LockIsHeld(b)); LockReleaseRecursive(b); + Insist(!LockIsHeld(b)); LockFinish(b); LockInit(a); LockClaim(a); diff --git a/mps/code/lockix.c b/mps/code/lockix.c index 5b166fdc625..30fecce488c 100644 --- a/mps/code/lockix.c +++ b/mps/code/lockix.c @@ -185,6 +185,21 @@ void (LockReleaseRecursive)(Lock lock) } +/* LockIsHeld -- test whether lock is held */ + +Bool (LockIsHeld)(Lock lock) +{ + AVERT(Lock, lock); + if (pthread_mutex_trylock(&lock->mut) == 0) { + Bool claimed = lock->claims > 0; + int res = pthread_mutex_unlock(&lock->mut); + AVER(res == 0); + return claimed; + } + return TRUE; +} + + /* Global locks * * .global: The two "global" locks are statically allocated normal locks. diff --git a/mps/code/lockw3.c b/mps/code/lockw3.c index daf2473d4e3..09dea1286fb 100644 --- a/mps/code/lockw3.c +++ b/mps/code/lockw3.c @@ -103,6 +103,15 @@ void (LockReleaseRecursive)(Lock lock) LeaveCriticalSection(&lock->cs); } +Bool (LockIsHeld)(Lock lock) +{ + if (TryEnterCriticalSection(&lock->cs)) { + Bool claimed = lock->claims > 0; + LeaveCriticalSection(&lock->cs); + return claimed; + } + return TRUE; +} /* Global locking is performed by normal locks. diff --git a/mps/code/mpm.h b/mps/code/mpm.h index 9f5f3365fa4..013d467843a 100644 --- a/mps/code/mpm.h +++ b/mps/code/mpm.h @@ -487,7 +487,7 @@ extern Res ArenaDescribeTracts(Arena arena, mps_lib_FILE *stream, Count depth); extern Bool ArenaAccess(Addr addr, AccessSet mode, MutatorFaultContext context); extern Res ArenaFreeLandInsert(Arena arena, Addr base, Addr limit); extern void ArenaFreeLandDelete(Arena arena, Addr base, Addr limit); - +extern void ArenaDenounce(Arena arena); extern Bool GlobalsCheck(Globals arena); extern Res GlobalsInit(Globals arena); @@ -546,10 +546,12 @@ extern Bool (ArenaStep)(Globals globals, double interval, double multiplier); extern void ArenaClamp(Globals globals); extern void ArenaRelease(Globals globals); extern void ArenaPark(Globals globals); +extern void ArenaPostmortem(Globals globals); extern void ArenaExposeRemember(Globals globals, Bool remember); extern void ArenaRestoreProtection(Globals globals); extern Res ArenaStartCollect(Globals globals, int why); extern Res ArenaCollect(Globals globals, int why); +extern Bool ArenaBusy(Arena arena); extern Bool ArenaHasAddr(Arena arena, Addr addr); extern Res ArenaAddrObject(Addr *pReturn, Arena arena, Addr addr); extern void ArenaChunkInsert(Arena arena, Chunk chunk); diff --git a/mps/code/mpmst.h b/mps/code/mpmst.h index 04c19f63e92..dd0ecd077fc 100644 --- a/mps/code/mpmst.h +++ b/mps/code/mpmst.h @@ -504,6 +504,7 @@ typedef struct mps_arena_class_s { ArenaChunkFinishMethod chunkFinish; ArenaCompactMethod compact; ArenaPagesMarkAllocatedMethod pagesMarkAllocated; + ArenaChunkPageMappedMethod chunkPageMapped; Sig sig; } ArenaClassStruct; diff --git a/mps/code/mpmtypes.h b/mps/code/mpmtypes.h index d1180ea9a8d..5d6ce41ff08 100644 --- a/mps/code/mpmtypes.h +++ b/mps/code/mpmtypes.h @@ -124,6 +124,7 @@ typedef void (*ArenaCompactMethod)(Arena arena, Trace trace); typedef Res (*ArenaPagesMarkAllocatedMethod)(Arena arena, Chunk chunk, Index baseIndex, Count pages, Pool pool); +typedef Bool (*ArenaChunkPageMappedMethod)(Chunk chunk, Index index); /* These are not generally exposed and public, but are part of a commercial diff --git a/mps/code/mps.h b/mps/code/mps.h index 6aeffd2962d..c9ecab4cfae 100644 --- a/mps/code/mps.h +++ b/mps/code/mps.h @@ -435,6 +435,7 @@ typedef struct mps_fmt_fixed_s { extern void mps_arena_clamp(mps_arena_t); extern void mps_arena_release(mps_arena_t); extern void mps_arena_park(mps_arena_t); +extern void mps_arena_postmortem(mps_arena_t); extern void mps_arena_expose(mps_arena_t); extern void mps_arena_unsafe_expose_remember_protection(mps_arena_t); extern void mps_arena_unsafe_restore_protection(mps_arena_t); @@ -460,6 +461,7 @@ extern size_t mps_arena_spare_commit_limit(mps_arena_t); extern double mps_arena_pause_time(mps_arena_t); extern void mps_arena_pause_time_set(mps_arena_t, double); +extern mps_bool_t mps_arena_busy(mps_arena_t); extern mps_bool_t mps_arena_has_addr(mps_arena_t, mps_addr_t); extern mps_bool_t mps_addr_pool(mps_pool_t *, mps_arena_t, mps_addr_t); extern mps_bool_t mps_addr_fmt(mps_fmt_t *, mps_arena_t, mps_addr_t); diff --git a/mps/code/mpsi.c b/mps/code/mpsi.c index 9ae6fcbfa97..5bb6e7fa860 100644 --- a/mps/code/mpsi.c +++ b/mps/code/mpsi.c @@ -260,6 +260,14 @@ void mps_arena_park(mps_arena_t arena) } +void mps_arena_postmortem(mps_arena_t arena) +{ + /* Don't call ArenaEnter -- one of the purposes of this function is + * to release the arena lock if it's held */ + ArenaPostmortem(ArenaGlobals(arena)); +} + + void mps_arena_expose(mps_arena_t arena) { ArenaEnter(arena); @@ -374,6 +382,14 @@ void mps_arena_destroy(mps_arena_t arena) } +/* mps_arena_busy -- is the arena part way through an operation? */ + +mps_bool_t mps_arena_busy(mps_arena_t arena) +{ + return ArenaBusy(arena); +} + + /* mps_arena_has_addr -- is this address managed by this arena? */ mps_bool_t mps_arena_has_addr(mps_arena_t arena, mps_addr_t p) diff --git a/mps/code/mpsicv.c b/mps/code/mpsicv.c index d1a9759a3b8..0df2c4bc0c7 100644 --- a/mps/code/mpsicv.c +++ b/mps/code/mpsicv.c @@ -450,11 +450,13 @@ static void *test(void *arg, size_t s) mps_word_t c; size_t r; + Insist(!mps_arena_busy(arena)); + c = mps_collections(arena); if(collections != c) { collections = c; - printf("\nCollection %"PRIuLONGEST", %lu objects.\n", (ulongest_t)c, i); + printf("Collection %"PRIuLONGEST", %lu objects.\n", (ulongest_t)c, i); for(r = 0; r < exactRootsCOUNT; ++r) { cdie(exactRoots[r] == objNULL || dylan_check(exactRoots[r]), "all roots check"); @@ -565,7 +567,7 @@ int main(int argc, char *argv[]) MPS_ARGS_ADD(args, MPS_KEY_PAUSE_TIME, rnd_pause_time()); MPS_ARGS_ADD(args, MPS_KEY_ARENA_SIZE, TEST_ARENA_SIZE); die(mps_arena_create_k(&arena, mps_arena_class_vm(), args), - "arena_create\n"); + "arena_create"); } MPS_ARGS_END(args); die(mps_thread_reg(&thread, arena), "thread_reg"); @@ -591,9 +593,17 @@ int main(int argc, char *argv[]) } mps_tramp(&r, test, arena, 0); - mps_root_destroy(reg_root); - mps_thread_dereg(thread); - mps_arena_destroy(arena); + switch (rnd() % 2) { + default: + case 0: + mps_root_destroy(reg_root); + mps_thread_dereg(thread); + mps_arena_destroy(arena); + break; + case 1: + mps_arena_postmortem(arena); + break; + } printf("%s: Conclusion: Failed to find any defects.\n", argv[0]); return 0; diff --git a/mps/code/qs.c b/mps/code/qs.c index 2a62a5ef71a..9e3089b3a89 100644 --- a/mps/code/qs.c +++ b/mps/code/qs.c @@ -457,6 +457,7 @@ static mps_res_t scan1(mps_ss_t ss, mps_addr_t *objectIO) static mps_res_t scan(mps_ss_t ss, mps_addr_t base, mps_addr_t limit) { + Insist(mps_arena_busy(arena)); while(base < limit) { mps_res_t res; diff --git a/mps/code/tagtest.c b/mps/code/tagtest.c index 62df8ee2b1d..9b3dea53c60 100644 --- a/mps/code/tagtest.c +++ b/mps/code/tagtest.c @@ -77,9 +77,12 @@ static void pad(mps_addr_t addr, size_t size) } } +static mps_arena_t arena; + static mps_res_t scan(mps_ss_t ss, mps_addr_t base, mps_addr_t limit) { + Insist(mps_arena_busy(arena)); MPS_SCAN_BEGIN(ss) { mps_word_t *p = base; while (p < (mps_word_t *)limit) { @@ -106,7 +109,7 @@ static mps_addr_t skip(mps_addr_t addr) } -static void collect(mps_arena_t arena, size_t expected) +static void collect(size_t expected) { size_t finalized = 0; mps_arena_collect(arena); @@ -148,7 +151,6 @@ static const char *mode_name[] = { static void test(int mode) { - mps_arena_t arena; mps_thr_t thread; mps_root_t root; mps_fmt_t fmt; @@ -214,7 +216,7 @@ static void test(int mode) die(mps_finalize(arena, &addr), "finalize"); } - collect(arena, expected); + collect(expected); mps_arena_park(arena); mps_ap_destroy(ap); diff --git a/mps/code/traceanc.c b/mps/code/traceanc.c index 52761e02092..dabbd232b8d 100644 --- a/mps/code/traceanc.c +++ b/mps/code/traceanc.c @@ -530,12 +530,11 @@ void TraceIdMessagesDestroy(Arena arena, TraceId ti) -/* -------- ArenaRelease, ArenaClamp, ArenaPark -------- */ +/* ----- ArenaRelease, ArenaClamp, ArenaPark, ArenaPostmortem ----- */ -/* ArenaRelease, ArenaClamp, ArenaPark -- allow/prevent collection work. - * - * These functions allow or prevent collection work. +/* ArenaRelease, ArenaClamp, ArenaPark, ArenaPostmortem -- + * allow/prevent collection work. */ @@ -596,6 +595,61 @@ void ArenaPark(Globals globals) AVER(!ArenaEmergency(arena)); } + +/* arenaExpose -- discard all protection from MPS-managed memory + * + * This is called by ArenaPostmortem, which we expect only to be used + * after a fatal error. So we use the lowest-level description of the + * MPS-managed memory (the chunk ring page tables) to avoid the risk + * of the higher-level structurs (like the segments) having been + * corrupted. + */ + +static void arenaExpose(Arena arena) +{ + Ring node, next; + RING_FOR(node, &arena->chunkRing, next) { + Chunk chunk = RING_ELT(Chunk, arenaRing, node); + Index i; + for (i = 0; i < chunk->pages; ++i) { + if (Method(Arena, arena, chunkPageMapped)(chunk, i)) { + ProtSet(PageIndexBase(chunk, i), PageIndexBase(chunk, i + 1), + AccessSetEMPTY); + } + } + } +} + + +/* ArenaPostmortem -- enter the postmortem state */ + +void ArenaPostmortem(Globals globals) +{ + Arena arena = GlobalsArena(globals); + + /* Ensure lock is releases. */ + while (LockIsHeld(globals->lock)) { + LockReleaseRecursive(globals->lock); + } + + /* Acquire the lock again so that we can call ArenaDenounce. */ + ArenaEnter(arena); + + /* Remove the arena from the global arena ring so that it no longer + * handles protection faults. */ + ArenaDenounce(arena); + + /* Clamp the arena so that ArenaPoll does nothing. */ + ArenaClamp(globals); + + /* Remove all protection from mapped pages. */ + arenaExpose(arena); + + /* Release the lock finally. */ + ArenaLeave(arena); +} + + /* ArenaStartCollect -- start a collection of everything in the * arena; leave unclamped. */ @@ -775,7 +829,6 @@ void ArenaRestoreProtection(Globals globals) } } } - arenaForgetProtection(globals); } diff --git a/mps/design/lock.txt b/mps/design/lock.txt index 6dc4e55de15..4ce41baba4c 100644 --- a/mps/design/lock.txt +++ b/mps/design/lock.txt @@ -59,6 +59,11 @@ be claimed again by the thread currently holding them, without blocking or deadlocking. (This is needed to implement the global recursive lock.) +_`.req.held`: Provide a means to test if a lock is held. (This is +needed for debugging a dynamic function table callback on Windows on +x86-64. See ``mps_arena_busy()`` for a detailed description of this +use case.) + _`.req.global`: Provide *global* locks: that is locks that need not be allocated or initialized by the user. @@ -124,6 +129,10 @@ thread and claims the lock (if not already held). Restores the previous state of the lock remembered by the corresponding ``LockClaimRecursive()`` call. +``Bool LockIsHeld(Lock lock)`` + +Return true if the lock is held, false otherwise. + ``void LockClaimGlobal(void)`` Claims ownership of the binary global lock which was previously not diff --git a/mps/manual/source/glossary/c.rst b/mps/manual/source/glossary/c.rst index 2165bac2394..478276e619b 100644 --- a/mps/manual/source/glossary/c.rst +++ b/mps/manual/source/glossary/c.rst @@ -193,14 +193,14 @@ Memory Management Glossary: C .. mps:specific:: - One of the three states an :term:`arena` can be in (the - others being the :term:`unclamped state` and the - :term:`parked state`). In the clamped state, no object - motion occurs and the staleness of :term:`location - dependencies` does not change. However, a :term:`garbage - collection` may be in progress. Call - :c:func:`mps_arena_clamp` to put an arena into the clamped - state. + One of the four states an :term:`arena` can be in (the + others being the :term:`unclamped state`, the + :term:`parked state`, and the :term:`postmortem state`). + In the clamped state, no object motion occurs and the + staleness of :term:`location dependencies` does not + change. However, a :term:`garbage collection` may be in + progress. Call :c:func:`mps_arena_clamp` to put an arena + into the clamped state. client arena diff --git a/mps/manual/source/glossary/p.rst b/mps/manual/source/glossary/p.rst index 4ca1dbcd275..b15da7171d4 100644 --- a/mps/manual/source/glossary/p.rst +++ b/mps/manual/source/glossary/p.rst @@ -402,6 +402,16 @@ Memory Management Glossary: P class of :term:`pools` that manage memory according to particular policy. See :ref:`pool`. + postmortem state + + .. mps:specific:: + + One of the four states an :term:`arena` can be in (the + others being the :term:`unclamped state`, the + :term:`clamped state`, and the :term:`parked state`). In the postmortem state, + + + precise garbage collection .. see:: :term:`exact garbage collection`. diff --git a/mps/manual/source/guide/debug.rst b/mps/manual/source/guide/debug.rst index 3c1678e660b..cd95ef253ab 100644 --- a/mps/manual/source/guide/debug.rst +++ b/mps/manual/source/guide/debug.rst @@ -94,6 +94,31 @@ General debugging advice On OS X, barrier hits do not use signals and so do not enter the debugger. +#. .. index:: + single: postmortem debugging + single: postmortem state + + If the :term:`client program` is stopped in the debugger with the + MPS part of the way through execution of an operation in an + :term:`arena` (for example, a crash inside a :term:`scan method`), + it will not be possible to call introspection functions, such as + :c:func:`mps_arena_has_addr` or :c:func:`mps_addr_pool` (because + the MPS is not re-entrant), and it may not be possible to examine + some regions of memory (because they are :term:`protected` by the + MPS). + + If you are in this situation and would like to be able to call MPS + functions or examine regions of memory from the debugger, then you + can put the arena into the :term:`postmortem state` by calling + :c:func:`mps_arena_postmortem` from the debugger. This unlocks the + arena and turns off protection. + + .. warning:: + + After calling :c:func:`mps_arena_postmortem`, MPS-managed + memory is not in a consistent state, and so it is no longer + safe to continue running the client program. + .. index:: single: ASLR diff --git a/mps/manual/source/release.rst b/mps/manual/source/release.rst index 5f73f0ad5b7..831f123cc36 100644 --- a/mps/manual/source/release.rst +++ b/mps/manual/source/release.rst @@ -25,6 +25,12 @@ New features .. _LinuxThreads: http://pauillac.inria.fr/~xleroy/linuxthreads/ +#. New function :c:func:`mps_arena_postmortem` assists with postmortem + debugging. + +#. New function :c:func:`mps_arena_busy` assists debugging of re-entry + errors in dynamic function table callbacks on Windows on x86-64. + Interface changes ................. diff --git a/mps/manual/source/topic/arena.rst b/mps/manual/source/topic/arena.rst index f1386781572..4fce7a968bb 100644 --- a/mps/manual/source/topic/arena.rst +++ b/mps/manual/source/topic/arena.rst @@ -649,21 +649,42 @@ An arena is always in one of three states. The *parked state* is the same as the clamped state, with the additional constraint that no garbage collections are in progress. +#. .. index:: + single: arena; postmortem state + single: postmortem state + + In the *postmortem state*, incremental collection does not take + place, objects do not move in memory, references do not change, the + staleness of :term:`location dependencies` does not change, and + memory occupied by :term:`unreachable` objects is not recycled. + Additionally, all memory protection is removed, and memory may be + in an inconsistent state. + + .. warning:: + + In this state, memory managed by the arena is not in a + consistent state, and so it is no longer safe to continue + running the client program. This state is intended for + postmortem debugging only. + + Here's a summary: -============================================ ================================== ============================= =========================== -State unclamped clamped parked -============================================ ================================== ============================= =========================== -Collections may be running? yes yes no -New collections may start? yes no no -Objects may move? yes no no -Location dependencies may become stale? yes no no -Memory may be returned to the OS? yes no no -Functions that leave the arena in this state :c:func:`mps_arena_create_k`, :c:func:`mps_arena_clamp`, :c:func:`mps_arena_park`, +============================================ ================================== ============================= =========================== ============================== +State unclamped clamped parked postmortem +============================================ ================================== ============================= =========================== ============================== +Collections may be running? yes yes no yes +New collections may start? yes no no no +Objects may move? yes no no no +Location dependencies may become stale? yes no no no +Memory may be returned to the OS? yes no no no +Memory protection may be applied? yes yes yes no +Safe to continue running? yes yes yes no +Functions that leave the arena in this state :c:func:`mps_arena_create_k`, :c:func:`mps_arena_clamp`, :c:func:`mps_arena_park`, :c:func:`mps_arena_postmortem` :c:func:`mps_arena_release`, :c:func:`mps_arena_step` :c:func:`mps_arena_collect` - :c:func:`mps_arena_start_collect`, - :c:func:`mps_arena_step` -============================================ ================================== ============================= =========================== + :c:func:`mps_arena_start_collect`, + :c:func:`mps_arena_step` +============================================ ================================== ============================= =========================== ============================== The clamped and parked states are important when introspecting and debugging. If you are examining the contents of the heap, you don't @@ -688,7 +709,7 @@ can only be called in this state. Put an :term:`arena` into the :term:`clamped state`. - ``arena`` is the arena to clamp. + ``arena`` is the arena. In the clamped state, no object motion will occur and the staleness of :term:`location dependencies` will not change. All @@ -706,7 +727,7 @@ can only be called in this state. Put an :term:`arena` into the :term:`parked state`. - ``arena`` is the arena to park. + ``arena`` is the arena. While an arena is parked, no object motion will occur and the staleness of :term:`location dependencies` will not change. All @@ -722,12 +743,33 @@ can only be called in this state. Puts an arena into the :term:`unclamped state`. - ``arena`` is the arena to unclamp. + ``arena`` is the arena. While an arena is unclamped, :term:`garbage collection`, object motion, and other background activity can take place. +.. c:function:: void mps_arena_postmortem(mps_arena_t arena) + + Puts an arena into the :term:`postmortem state`. + + ``arena`` is the arena. + + In the postmortem state, incremental collection does not take + place, objects do not move in memory, references do not change, + the staleness of :term:`location dependencies` does not change, + and memory occupied by :term:`unreachable` objects is not + recycled. Additionally, all memory protection is removed, and + memory may be in an inconsistent state. + + .. warning:: + + After calling this function, memory managed by the arena is not + in a consistent state, and so it is no longer safe to continue + running the client program. This functions is intended for + postmortem debugging only. + + .. index:: single: garbage collection; running single: collection; running @@ -885,9 +927,10 @@ application. .. index:: pair: arena; introspection + pair: arena; debugging -Arena introspection -------------------- +Arena introspection and debugging +--------------------------------- .. note:: @@ -903,6 +946,44 @@ Arena introspection address belongs. +.. c:function:: mps_bool_t mps_arena_busy(mps_arena_t arena) + + Return true if an :term:`arena` is part of the way through + execution of an operation, false otherwise. + + ``arena`` is the arena. + + .. note:: + + This function is intended to assist with debugging fatal + errors in the :term:`client program`. It is not expected to be + needed in normal use. If you find yourself wanting to use this + function other than in the use case described below, there may + be a better way to meet your requirements: please + :ref:`contact us `. + + A debugger running on Windows on x86-64 needs to decode the + call stack, which it does by calling a callback that was + previously installed in the dynamic function table using + |RtlInstallFunctionTableCallback|_. If the debugger is entered + while the arena is busy, and if the callback needs to read + from MPS-managed memory, then it may attempt to re-enter the + MPS, which will fail as the MPS is not re-entrant. + + .. |RtlInstallFunctionTableCallback| replace:: ``RtlInstallFunctionTableCallback()`` + .. _RtlInstallFunctionTableCallback: https://msdn.microsoft.com/en-us/library/windows/desktop/ms680595(v=vs.85).aspx + + If this happens, in order to allow the debugger to finish + decoding the call stack, the only remedy is to put the arena + into the :term:`postmortem state`, so that memory is + :term:`unprotected` and objects do not move. So in your + dynamic function table callback, you might write:: + + if (mps_arena_busy(arena)) { + mps_arena_postmortem(arena); + } + + .. c:function:: mps_bool_t mps_arena_has_addr(mps_arena_t arena, mps_addr_t addr) Test whether an :term:`address` is managed by an :term:`arena`.