On 7/22/2010 3:15 AM, Adam Leventhal wrote:
What we could do is have the ustack() action record %o7 as well and then figure 
out in user-land whether or not it's relevant.

Actually, I tried %o7 first, and it's pretty dodgy -- once a stack frame has 
been created, it can hold anything. CC seems to use it a lot for bit-shifting 
and as the target of JMPL. In contrast, %i7 is (nearly) always valid.
In leaf-context, %o7 is all you have -- the %is and %ls belong to the previous 
frame.
That's exactly why %i7 works so well compared to %o7 -- the leaf doesn't mess with it. At all times %i7 points to a CALL (or JMPL***) somewhere higher in the call chain, but not higher than the ustack() reports as the current function's caller. This target might be the caller of a leaf, or the first in a sequence of tail calls (which may or may not call a leaf), but either way it's accurate to add that target to the stack trace if it's not the current function:

  1. foo -> bar --- bar owns %i7, mem[%i7] is inside foo,
     target_of(mem[%i7]) == bar (ustack is accurate)
  2. foo -> tail1 -> ... -> tailN -> bar --- same as #1, but
     target_of(mem[%i7]) == tail1 != bar (insert tail1 just above leaf)
  3. foo -> bar -> leaf --- same as #1, but target_of(mem[%i7]) == bar
     != leaf (insert bar just above leaf)
  4. foo -> bar -> tail1 -> ... tailN -> leaf --- bar owns %i7 (insert
     bar just above leaf... no way to recover tail calls)
  5. foo -> tail1 -> ... -> tailN -> bar -> leaf --- bar owns %i7,
     mem[%i7] is inside foo, target_of(mem[%i7]) == tail1 != leaf
     (insert tail1 just above leaf... no way to recover other tail
     calls or bar)
  6. foo -> tail1 -> ... -> tailN -> bar -> tailA -> ... -> tailZ ->
     foo --- combination of previous two (insert tail1 just above leaf,
     other functions lost)

In every case, if mem[%i7] is a CALL and target_of(mem[%i7]) isn't the current function, then it is correct to insert that target function as second-from-top -- it is either a lost tail call or a missing caller we can't see because of the leaf context. The one caveat is the inserted caller will not have an offset, but this doesn't matter if we're only reconstructing a control flow graph.

*** If %i7 points to a CALL instruction, we can decode it and compute the target address as %i7 + (int) (4*mem[%i7]). However, we're out of luck if it was a JMPL instruction, because we can't assume anything about the current content of registers used in the past to compute the target address.



http://wikis.sun.com/display/DTrace/Actions+and+Subroutines#ActionsandSubroutines-%7B%7Bustack%7D%7D,
 needs something resembling:

Limitations: Because ustack() must traverse stack frames to build its stack 
trace, functions which do not establish a stack frame can lead to unpredictable 
results. In particular
        • Functions making tail calls will not appear because they tear down 
their own stack frame before making the call.
        • Except inside function entry probes, leaf functions which have not (yet) established 
a stack frame sometimes prevent their caller from appearing in the stack trace (e.g. foo -> 
 bar ->  baz will appear as foo ->  baz). See<link-to-note-at-profile-provider>.

http://wikis.sun.com/display/DTrace/profile+Provider needs a new section at the 
end (before 'Stability'):

Limitations:

ustack() only reports the caller of a leaf function if the latter has 
established a stack frame.
A leaf function is typically used to refer to one that doesn't establish a 
stack frame.
OK. How about this for the ustack entry:

Limitations: ustack() only reports callers identified by a return address in some stack frame:

   * Functions making tail calls do not appear because control never
     returns to them
   * Unless ustack() is called from a pid provider :::entry or
     :::return probe, a leaf function's caller will not appear because
     the return address does not reside in a stack frame. Entry/return
     probes are a special case because the return address is known even
     without a stack frame, but this not true in general

For example, suppose a program makes the following sequence of function calls::

foo -> tail_caller1 -> bar -> tail_caller2 -> baz

Calling ustack() from pid$target::baz:entry would report foo -> bar -> baz, because foo is mentioned in a stack frame and the pid provider can identify bar. However, calling ustack() from an unanchored (asynchronous) context, such as a profile or tick probe, can report foo -> baz. This always occurs if baz is a leaf function (e.g. never establishes a stack frame), or occasionally if baz does not have a stack frame yet (or any more) because the probe fired inside prologue (or epilogue) code. Note that the two functions making tail calls do not appear in the stack trace under any circumstances.


==== and this for the profile provider entry (added at the end of the first paragraph)

Caveat: ustack() has some limitations when called from unanchored context (See <link-to-ustack-limitations>).

=====

Incidentally, I think the ustack entry would be much easier to follow if it presented the general case first -- ustack(void) and ustack(nframes) -- and explained what it does (similar to kstack, it can also be used as a key for aggregation). Then it could explain (in one sentence) that the strsize arg exists to support java applications and forward-reference the jstack entry. All the examples about java stacks could then move there, where they belong.

Thoughts?
Ryan

_______________________________________________
dtrace-discuss mailing list
dtrace-discuss@opensolaris.org

Reply via email to