New issue
Advanced search Search tips
Note: Color blocks (like or ) mean that a user may not be available. Tooltip shows the reason.

Issue 719679 link

Starred by 2 users

Issue metadata

Status: Untriaged
Owner: ----
Cc:
Components:
EstimatedDays: ----
NextAction: ----
OS: All
Pri: 2
Type: Bug



Sign in to add a comment

Enable stack tracing to jump past v8

Project Member Reported by dcheng@chromium.org, May 8 2017

Issue description

I was debugging something recently, and found myself wishing that base::debug::StackTrace could skip past v8 on non-x86 platforms.

I started hacking something together with libunwind. I'm told that using libunwind directly isn't feasible, as libunwind will give up as soon as it finds a stack frame it doesn't understand. Instead, I'm trying to forge an unw_context_t, using the frame pointer / stack pointer / pc from v8::internal::Isolate::Current()->thread_local_top()->js_entry_sp_, etc. This would be highly-platform specific and not very portable (as this stack trace helper would be implicitly tied to v8).

It's been pointed out that this might be a generally useful thing to be able to do. A number of other suggestions from that discussion:
- register a custom dwarf entry for exception handling
- generalize the lookaside stack currently used for JNI: https://codereview.chromium.org/2361353002
- integrate with gdb's jit debugging infrastructure.
 
Cc: haraken@chromium.org
Labels: OS-All
Cc: jkummerow@chromium.org
Jakob has a patch that scans the stack skipping JS frames. Maybe you could take some inspiration from that?
FWIW dskiba added a stack-scanner to the base::debug::BackTrace not too long ago.
Just a thought about that. Pure stack scanning without extra context has some pathological false positives (regardless of v8), e.g.:

Imagine that a callstack looks like this:

-+> main()
 |-+> small_function_1()
 | |-> small_function_2()
 |
 |-+> large_function()
   |--> v8::Execute()

where say small_function_X() have frames of 16 bytes each, and large_function has a larger stack frame (say 64 bytes).
When you enter large_function, it will push the stack pointer by -64 bytes and then call v8 execute (again, v8 execute is irrelevant here)
The problem is that while doing that, it will _not_ clear up its current stack frame (say that the code that would have used the locals happens after v8::Execute() )
so the currently uninitialized stack frame of large_function() will still have the pointer from the previous execution tree.

In other words, soon after small_function_2() returns, the stack will look like this:

+----------------------------+----------------------------+--------------+
| SF2 locals,  retaddr: &SF1 | SF1 locals, retaddr: &main |  main locals |
+----------------------------+----------------------------+--------------+

Using a stack scanner at this point will find the right return addresses that is [&SF1, &main]

The problem is when you are in the middle of that V8::Execute() after large_function(), now your stack will look like:
+-------------------------------+---------------------------------------------------------+--------------+
| v8::exec locals, retaddr: &LF | LF locals (still has the stale pointers &SF1, &main)    |  main locals |
+-------------------------------+---------------------------------------------------------+--------------+

So if you stack scan this, the stack trace which will come out as:
v8::exec -> large_function -> small_function_1 -> main
where small_function_1 is a false positive, result of having stack scanned an uninitialized stack.


Perhaps we could inject some markers on the stack via RAII objects, understood by the stack scanner, which clean up themselves on return.
What I am thinking about is: say that v8 execute knows that it will break the FP chain. It could use a base::debug::ScopedCallFrameMarker() and inject two markers on the stack to hint the stack scanner in base, of the form:
 - From this point on, ignore pointers found on the stack, until you see the next marker.
 - This is the next frame, resume the walk from here.

Resuming is easier for FP-based unwinding but might be a bit trickier for the CFI-based one though.

But that RAII object and markers won't help Windows, which doesn't use frame pointers to unwind.

I wonder if Jakob's patch handles that? 
On Windows the stack unwinding of x64 processes is based on metadata which is normally stored in the binary by the compiler/linker. When code is generated it is expected that RtlAddFunctionTable will be used to dynamically create the metadata. Here are some references:

http://factor-language.blogspot.com/2010/04/frame-based-structured-exception.html
https://msdn.microsoft.com/en-us/library/ms680588(VS.85).aspx

Using RtlAddFunctionTable should make stack walking work within Chromium, in the VS and windbg debuggers, and also in the ETW profiler and other profilers.

Disclaimer: I haven't used this technique.

Comment 7 by siggi@chromium.org, Oct 4 2017

There is integration code in Chrome to do this on Windows: https://cs.chromium.org/chromium/src/components/crash/content/app/crashpad_win.cc?q=RtlAddFunctionTable&dr=C&l=228. I know this because I was recently in there, and briefly broke it.

Sign in to add a comment