Closed Bug 956324 Opened 11 years ago Closed 11 years ago

GC: Debugger can hold pointers to dead breakpoint handler objects

Categories

(Core :: JavaScript Engine, defect)

x86_64
Linux
defect
Not set
normal

Tracking

()

RESOLVED FIXED
mozilla29

People

(Reporter: jonco, Assigned: jonco)

References

Details

Attachments

(1 file, 1 obsolete file)

The debugger mochitests are failing like this: 09:39:59 WARNING - PROCESS-CRASH | chrome://mochitests/content/browser/browser/devtools/debugger/test/browser_dbg_pretty-print-09.js | application crashed [@ js::gc::Cell::tenuredZone() const] 09:39:59 INFO - Crash dump filename: /tmp/tmp2fDLYU/minidumps/7dccfba4-bb16-0879-3e50a54d-58b5c9cb.dmp 09:39:59 INFO - Operating system: Linux 09:39:59 INFO - 0.0.0 Linux 2.6.31.5-127.fc12.i686.PAE #1 SMP Sat Nov 7 21:25:57 EST 2009 i686 09:39:59 INFO - CPU: x86 09:39:59 INFO - GenuineIntel family 6 model 23 stepping 10 09:39:59 INFO - 2 CPUs 09:39:59 INFO - Crash reason: SIGSEGV 09:39:59 INFO - Crash address: 0xdadffffc 09:39:59 INFO - Thread 0 (crashed) 09:39:59 INFO - 0 libxul.so!js::gc::Cell::tenuredZone() const [HeapAPI.h:8d11330faa7e : 133 + 0x0] 09:39:59 INFO - eip = 0x02e546b3 esp = 0xbfb95d10 ebp = 0xbfb95d48 ebx = 0x0500964c 09:39:59 INFO - esi = 0xdadadada edi = 0xdadffffc eax = 0xdadadada ecx = 0x03bd8129 09:39:59 INFO - edx = 0x13af3328 efl = 0x00210286 09:39:59 INFO - Found by: given as instruction pointer in context 09:39:59 INFO - 1 libxul.so!js::gc::BarrieredCell<js::ObjectImpl>::zone() const [Barrier.h:8d11330faa7e : 185 + 0xc] 09:39:59 INFO - eip = 0x02ebd004 esp = 0xbfb95d50 ebp = 0xbfb95d68 ebx = 0x0500964c 09:39:59 INFO - esi = 0xbfb95e94 edi = 0x8ce16790 09:39:59 INFO - Found by: call frame info 09:39:59 INFO - 2 libxul.so!MarkInternal<JSObject> [Marking.cpp:8d11330faa7e : 133 + 0x7] 09:39:59 INFO - eip = 0x02ec4b88 esp = 0xbfb95d70 ebp = 0xbfb95da8 ebx = 0x0500964c 09:39:59 INFO - esi = 0xbfb95e94 edi = 0x8ce16790 09:39:59 INFO - Found by: call frame info 09:39:59 INFO - 3 libxul.so!js::Debugger::markAll(JSTracer*) [Debugger.cpp:8d11330faa7e : 1576 + 0x15] 09:39:59 INFO - eip = 0x031f831b esp = 0xbfb95db0 ebp = 0xbfb95e48 ebx = 0x0500964c 09:39:59 INFO - esi = 0xbfb95e94 edi = 0x13af3320 09:39:59 INFO - Found by: call frame info 09:39:59 INFO - 4 libxul.so!js::Nursery::collect(JSRuntime*, JS::gcreason::Reason, js::Vector<js::types::TypeObject*, 0u, js::SystemAllocPolicy>*) [Nursery.cpp:8d11330faa7e : 633 + 0x7] 09:39:59 INFO - eip = 0x02ed3322 esp = 0xbfb95e50 ebp = 0xbfb95f78 ebx = 0x0500964c 09:39:59 INFO - esi = 0xbfb95e94 edi = 0x08c273e0 09:39:59 INFO - Found by: call frame info 09:39:59 INFO - 5 libxul.so!js::MinorGC(JSRuntime*, JS::gcreason::Reason) [jsgc.cpp:8d11330faa7e : 5030 + 0x1b] 09:39:59 INFO - eip = 0x030e3587 esp = 0xbfb95f80 ebp = 0xbfb95f98 ebx = 0x0500964c 09:39:59 INFO - esi = 0xbfb96190 edi = 0xbfb96198 09:39:59 INFO - Found by: call frame info 09:39:59 INFO - 6 libxul.so!js::IterateScripts(JSRuntime*, JSCompartment*, void*, void (*)(JSRuntime*, void*, JSScript*)) [Iteration.cpp:8d11330faa7e : 95 + 0xe] 09:39:59 INFO - eip = 0x02eb67db esp = 0xbfb95fa0 ebp = 0xbfb96068 ebx = 0x0500964c 09:39:59 INFO - esi = 0xbfb96190 edi = 0xbfb96198 09:39:59 INFO - Found by: call frame info 09:39:59 INFO - 7 libxul.so!js::Debugger::ScriptQuery::findScripts(JS::AutoScriptVector*) [Debugger.cpp:8d11330faa7e : 2448 + 0x9] It's not always the same test, but it's the same backtrace. Debugger::findScripts() is causing a minor GC, and this encounters dead objects when iterating breakpoints in Debugger::markAll().
More often test browser/devtools/debugger/test/browser_dbg_source-maps-02.js crashes like this. It's reproducable locally but only by running the test in a loop for ~20mins. The problem is that a breakpoint's handler object has been collected, but the breakpoint is still alive. This should not be possible as Debugger::markAllIncrementally() marks the handler object if the breakpoint is alive.
Debugger::markAllIteratively() marks breakpoint handler objects, but it finds them via their debuggees. This has the consequence that if a debugger has all of its debuggees removed while it still has breakpoints, those breakpoints are not marked and the possibility arises that the handler object can be collected while the debugger still has a pointer to it. This is actually not a GGC specific problem, it's just that the list of breakpoints is rarely traversed. The markAll() function called at the start of minor GC does this though. I don't know what the semantics of the debugger are meant to be in this case, but a possible fix is to remove all of a debugger's breakpoints when the last of its debuggees is removed. Jim does this sound like a good way to proceed?
Flags: needinfo?(jimb)
Summary: GenerationalGC: Debugger mochitests crash on dead object in markAll() → GC: Debugger can hold pointers to dead breakpoint handler objects
Attached patch debugger-crash (obsolete) — Splinter Review
Potential fix.
Comment on attachment 8359133 [details] [diff] [review] debugger-crash Jim doesn't seem to be around, so assigning this to you for review. Feel free to r- if we actually don't want this behaviour.
Attachment #8359133 - Flags: review?(wmccloskey)
Flags: needinfo?(jimb)
Nice find. I'd love to give you an r+ on this. I suspect it's been causing intermittent oranges for a long time. I don't know enough to say whether the fix is correct, though. It seems possible that we want this behavior: add breakpoint, remove global from debugger, add it back, expect the breakpoint is still there. I asked Jason whether this is desirable and he said to ask Jim :-). However, if it *is* safe to remove breakpoints when removing a debuggee global, I think we should remove them unconditionally (not just if the debugger no longer has any more globals). Otherwise the semantics would just be weird. An alternate approach would be to simplify markAllIteratively. It's really complex, and I'm not sure why the complexity is needed. It looks like we have a list of all debuggers in the runtime (JSRuntime::debuggerList). Why don't we just iterate over that to find all the debuggers? Then we wouldn't have to worry about missing debuggers without any debugees. I no longer remember why we did it the current way. I think it had something to do with compartment GCs, but that really doesn't make any sense right now. I think a full traversal of all debuggers would be a lot faster than what we're doing now (going through all compartments).
Attachment #8359133 - Flags: review?(wmccloskey) → review?(jimb)
In our tools, we never actually remove debuggees (it seems). So forgetting a debuggee's breakpoints when it is removed as a debuggee won't be a compatibility issue. I'll take a look at the patch.
Comment on attachment 8359133 [details] [diff] [review] debugger-crash Review of attachment 8359133 [details] [diff] [review]: ----------------------------------------------------------------- ::: js/src/gc/Marking.cpp @@ +154,5 @@ > JS_ASSERT_IF(IS_GC_MARKING_TRACER(trc) && AsGCMarker(trc)->getMarkColor() == GRAY, > !thing->zone()->isGCMarkingBlack() || rt->isAtomsZone(thing->zone())); > > + JS_ASSERT_IF(IS_GC_MARKING_TRACER(trc), > + !(thing->zone()->isGCSweeping() || thing->zone()->isGCFinished())); This is just saying, "If we're marking with this tracer, then we certainly shouldn't be sweeping, and we shouldn't be marked as done with the GC", right? ::: js/src/vm/Debugger.cpp @@ +2316,5 @@ > + for (Breakpoint *bp = firstBreakpoint(); bp; bp = nextbp) { > + nextbp = bp->nextInDebugger(); > + bp->destroy(fop); > + } > + } Could we change this to instead always drop breakpoints in the removed debuggee? That seems easier to reason about than having special behavior when the last debuggee is removed. ("My breakpoints will persist, unless at some intervening time the debuggee count dropped to zero" is not very friendly.)
Attachment #8359133 - Flags: review?(jimb)
(In reply to Jim Blandy :jimb from comment #7) > > + JS_ASSERT_IF(IS_GC_MARKING_TRACER(trc), > > + !(thing->zone()->isGCSweeping() || thing->zone()->isGCFinished())); This is for incremental sweeping, to say that we're not trying to mark something in a zone that we already finished marking in. That would mean we might have already swept something that turned out to be reachable after all. We sweep zones in groups and calculate the order such that this should be impossible. > Could we change this to instead always drop breakpoints in the removed > debuggee? That seems easier to reason about than having special behavior > when the last debuggee is removed. ("My breakpoints will persist, unless at > some intervening time the debuggee count dropped to zero" is not very > friendly.) That sounds good to me.
Updated patch.
Attachment #8359133 - Attachment is obsolete: true
Attachment #8361138 - Flags: review?(jimb)
Comment on attachment 8361138 [details] [diff] [review] debugger-crash-v2 Jim doesn't seem to be responding. Bill could you review? As above, Jim says we do want to drop all breakpoints for a debuggee when that debuggee is removed.
Attachment #8361138 - Flags: review?(jimb) → review?(wmccloskey)
Attachment #8361138 - Flags: review?(wmccloskey) → review+
Status: NEW → RESOLVED
Closed: 11 years ago
Flags: in-testsuite+
Resolution: --- → FIXED
Target Milestone: --- → mozilla29
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Created:
Updated:
Size: