]> www.infradead.org Git - users/jedix/linux-maple.git/commit
dtrace: Improve dtrace_getufpstack() (locks, stack detection, faults)
authorNick Alcock <nick.alcock@oracle.com>
Mon, 24 Mar 2014 22:43:17 +0000 (22:43 +0000)
committerKris Van Hees <kris.van.hees@oracle.com>
Tue, 29 Apr 2014 05:58:16 +0000 (01:58 -0400)
commit81e24eecf39f787a5ca6b99f6a1ea1af7483e931
treecc17da82ae03d52513ffff872b006db0e354a871
parenta617da8deb3d1948f7f57532db190d690acdc307
dtrace: Improve dtrace_getufpstack() (locks, stack detection, faults)

dtrace_getufpstack() had several flaws exposed by ustack() of multithreaded
processes.  All the flaws touch the same small body of code, and none could be
verified to work until all were in place: hence this rather do-everything
commit.

Firstly, it was detecting the end of the stack using mm->start_stack.  This is
incorrect for all threads but the first, and is even incorrect for the first
thread in languages such as Go with split stacks.  As it is, this causes the
stack traversal to attempt to walk over a gap with no VMAs, causing a crash.

The correct solution is of course to look at the VMAs to find the VMA which
covers the user's stack address.  We are already looking at the VMAs in
is_code_addr(), but this is both a linear scan when all but no-mmu platforms
have better ways, and a *lockless* scan.  This is barely safe in the
single-threaded case, but in the multithreaded case other tasks sharing the same
mm may well be executing in parallel, and it becomes crucial that scanning the
VMAs be done under the mmap_sem.  Unfortunately we cannot always take the
mmap_sem: DTrace may well be invoked in contexts in which sleeping is
prohibited, and in which other threads have the semaphore.  So we must do a
down_read_trylock() on the mmap_sem, aborting the ustack() if we cannot take it
just as we already do if this is a process with no mm at all.  (We also need to
boost the mm_users to prevent problems with group exits.)

We are also accessing the pages themselves without pinning, which means
concurrent memory pressure could swap them out, or memory compaction move them
around.  We can use __get_user_pages() to get the VMA and pin the pages we need
simultaneously, as long as we use the newly-introduced FOLL_NOFAULT to ensure
that __get_user_pages() does not incur page faults.  We wrap __get_user_pages()
in a local find_user_vma(), which also arranges to optionally fail if particular
pages (such as the stack pages) are not in core.  (We need the VMA for some
pages so we can see if they are likely to be text-segment VMAs or not: such
pages do not need to be in core and ustack() need not fail if they are swapped
out.)

For efficiency's sake, we pin each stack page as we cross the page boundary into
it, releasing it afterwards.

But even this does not suffice.  FOLL_NOFAULT ensures that __get_user_pages()
will not fault, but does not ensure that a page fault will not happen when
accessing the page itself.  So we use the newly-introduced CPU_DTRACE_NOPF
machinery to entirely suppress page faults inside get_user() (and nowhere else),
and check it afterwards.

As an additional feature, dtrace_getufpstack() can now be called with a NULL
pcstack and a pcstack_limit of zero, meaning that the stack frame entries are
only counted, not recorded.  We use this feature to reimplement
dtrace_getustackdepth() in terms of dtrace_getufpstack().

With this change, multithreaded ustack()s appear to work, even in the presence
of non-glibc stack layouts (as used by Java and other non-glibc threading
libraries) and concurrent group exits and VMA changes.

Orabug: 18412802
Signed-off-by: Nick Alcock <nick.alcock@oracle.com>
Reviewed-by: Kris Van Hees <kris.van.hees@oracle.com>
Acked-by: Chuck Anderson <chuck.anderson@oracle.com>
dtrace/dtrace_isa.c
dtrace/include/dtrace/dtrace_impl.h