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