Merge lp:~strauman/epics-base/stacktrace-1 into lp:~epics-core/epics-base/3.15

Proposed by Till Straumann
Status: Merged
Merged at revision: 12544
Proposed branch: lp:~strauman/epics-base/stacktrace-1
Merge into: lp:~epics-core/epics-base/3.15
Diff against target: 1524 lines (+1356/-3)
21 files modified
configure/os/CONFIG.Common.linuxCommon (+3/-1)
configure/os/CONFIG.win32-x86.win32-x86 (+2/-2)
src/libCom/misc/cantProceed.c (+4/-0)
src/libCom/osi/Makefile (+6/-0)
src/libCom/osi/epicsStackTrace.c (+124/-0)
src/libCom/osi/epicsStackTrace.h (+43/-0)
src/libCom/osi/epicsStackTracePvt.h (+48/-0)
src/libCom/osi/os/Darwin/osdBackTrace.cpp (+10/-0)
src/libCom/osi/os/Darwin/osdFindAddr.cpp (+43/-0)
src/libCom/osi/os/Linux/osdBackTrace.cpp (+10/-0)
src/libCom/osi/os/Linux/osdFindAddr.cpp (+10/-0)
src/libCom/osi/os/WIN32/osdBackTrace.cpp (+23/-0)
src/libCom/osi/os/default/osdAssert.c (+4/-0)
src/libCom/osi/os/default/osdBackTrace.cpp (+16/-0)
src/libCom/osi/os/default/osdFindAddr.cpp (+25/-0)
src/libCom/osi/os/posix/osdElfFindAddr.cpp (+664/-0)
src/libCom/osi/os/posix/osdExecinfoBackTrace.cpp (+18/-0)
src/libCom/osi/os/solaris/osdBackTrace.cpp (+46/-0)
src/libCom/osi/os/solaris/osdFindAddr.cpp (+10/-0)
src/libCom/test/Makefile (+5/-0)
src/libCom/test/epicsStackTraceTest.c (+242/-0)
To merge this branch: bzr merge lp:~strauman/epics-base/stacktrace-1
Reviewer Review Type Date Requested Status
Andrew Johnson Approve
mdavidsaver Approve
Review via email: mp+232618@code.launchpad.net

Description of the change

Added a unit test in the standard location.

To post a comment you must log in.
lp:~strauman/epics-base/stacktrace-1 updated
12528. By Till Straumann

 - let default (no-op) version of stack trace print informative message
   that stack traces are not supported.

Revision history for this message
Andrew Johnson (anj) wrote :

A few disjoint comments in-line, hopefully others will add to this.

lp:~strauman/epics-base/stacktrace-1 updated
12529. By Till Straumann

 - removed obsolete rule (used during development)

12530. By Till Straumann

 - removed message about a stack trace going to be printed

12531. By Till Straumann

 - removed message; if there is no support remain silent

12532. By Till Straumann

 - print header message

Revision history for this message
Till Straumann (strauman) wrote :
Download full text (36.7 KiB)

Hi Andrew.

Thanks for your comments - I reply in-line...

On 08/29/2014 12:38 PM, Andrew Johnson wrote:
> A few disjoint comments in-line, hopefully others will add to this.
>
> Diff comments:
>
>> === modified file 'configure/os/CONFIG.Common.linuxCommon'
>> --- configure/os/CONFIG.Common.linuxCommon 2012-10-29 19:20:36 +0000
>> +++ configure/os/CONFIG.Common.linuxCommon 2014-08-28 22:11:13 +0000
>> @@ -20,7 +20,9 @@
>> # -D_BSD_SOURCE for gethostname() in unistd.h as needed by cacChannelIO.cpp.
>> OP_SYS_CPPFLAGS += -D_BSD_SOURCE
>> OP_SYS_CPPFLAGS += -Dlinux
>> -OP_SYS_LDLIBS += -lrt -ldl
>> +# add -rdynamic so that more symbols (all globals, not only
>> +# cross-referenced ones) are available to 'backtrace'.
>> +OP_SYS_LDLIBS += -lrt -ldl -rdynamic
>>
>> # Added here for cross-target builds which include this file
>> STATIC_LDFLAGS_YES= -Wl,-Bstatic
>>
>> === modified file 'src/libCom/misc/cantProceed.c'
>> --- src/libCom/misc/cantProceed.c 2010-10-05 19:27:37 +0000
>> +++ src/libCom/misc/cantProceed.c 2014-08-28 22:11:13 +0000
>> @@ -19,6 +19,7 @@
>> #include "errlog.h"
>> #include "cantProceed.h"
>> #include "epicsThread.h"
>> +#include "epicsStackTrace.h"
>>
>> epicsShareFunc void * callocMustSucceed(size_t count, size_t size, const char *msg)
>> {
>> @@ -62,6 +63,11 @@
>>
>> errlogPrintf("Thread %s (%p) can't proceed, suspending.\n",
>> epicsThreadGetNameSelf(), (void *)epicsThreadGetIdSelf());
>> +
>> + errlogPrintf("Dumping a stack trace:\n");
>> + epicsStackTrace();
>> + errlogPrintf("\n");
>> +
> If an OS doesn't support stack traces I would prefer that we not print the other stuff on every cantProceed() call. Maybe move those errlogPrintf() calls into the epicsStackTrace() routine?
Fair enough. Done.
>
>> errlogFlush();
>>
>> epicsThreadSleep(1.0);
>>
>> === modified file 'src/libCom/osi/Makefile'
>> --- src/libCom/osi/Makefile 2014-05-23 19:14:49 +0000
>> +++ src/libCom/osi/Makefile 2014-08-28 22:11:13 +0000
>> @@ -56,6 +56,7 @@
>> INC += epicsStdioRedirect.h
>> INC += epicsTempFile.h
>> INC += epicsGetopt.h
>> +INC += epicsStackTrace.h
>>
>> INC += devLib.h
>> INC += devLibVME.h
>> @@ -140,3 +141,11 @@
>> Com_SRCS_WIN32 += setThreadName.cpp
>> #Com_SRCS_WIN32 += dllmain.cpp
>> Com_SRCS_WIN32 += forceBadAllocException.cpp
>> +
>> +#Stack trace support
>> +Com_SRCS += osdStackTrace.c
>> +Com_SRCS_Linux += execinfoStackTrace.c
>> +Com_SRCS_Darwin += execinfoStackTrace.c
>> +#we could use execinfoStackTrace.c on freebsd, too, but AFAIK
>> +#you need libexecinfo.a and execinfo.h. I don't know if that
>> +#is routinely available so we don't use it for now.
>>
>> === modified file 'src/libCom/osi/RULES'
>> --- src/libCom/osi/RULES 2014-08-05 19:03:37 +0000
>> +++ src/libCom/osi/RULES 2014-08-28 22:11:13 +0000
>> @@ -13,3 +13,7 @@
>> osdNetIntf$(DEP): $(COMMON_DIR)/epicsVersion.h
>> osdSock$(DEP): $(COMMON_DIR)/epicsVersion.h
>>
>> +execinfoConfig.h:
>> + touch $@
>> +
>> +execinfoStackTrace$(OBJ): execinfoConfig.h
> What is execinfoConfig.h for?
Leftover from development. Removed, thanks.
>
>> === added file 'src/libCo...

Revision history for this message
mdavidsaver (mdavidsaver) wrote :

Do you plan to remove the "#if 0" code?

Revision history for this message
mdavidsaver (mdavidsaver) wrote :

Would it be possible to rework elfLookupAddr() into a form equivalent to backtrace_symbols() and move it to a separate file? I think this would help to compartmentalize things as the bulk of the code in this branch sits behind elfLookupAddr.

Revision history for this message
Andrew Johnson (anj) wrote :

Since both implementations of epicsStackTraceGetFeatures() return a constant that is calculated at compile-time don't see the need for this to be a function; an extern const int would work just as well.

I agree with Michael's comment about reworking elfLookupAddr(); we have been trying to remove architecture-specific conditionally compiled code where possible, providing a common set of osd*() routines in the libCom/osi/os/osd*.c files that the higher-level implementation can call. This makes the code much easier to read and debug IMHO. The execinfoStackTrace.c file currently has more #ifs and #ifdefs in it than all the rest of libCom/osi/*.c[pp] put together.

Which of our platforms will use the elfLookupAddr() implementation? There would seem to be some conceptual overlap between this and the epicsFindSymbol() API (in epicsFindSymbol.h and os/*/osdFindSymbol.c), although that is looking for an address given the symbol name, which is the opposite usage.

VxWorks provides a routine tt(taskId) for printing stack-traces but the output is sent directly to the OS's stdout or stderr, and the internals of the implementation are not accessible. Oh, and tt() can't trace its own thread either, so it's not much use to us for this anyway.

Revision history for this message
Till Straumann (strauman) wrote :

How do you feel about my adding the reverse lookup:

epicsFindAddress()

to the epicsFindSymbol API. The stacktrace could also be added - this
requires only little (but important) code in addition to epicsFindAddress().
This is the 'backtrace' call from <execinfo.h> which is available on
linux/freebsd/darwin but not solaris.

IMHO it is not satisfactory that the implementations for darwin, linux and
solaris (freebsd could be added) are identical. Since they all use
dlopen/dlerror/dlsym
which *are* posix -- this implementation should clearly go into a single
file
in the 'posix' directory.

The reverse lookup requires 'dladdr' which is not posix but an addition
which is available under BSD, darwin, gnu/linux and solaris - therefore
it should not be a problem.

The story with the ELF stuff is that 'dladdr' gives incomplete information
on some systems (most importantly: linux) which is why we then look
at the ELF data. The ELF business could be moved to a different file
used only by linux (ATM).

RFC
- Till

On 09/03/2014 02:42 PM, Andrew Johnson wrote:
> Since both implementations of epicsStackTraceGetFeatures() return a constant that is calculated at compile-time don't see the need for this to be a function; an extern const int would work just as well.
>
> I agree with Michael's comment about reworking elfLookupAddr(); we have been trying to remove architecture-specific conditionally compiled code where possible, providing a common set of osd*() routines in the libCom/osi/os/osd*.c files that the higher-level implementation can call. This makes the code much easier to read and debug IMHO. The execinfoStackTrace.c file currently has more #ifs and #ifdefs in it than all the rest of libCom/osi/*.c[pp] put together.
>
> Which of our platforms will use the elfLookupAddr() implementation? There would seem to be some conceptual overlap between this and the epicsFindSymbol() API (in epicsFindSymbol.h and os/*/osdFindSymbol.c), although that is looking for an address given the symbol name, which is the opposite usage.
>
> VxWorks provides a routine tt(taskId) for printing stack-traces but the output is sent directly to the OS's stdout or stderr, and the internals of the implementation are not accessible. Oh, and tt() can't trace its own thread either, so it's not much use to us for this anyway.
>

Revision history for this message
Till Straumann (strauman) wrote :

On a related note:

I just looked at epicsFindSymbol() and wanted to point out that this API
is not implemented
in a thread-safe way (the posix variant, that is). The standard says

"It is implementation-defined whether or not the /dlerror/() function is
thread-safe.
  A thread-safe implementation shall return only errors that occur on
the current thread."

(http://pubs.opengroup.org/onlinepubs/9699919799/)

A little test reveals that (at least: recent, i.e., ubuntu 14.04, osx
10.9) linux + osx are
thread-safe but solaris is not.

- T.

On 09/03/2014 02:42 PM, Andrew Johnson wrote:
> Since both implementations of epicsStackTraceGetFeatures() return a constant that is calculated at compile-time don't see the need for this to be a function; an extern const int would work just as well.
>
> I agree with Michael's comment about reworking elfLookupAddr(); we have been trying to remove architecture-specific conditionally compiled code where possible, providing a common set of osd*() routines in the libCom/osi/os/osd*.c files that the higher-level implementation can call. This makes the code much easier to read and debug IMHO. The execinfoStackTrace.c file currently has more #ifs and #ifdefs in it than all the rest of libCom/osi/*.c[pp] put together.
>
> Which of our platforms will use the elfLookupAddr() implementation? There would seem to be some conceptual overlap between this and the epicsFindSymbol() API (in epicsFindSymbol.h and os/*/osdFindSymbol.c), although that is looking for an address given the symbol name, which is the opposite usage.
>
> VxWorks provides a routine tt(taskId) for printing stack-traces but the output is sent directly to the OS's stdout or stderr, and the internals of the implementation are not accessible. Oh, and tt() can't trace its own thread either, so it's not much use to us for this anyway.
>

Revision history for this message
Andrew Johnson (anj) wrote :

I was just reading an article about the GLIBC feature test macros on LWN http://lwn.net/Articles/590381/ which says that application code should never #define __USE_GNU or any of the other __USE_xxx macros itself (under the heading "A regular pitfall"). Please fix.

Revision history for this message
Till Straumann (strauman) wrote :

Yeah, I had already noticed and fixed that one.

On 09/08/2014 08:52 AM, Andrew Johnson wrote:
> I was just reading an article about the GLIBC feature test macros on LWN http://lwn.net/Articles/590381/ which says that application code should never #define __USE_GNU or any of the other __USE_xxx macros itself (under the heading "A regular pitfall"). Please fix.
>

lp:~strauman/epics-base/stacktrace-1 updated
12533. By Till Straumann

 - replaced TABs by four blanks

12534. By Till Straumann

 - replaced TABs by four blanks

12535. By Till Straumann

 - added test case for library symbols
 - replaced TABs by spaces

12536. By Till Straumann

 - added EPICS_STACKTRACE_DYN_SYMBOL

12537. By Till Straumann

 - canonicalized printout formatting
 - added EPICS_STACKTRACE_DYN_SYMBOL
 - added USE_DLADDR branch (which is a 'super' of USE_ELF, i.e., the latter
   depends on the former)

12538. By Till Straumann

 - call first epicsStackTraceRecurseGbl() through function pointer -- clang
   (darwin) had optimized it away.

12539. By Till Straumann

 - forget about backtrace_symbols() -- always use 'dladdr'. (The former
   is non-standard either and AFAIK always based on the latter.) Always
   using dladdr allows us to precisely control the printout formatting
   and keep it consistent.

12540. By Till Straumann

 - use unistd's _POSIX_MAPPED_FILES to determine if a system has mmap.

12541. By Till Straumann

 - first stab at breaking stack trace facility into separate files

12542. By Till Straumann

 - (hopefully) correct usage of sharedLib.h...

12543. By Till Straumann

 - fixed illegal void* pointer arithmetic by casting to char*
 - removed more unused stuff

12544. By Till Straumann

 - fixed illegal void* pointer arithmetic (by casting to char*)

12545. By Till Straumann

 - added windows versions of osdBackTrace.c, osdFindAddr.c; the latter
   does not implement addr->symbol conversion yet.

12546. By Till Straumann

 - turn off frame-pointer optimization (otherwise stack trace capture
   does not work).

12547. By Till Straumann <strauman@rhel6-64f>

 - solaris implementation of epicsBackTrace() via walkcontext()

12548. By Till Straumann <strauman@rhel6-64f>

 - renamed symbol that clashed on solaris

12549. By Till Straumann <strauman@rhel6-64f>

 - fixed illegal void* pointer arithmetic by casting to char*

12550. By Till Straumann

 - renamed:
  src/libCom/osi/os/posix/osdBackTrace.c => src/libCom/osi/os/posix/osdExecinfoBackTrace.c
  src/libCom/osi/os/posix/osdFindAddr.c => src/libCom/osi/os/posix/osdElfFindAddr.c

  not all 'posix' platforms can use the above versions (since the used APIs are not really POSIX)
  but a subset can. The platforms which can use either version #include it from 'their'
  osdBackTrace.c/osdFindAddr.c.

12551. By Till Straumann

 - updated copyright info

12552. By Till Straumann

 - cleanup
 - reduced epicsStackTraceGetFeatures to epicsFindAddrGetFeatures

12553. By Till Straumann

 - updated copyright info
 - let epicsFindAddr clear out returned symbol info
 - provide epicsFindAddrGetFeatures instead of epicsStackTraceGetFeatures

12554. By Till Straumann

 - WIN32 can now use default osdFindAddr (until symbol lookup is implemented)

12555. By Till Straumann

 - simplified code
 - provide epicsFindAddrGetFeatures() instead of epicsStackTraceGetFeatures()

12556. By Till Straumann

 - simplified some code
 - epicsStackTrace.c provides generic epicsStackTraceGetFeatures() testing
   the 'epicsBackTrace()' functionality. Call out to epicsFindAddrGetFeatures()
   to inquire about symbol lookup support.

Revision history for this message
Till Straumann (strauman) wrote :

I restructured the code and hope to have addressed your concerns.
Functionally, dumping a stack trace can be split into two major
tasks:
 - obtaining a backtrace (array of numerical PC addresses)
 - finding a text symbol close to the addresses

epicsStackTrace() is now a generic piece of code which calls out to
  epicsBackTrace() for obtaining the numerical backtrace
  epicsFindAddr() for obtaining symbol information

It makes sense to break the two OSD functions up into separate files
because sometimes the implementation of one of them can be shared
between OSes but the other one can't. E.g., linux and solaris can
use the same symbol lookup (ELF) but require different backtrace
or e.g., OSX and linux can use the same backtrace but require
different symbol lookup.

There are variants for
   os/default: provides no-op osdBackTrace.c, osdFindAddr.c
   os/WIN32: implements osdBackTrace.c, inherits default osdFindAddr.c
   os/solaris: implements osdBackTrace.c, inherits posix osdElfFindAddr.c
   os/linux: inherits posix osdExecinfoBacktrace.c, inherits posix osdElfFindAddr.c
   os/darwin: inherits posix osdExecinfoBacktrace.c, implements osdFindAddr.c

The reason for special names of the 'posix' variants is that while there
are no true posix APIs that could help the implementation the 'posix'
version can be expected to work on multiple (but not all!) platforms that
EPICS treats as 'posix' (e.g., backtrace should work on any system with execinfo.h).
Since there could be other flavors of shareable implementations in the future
(and since not all 'posix' OSes should inherit the above variants) more
specific names were used.

All of the above have been built and tested. I have no access to freebsd
but osdBackTrace.c/osdFindAddr.c from linux could be copied to os/freebsd;
the same algorithms should work.

Conditional compilation (except for debugging statements) has been almost
completely eliminated except for:
 - unistd/POSIX feature test for presence of mmap() on a platform
 - handle a small, special quirk under solaris

Symbol lookup for WIN32 should not be hard to add but I don't want to spend
more time until a positive decision to merge this facility has been made.

Revision history for this message
mdavidsaver (mdavidsaver) wrote :

With the reorganization this code is much more understandable to me. I see that the ELF code reads the on-disk executable/library at the time the stack trace is generated (the first time). IOCs can run for a long time, and it isn't out of the question that the code has been re-compiled without a restart.

Is there some way we can detect if the file read from disk really corresponds to the running code? Perhaps comparing file modification time with process start time?

lp:~strauman/epics-base/stacktrace-1 updated
12557. By Till Straumann

 - made osdBackTrace/osdFindAddr c++ sources to be more flexible for
   implementations. Fixed compiler warnings (c++ more picky than cc).

12558. By Till Straumann

 - implemented suggestion by Michael Davidsaver: when reading ELF files
   check modifification time against program start time and warn if the
   file was modified more recently since it could be out of date.

12559. By Till Straumann <strauman@rhel6-64f>

 - fixed c++ warnings

12560. By Till Straumann <strauman@rhel6-64f>

 - fixed c++ warnings

Revision history for this message
Till Straumann (strauman) wrote :

OK. I have added code to the ELF reader which compares the file
modification time
to the time when the process started (I simply use a c++ initializer to
obtain the
process start time which is more portable than scanning /proc entries).

A warning is printed if the file has been modified more recently than
when the
process started. I feel that a warning is better than refusal to use the
file since
it could still be valid or at least better than nothing.

- T.

On 09/15/2014 01:02 PM, mdavidsaver wrote:
> With the reorganization this code is much more understandable to me. I see that the ELF code reads the on-disk executable/library at the time the stack trace is generated (the first time). IOCs can run for a long time, and it isn't out of the question that the code has been re-compiled without a restart.
>
> Is there some way we can detect if the file read from disk really corresponds to the running code? Perhaps comparing file modification time with process start time?
>

Revision history for this message
mdavidsaver (mdavidsaver) wrote :

A warning seem adaquate.

Can we get away without assuming that time_t has 1 second resolution? Perhaps time() instead of clock_gettime()?

Given a 1 second resolution, any reason for "stat_b.st_mtime > prog_start_time" vs. ">="?

lp:~strauman/epics-base/stacktrace-1 updated
12561. By Till Straumann

 - use time() instead of clock_gettime();
 - when comparing modification to program start time use >= instead of >

Revision history for this message
Till Straumann (strauman) wrote :

On 09/19/2014 09:31 AM, mdavidsaver wrote:
> A warning seem adaquate.
>
> Can we get away without assuming that time_t has 1 second resolution?
No - because that is how it is defined :-)
> Perhaps time() instead of clock_gettime()?
Fine with me but there is no difference - tv_sec is also of type time_t.
However, using time() does simplify the code a little bit, thanks.
>
> Given a 1 second resolution, any reason for "stat_b.st_mtime > prog_start_time" vs. ">="?
Fine with me, too. I pushed the changes...

However, I can't help getting the impression that this code is
subject to extraordinary scrutiny which seems exaggerated
given that its only purpose is adding information to a post-mortem
situation.

I don't see comparable precautions being taken when it comes e.g.,
to loading 'dbd' or 'db' files which may also have been modified after
an IOC starts running - usually there is a time window of many seconds
to minutes after an IOC starts until it gets to read dbds and dbs...

Best
- T.

Revision history for this message
mdavidsaver (mdavidsaver) wrote :

>> Can we get away without assuming that time_t has 1 second resolution?
> No - because that is how it is defined :-)

Ok, for POSIX you are correct. Would be nice if this was always true, then we could simplify epicsTime.cpp.

Revision history for this message
mdavidsaver (mdavidsaver) wrote :

Just realized that you have in fact made the two changes I suggested. At this point I don't see any further issues.

review: Approve
Revision history for this message
Andrew Johnson (anj) wrote :

Making a few minor changes during merge process:

* Moved -rdynamic flag into OP_SYS_LDFLAGS
* Documented MSVS -Oy- flag
* Added epicsStackTraceTest() to epicsRunLibComTests()
* Changed epicsStackTraceTest() to use testSkip() for unsupported operations.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'configure/os/CONFIG.Common.linuxCommon'
2--- configure/os/CONFIG.Common.linuxCommon 2014-09-08 22:57:18 +0000
3+++ configure/os/CONFIG.Common.linuxCommon 2014-09-19 17:20:51 +0000
4@@ -17,7 +17,9 @@
5 POSIX_LDLIBS = -lpthread
6
7 OP_SYS_CPPFLAGS += -Dlinux
8-OP_SYS_LDLIBS += -lrt -ldl
9+# add -rdynamic so that more symbols (all globals, not only
10+# cross-referenced ones) are available to 'backtrace'.
11+OP_SYS_LDLIBS += -lrt -ldl -rdynamic
12
13 # Linker flags for static & shared-library builds
14 STATIC_LDFLAGS_YES= -Wl,-Bstatic
15
16=== modified file 'configure/os/CONFIG.win32-x86.win32-x86'
17--- configure/os/CONFIG.win32-x86.win32-x86 2013-07-26 19:37:55 +0000
18+++ configure/os/CONFIG.win32-x86.win32-x86 2014-09-19 17:20:51 +0000
19@@ -43,7 +43,7 @@
20 # -MD use MSVCRT (run-time as DLL, multi-thread support)
21 # -GL whole program optimization
22 # -Zi generate program database for debugging information
23-OPT_CFLAGS_YES = -Ox -GL
24+OPT_CFLAGS_YES = -Ox -GL -Oy-
25
26 #
27 # -Zi generate program database for debugging information
28@@ -107,7 +107,7 @@
29 # -Ox maximum optimizations
30 # -GL whole program optimization
31 # -Zi generate program database for debugging information
32-OPT_CXXFLAGS_YES = -Ox -GL
33+OPT_CXXFLAGS_YES = -Ox -GL -Oy-
34
35 #
36 # -Zi generate program database for debugging information
37
38=== modified file 'src/libCom/misc/cantProceed.c'
39--- src/libCom/misc/cantProceed.c 2010-10-05 19:27:37 +0000
40+++ src/libCom/misc/cantProceed.c 2014-09-19 17:20:51 +0000
41@@ -19,6 +19,7 @@
42 #include "errlog.h"
43 #include "cantProceed.h"
44 #include "epicsThread.h"
45+#include "epicsStackTrace.h"
46
47 epicsShareFunc void * callocMustSucceed(size_t count, size_t size, const char *msg)
48 {
49@@ -62,6 +63,9 @@
50
51 errlogPrintf("Thread %s (%p) can't proceed, suspending.\n",
52 epicsThreadGetNameSelf(), (void *)epicsThreadGetIdSelf());
53+
54+ epicsStackTrace();
55+
56 errlogFlush();
57
58 epicsThreadSleep(1.0);
59
60=== modified file 'src/libCom/osi/Makefile'
61--- src/libCom/osi/Makefile 2014-05-23 19:14:49 +0000
62+++ src/libCom/osi/Makefile 2014-09-19 17:20:51 +0000
63@@ -56,6 +56,7 @@
64 INC += epicsStdioRedirect.h
65 INC += epicsTempFile.h
66 INC += epicsGetopt.h
67+INC += epicsStackTrace.h
68
69 INC += devLib.h
70 INC += devLibVME.h
71@@ -140,3 +141,8 @@
72 Com_SRCS_WIN32 += setThreadName.cpp
73 #Com_SRCS_WIN32 += dllmain.cpp
74 Com_SRCS_WIN32 += forceBadAllocException.cpp
75+
76+#Stack trace support
77+Com_SRCS += epicsStackTrace.c
78+Com_SRCS += osdBackTrace.cpp
79+Com_SRCS += osdFindAddr.cpp
80
81=== added file 'src/libCom/osi/epicsStackTrace.c'
82--- src/libCom/osi/epicsStackTrace.c 1970-01-01 00:00:00 +0000
83+++ src/libCom/osi/epicsStackTrace.c 2014-09-19 17:20:51 +0000
84@@ -0,0 +1,124 @@
85+/*
86+ * Copyright: Stanford University / SLAC National Laboratory.
87+ *
88+ * EPICS BASE is distributed subject to a Software License Agreement found
89+ * in file LICENSE that is included with this distribution.
90+ *
91+ * Author: Till Straumann <strauman@slac.stanford.edu>, 2011, 2014
92+ */
93+
94+#include <stdlib.h>
95+
96+#include "epicsStackTracePvt.h"
97+#include "epicsThread.h"
98+#include "epicsMutex.h"
99+#include "errlog.h"
100+
101+#define epicsExportSharedSymbols
102+#include "epicsStackTrace.h"
103+
104+/* How many stack frames to capture */
105+#define MAXDEPTH 100
106+
107+static epicsThreadOnceId stackTraceInitId = EPICS_THREAD_ONCE_INIT;
108+static epicsMutexId stackTraceMtx;
109+
110+static void stackTraceInit(void *unused)
111+{
112+ stackTraceMtx = epicsMutexMustCreate();
113+}
114+
115+static void stackTraceLock(void)
116+{
117+ epicsThreadOnce( &stackTraceInitId, stackTraceInit, 0 );
118+ epicsMutexLock( stackTraceMtx );
119+}
120+
121+static void stackTraceUnlock(void)
122+{
123+ epicsMutexUnlock( stackTraceMtx );
124+}
125+
126+static int
127+dumpInfo(void *addr, epicsSymbol *sym_p)
128+{
129+int rval = 0;
130+
131+ rval += errlogPrintf("[%*p]", (int)(sizeof(addr)*2 + 2), addr);
132+ if ( sym_p ) {
133+ if ( sym_p->f_nam ) {
134+ rval += errlogPrintf(": %s", sym_p->f_nam);
135+ }
136+ if ( sym_p->s_nam ) {
137+ rval += errlogPrintf("(%s+0x%lx)", sym_p->s_nam, (unsigned long)((char*)addr - (char*)sym_p->s_val));
138+ } else {
139+ rval += errlogPrintf("(<no symbol information>)");
140+ }
141+ }
142+ rval += errlogPrintf("\n");
143+ errlogFlush();
144+
145+ return rval;
146+}
147+
148+void epicsStackTrace(void)
149+{
150+void **buf;
151+int i,n;
152+epicsSymbol sym;
153+
154+ if ( 0 == epicsStackTraceGetFeatures() ) {
155+ /* unsupported on this platform */
156+ return;
157+ }
158+
159+ if ( ! (buf = malloc(sizeof(*buf) * MAXDEPTH))) {
160+ free(buf);
161+ errlogPrintf("epicsStackTrace(): not enough memory for backtrace\n");
162+ return;
163+ }
164+
165+ n = epicsBackTrace(buf, MAXDEPTH);
166+
167+ if ( n > 0 ) {
168+
169+ stackTraceLock();
170+
171+ errlogPrintf("Dumping a stack trace of thread '%s':\n", epicsThreadGetNameSelf());
172+
173+ errlogFlush();
174+
175+ for ( i=0; i<n; i++ ) {
176+ if ( 0 == epicsFindAddr(buf[i], &sym) )
177+ dumpInfo(buf[i], &sym);
178+ else
179+ dumpInfo(buf[i], 0);
180+ }
181+
182+ errlogFlush();
183+
184+ stackTraceUnlock();
185+
186+ }
187+
188+ free(buf);
189+}
190+
191+int epicsStackTraceGetFeatures()
192+{
193+void *test[2];
194+
195+static int initflag = 10; /* init to a value larger than the test snapshot */
196+
197+ /* don't bother about epicsOnce -- if there should be a race and
198+ * the detection code is executed multiple times that is no big deal.
199+ */
200+ if ( 10 == initflag ) {
201+ initflag = epicsBackTrace(test, sizeof(test)/sizeof(test[0]));
202+ }
203+
204+ if ( initflag <= 0 )
205+ return 0; /* backtrace doesn't work or is not supported */
206+
207+ return ( EPICS_STACKTRACE_ADDRESSES | epicsFindAddrGetFeatures() );
208+}
209
210=== added file 'src/libCom/osi/epicsStackTrace.h'
211--- src/libCom/osi/epicsStackTrace.h 1970-01-01 00:00:00 +0000
212+++ src/libCom/osi/epicsStackTrace.h 2014-09-19 17:20:51 +0000
213@@ -0,0 +1,43 @@
214+/*
215+ * Copyright: Stanford University / SLAC National Laboratory.
216+ *
217+ * EPICS BASE is distributed subject to a Software License Agreement found
218+ * in file LICENSE that is included with this distribution.
219+ *
220+ * Author: Till Straumann <strauman@slac.stanford.edu>, 2011, 2014
221+ */
222+
223+#ifndef INC_epicsStackTrace_H
224+#define INC_epicsStackTrace_H
225+
226+#include "shareLib.h"
227+
228+#ifdef __cplusplus
229+extern "C" {
230+#endif
231+
232+/* Dump a stack trace to the errlog */
233+epicsShareFunc void epicsStackTrace(void);
234+
235+/* Inquire about functionality implemented on your system */
236+
237+/* StackTrace provides numerical addresses */
238+#define EPICS_STACKTRACE_ADDRESSES (1<<0)
239+
240+/* StackTrace is able to lookup dynamic symbols */
241+#define EPICS_STACKTRACE_DYN_SYMBOLS (1<<1)
242+
243+/* StackTrace is able to lookup global symbols */
244+#define EPICS_STACKTRACE_GBL_SYMBOLS (1<<2)
245+
246+/* StackTrace is able to lookup local symbols */
247+#define EPICS_STACKTRACE_LCL_SYMBOLS (1<<3)
248+
249+/* returns ORed bitset of supported features */
250+epicsShareFunc int epicsStackTraceGetFeatures(void);
251+
252+#ifdef __cplusplus
253+}
254+#endif
255+
256+#endif
257
258=== added file 'src/libCom/osi/epicsStackTracePvt.h'
259--- src/libCom/osi/epicsStackTracePvt.h 1970-01-01 00:00:00 +0000
260+++ src/libCom/osi/epicsStackTracePvt.h 2014-09-19 17:20:51 +0000
261@@ -0,0 +1,48 @@
262+/*
263+ * Copyright: Stanford University / SLAC National Laboratory.
264+ *
265+ * EPICS BASE is distributed subject to a Software License Agreement found
266+ * in file LICENSE that is included with this distribution.
267+ *
268+ * Author: Till Straumann <strauman@slac.stanford.edu>, 2011, 2014
269+ */
270+
271+#ifndef INC_epicsStackTracePvt_H
272+#define INC_epicsStackTracePvt_H
273+
274+#include "shareLib.h"
275+
276+typedef struct epicsSymbol {
277+ const char *f_nam; /* file where the symbol is defined */
278+ const char *s_nam; /* symbol name */
279+ void *s_val; /* symbol value */
280+} epicsSymbol;
281+
282+#ifdef __cplusplus
283+extern "C" {
284+#endif
285+
286+/* Take a snapshot of the stack into 'buf' (limited to buf_sz entries)
287+ * RETURNS: actual number of entries in 'buf'
288+ */
289+epicsShareFunc int epicsBackTrace(void **buf, int buf_sz);
290+
291+/* Find symbol closest to 'addr'.
292+ *
293+ * If successful the routine fills in the members of *sym_p but
294+ * note that 'f_nam' and/or 's_nam' may be NULL if the address
295+ * cannot be resolved.
296+ *
297+ * RETURNS: 0 on success, nonzero on failure (not finding an address
298+ * is not considered an error).
299+ */
300+epicsShareFunc int epicsFindAddr(void *addr, epicsSymbol *sym_p);
301+
302+/* report supported features (as reported by epicsStackTraceGetFeatures) */
303+epicsShareFunc int epicsFindAddrGetFeatures();
304+
305+#ifdef __cplusplus
306+}
307+#endif
308+
309+#endif
310
311=== added file 'src/libCom/osi/os/Darwin/osdBackTrace.cpp'
312--- src/libCom/osi/os/Darwin/osdBackTrace.cpp 1970-01-01 00:00:00 +0000
313+++ src/libCom/osi/os/Darwin/osdBackTrace.cpp 2014-09-19 17:20:51 +0000
314@@ -0,0 +1,10 @@
315+/*
316+ * Copyright: Stanford University / SLAC National Laboratory.
317+ *
318+ * EPICS BASE is distributed subject to a Software License Agreement found
319+ * in file LICENSE that is included with this distribution.
320+ *
321+ * Author: Till Straumann <strauman@slac.stanford.edu>, 2011, 2014
322+ */
323+
324+#include "osdExecinfoBackTrace.cpp"
325
326=== added file 'src/libCom/osi/os/Darwin/osdFindAddr.cpp'
327--- src/libCom/osi/os/Darwin/osdFindAddr.cpp 1970-01-01 00:00:00 +0000
328+++ src/libCom/osi/os/Darwin/osdFindAddr.cpp 2014-09-19 17:20:51 +0000
329@@ -0,0 +1,43 @@
330+/*
331+ * Copyright: Stanford University / SLAC National Laboratory.
332+ *
333+ * EPICS BASE is distributed subject to a Software License Agreement found
334+ * in file LICENSE that is included with this distribution.
335+ *
336+ * Author: Till Straumann <strauman@slac.stanford.edu>, 2011, 2014
337+ */
338+
339+/* Make sure dladdr() is visible */
340+#define _DARWIN_C_SOURCE
341+
342+#include <dlfcn.h>
343+
344+#define epicsExportSharedSymbols
345+#include "epicsStackTrace.h"
346+#include "epicsStackTracePvt.h"
347+
348+/* Darwin's finds local symbols, too :-) */
349+
350+int epicsFindAddr(void *addr, epicsSymbol *sym_p)
351+{
352+Dl_info inf;
353+
354+ if ( ! dladdr(addr, &inf) ) {
355+ sym_p->f_nam = 0;
356+ sym_p->s_nam = 0;
357+ sym_p->s_val = 0;
358+ } else {
359+ sym_p->f_nam = inf.dli_fname;
360+ sym_p->s_nam = inf.dli_sname;
361+ sym_p->s_val = inf.dli_saddr;
362+ }
363+
364+ return 0;
365+}
366+
367+int epicsFindAddrGetFeatures(void)
368+{
369+ return EPICS_STACKTRACE_LCL_SYMBOLS
370+ | EPICS_STACKTRACE_GBL_SYMBOLS
371+ | EPICS_STACKTRACE_DYN_SYMBOLS;
372+}
373
374=== added file 'src/libCom/osi/os/Linux/osdBackTrace.cpp'
375--- src/libCom/osi/os/Linux/osdBackTrace.cpp 1970-01-01 00:00:00 +0000
376+++ src/libCom/osi/os/Linux/osdBackTrace.cpp 2014-09-19 17:20:51 +0000
377@@ -0,0 +1,10 @@
378+/*
379+ * Copyright: Stanford University / SLAC National Laboratory.
380+ *
381+ * EPICS BASE is distributed subject to a Software License Agreement found
382+ * in file LICENSE that is included with this distribution.
383+ *
384+ * Author: Till Straumann <strauman@slac.stanford.edu>, 2011, 2014
385+ */
386+
387+#include "osdExecinfoBackTrace.cpp"
388
389=== added file 'src/libCom/osi/os/Linux/osdFindAddr.cpp'
390--- src/libCom/osi/os/Linux/osdFindAddr.cpp 1970-01-01 00:00:00 +0000
391+++ src/libCom/osi/os/Linux/osdFindAddr.cpp 2014-09-19 17:20:51 +0000
392@@ -0,0 +1,10 @@
393+/*
394+ * Copyright: Stanford University / SLAC National Laboratory.
395+ *
396+ * EPICS BASE is distributed subject to a Software License Agreement found
397+ * in file LICENSE that is included with this distribution.
398+ *
399+ * Author: Till Straumann <strauman@slac.stanford.edu>, 2011, 2014
400+ */
401+
402+#include "osdElfFindAddr.cpp"
403
404=== added file 'src/libCom/osi/os/WIN32/osdBackTrace.cpp'
405--- src/libCom/osi/os/WIN32/osdBackTrace.cpp 1970-01-01 00:00:00 +0000
406+++ src/libCom/osi/os/WIN32/osdBackTrace.cpp 2014-09-19 17:20:51 +0000
407@@ -0,0 +1,23 @@
408+/*
409+ * Copyright: Stanford University / SLAC National Laboratory.
410+ *
411+ * EPICS BASE is distributed subject to a Software License Agreement found
412+ * in file LICENSE that is included with this distribution.
413+ *
414+ * Author: Till Straumann <strauman@slac.stanford.edu>, 2014
415+ */
416+
417+#include <Windows.h>
418+
419+#define epicsExportSharedSymbols
420+#include "epicsStackTracePvt.h"
421+
422+int epicsBackTrace(void **buf, int buf_sz)
423+{
424+ /* Docs say that (for some windows versions) the sum of
425+ * skipped + captured frames must be less than 63
426+ */
427+ if ( buf_sz >= 63 )
428+ buf_sz = 62;
429+ return CaptureStackBackTrace(0, buf_sz, buf, 0);
430+}
431
432=== modified file 'src/libCom/osi/os/default/osdAssert.c'
433--- src/libCom/osi/os/default/osdAssert.c 2010-10-05 19:27:37 +0000
434+++ src/libCom/osi/os/default/osdAssert.c 2014-09-19 17:20:51 +0000
435@@ -20,6 +20,7 @@
436 #include "epicsThread.h"
437 #include "epicsTime.h"
438 #include "cantProceed.h"
439+#include "epicsStackTrace.h"
440
441
442 void epicsAssert (const char *pFile, const unsigned line,
443@@ -31,6 +32,9 @@
444 "A call to 'assert(%s)'\n"
445 " by thread '%s' failed in %s line %u.\n",
446 pExp, epicsThreadGetNameSelf(), pFile, line);
447+
448+ epicsStackTrace();
449+
450 errlogPrintf("EPICS Release %s.\n", epicsReleaseVersion);
451
452 if (epicsTimeGetCurrent(&current) == 0) {
453
454=== added file 'src/libCom/osi/os/default/osdBackTrace.cpp'
455--- src/libCom/osi/os/default/osdBackTrace.cpp 1970-01-01 00:00:00 +0000
456+++ src/libCom/osi/os/default/osdBackTrace.cpp 2014-09-19 17:20:51 +0000
457@@ -0,0 +1,16 @@
458+/*
459+ * Copyright: Stanford University / SLAC National Laboratory.
460+ *
461+ * EPICS BASE is distributed subject to a Software License Agreement found
462+ * in file LICENSE that is included with this distribution.
463+ *
464+ * Author: Till Straumann <strauman@slac.stanford.edu>, 2011, 2014
465+ */
466+
467+#define epicsExportSharedSymbols
468+#include "epicsStackTracePvt.h"
469+
470+int epicsBackTrace(void **buf, int buf_sz)
471+{
472+ return -1;
473+}
474
475=== added file 'src/libCom/osi/os/default/osdFindAddr.cpp'
476--- src/libCom/osi/os/default/osdFindAddr.cpp 1970-01-01 00:00:00 +0000
477+++ src/libCom/osi/os/default/osdFindAddr.cpp 2014-09-19 17:20:51 +0000
478@@ -0,0 +1,25 @@
479+/*
480+ * Copyright: Stanford University / SLAC National Laboratory.
481+ *
482+ * EPICS BASE is distributed subject to a Software License Agreement found
483+ * in file LICENSE that is included with this distribution.
484+ *
485+ * Author: Till Straumann <strauman@slac.stanford.edu>, 2011, 2014
486+ */
487+
488+#define epicsExportSharedSymbols
489+#include "epicsStackTracePvt.h"
490+#include "epicsStackTrace.h"
491+
492+int epicsFindAddr(void *addr, epicsSymbol *sym_p)
493+{
494+ sym_p->f_nam = 0;
495+ sym_p->s_nam = 0;
496+ sym_p->s_val = 0;
497+ return -1;
498+}
499+
500+int epicsFindAddrGetFeatures(void)
501+{
502+ return 0;
503+}
504
505=== added file 'src/libCom/osi/os/posix/osdElfFindAddr.cpp'
506--- src/libCom/osi/os/posix/osdElfFindAddr.cpp 1970-01-01 00:00:00 +0000
507+++ src/libCom/osi/os/posix/osdElfFindAddr.cpp 2014-09-19 17:20:51 +0000
508@@ -0,0 +1,664 @@
509+/*
510+ * Copyright: Stanford University / SLAC National Laboratory.
511+ *
512+ * EPICS BASE is distributed subject to a Software License Agreement found
513+ * in file LICENSE that is included with this distribution.
514+ *
515+ * Author: Till Straumann <strauman@slac.stanford.edu>, 2011, 2014
516+ */
517+
518+/* Make sure dladdr() is visible on linux/solaris */
519+#ifndef _GNU_SOURCE
520+#define _GNU_SOURCE
521+#endif
522+/* get dladdr under solaris */
523+#ifndef __EXTENSIONS__
524+#define __EXTENSIONS__
525+#endif
526+
527+#include <unistd.h>
528+#include <dlfcn.h>
529+#include <stdlib.h>
530+#include <fcntl.h>
531+#include <string.h>
532+#include <elf.h>
533+#include <errno.h>
534+#include <inttypes.h>
535+#include <sys/stat.h>
536+#include <sys/types.h>
537+#include <time.h>
538+
539+#ifdef _POSIX_MAPPED_FILES
540+#include <sys/mman.h>
541+#endif
542+
543+#include "epicsMutex.h"
544+#include "epicsThread.h"
545+#include <errlog.h>
546+
547+#define epicsExportSharedSymbols
548+#include "epicsStackTrace.h"
549+#include "epicsStackTracePvt.h"
550+
551+#define FIND_ADDR_DEBUG 0
552+
553+/*
554+ * On some systems (linux, solaris) dladdr doesn't find local symbols
555+ * or symbols in the main executable.
556+ * Hence, we want to use dladdr() to find the file name
557+ * where a symbol is defined and if not more information is available
558+ * then proceed to lookup symbols in the ELF symbol tables.
559+ */
560+
561+/* Macros to handle elf32 vs. elf64 access to unions etc. */
562+
563+#define FLD(c,s,f) (ELFCLASS32==c ? s.e32.f : s.e64.f )
564+#define ARR(c,s,i,f) (ELFCLASS32==c ? s.e32[i].f : s.e64[i].f)
565+
566+/* Elf header */
567+typedef union Ehdr_ {
568+ Elf32_Ehdr e32;
569+ Elf64_Ehdr e64;
570+} Ehdr;
571+
572+/* Section header */
573+typedef union Shdr_ {
574+ Elf32_Shdr e32;
575+ Elf64_Shdr e64;
576+} Shdr;
577+
578+/* Elf symbol */
579+typedef union Sym_ {
580+ void *raw;
581+ Elf32_Sym *e32;
582+ Elf64_Sym *e64;
583+} Sym;
584+
585+/* Memory mapped portion of a file; we must
586+ * keep additional information because the
587+ * map's starting address + length must be
588+ * page-aligned (man mmap).
589+ */
590+typedef struct MMap_ {
591+ void *addr;
592+ off_t off; /* offset into the map where 'real' data start */
593+ size_t len;
594+ size_t max; /* max offset: legal data from addr+off .. addr+off+max-1 */
595+ void (*freeMap)(struct MMap_*); /* 'method' to destroy the mapping */
596+} *MMap;
597+
598+/* Structure describing symbol information
599+ * contained in a file.
600+ * We keep these around (so that the file
601+ * doesn't have to be opened + parsed every
602+ * time we do a lookup).
603+ */
604+typedef struct ESyms_ {
605+ struct ESyms_ *next; /* linked list; one struct per executable */
606+ const char *fname; /* file name */
607+ int fd; /* file descriptor */
608+ uintptr_t addr; /* address where file is loaded */
609+ MMap symMap;
610+ MMap strMap;
611+ size_t nsyms;
612+ uint8_t eclss;
613+} *ESyms;
614+
615+/* LOCKING NOTE: if the ELF symbol facility is ever expanded to be truly used
616+ * in a multithreaded way then proper multiple-readers, single-writer locking
617+ * should be implemented.
618+ */
619+
620+/* Linked list where we keep all our ESyms */
621+static ESyms elfs = 0;
622+
623+static epicsMutexId listMtx;
624+static epicsThreadOnceId listMtxInitId = EPICS_THREAD_ONCE_INIT;
625+
626+static time_t prog_start_time = time(0);
627+
628+extern "C" {
629+
630+static void listMtxInit(void *unused)
631+{
632+ listMtx = epicsMutexMustCreate();
633+}
634+
635+}
636+
637+static void
638+elfsLockWrite()
639+{
640+ epicsThreadOnce(&listMtxInitId, listMtxInit, 0);
641+ epicsMutexMustLock(listMtx);
642+}
643+
644+static void
645+elfsUnlockWrite()
646+{
647+ epicsMutexUnlock(listMtx);
648+}
649+
650+static void
651+freeMap(MMap m)
652+{
653+ if ( m ) {
654+ m->freeMap(m);
655+ free(m);
656+ }
657+}
658+
659+/* Helper to read exactly 'sz' bytes into 'buf'
660+ * RETURNS: # chars read or negative value on error.
661+ */
662+static ssize_t
663+do_read(int fd, void *buf, ssize_t sz)
664+{
665+ssize_t got;
666+char *ptr=(char*)buf;
667+ while ( sz > 0 ) {
668+ if ( (got=read(fd,ptr,sz)) <= 0 ) {
669+ return got;
670+ }
671+ ptr+=got;
672+ sz -=got;
673+ }
674+ return ptr-(char*)buf;
675+}
676+
677+/* Elf file access -- can either be with mmap or by sequential read */
678+
679+#ifdef _POSIX_MAPPED_FILES
680+/* Destructor for data that is mmap()ed */
681+static void
682+freeMapMmap(MMap m)
683+{
684+ if ( MAP_FAILED != m->addr )
685+ munmap( m->addr, m->len );
686+}
687+
688+/* Obtain section data with mmap() */
689+static MMap
690+getscn_mmap(int fd, uint8_t c, Shdr *shdr_p)
691+{
692+off_t n;
693+MMap rval = 0;
694+size_t pgsz = sysconf(_SC_PAGESIZE);
695+
696+ if ( 0 == (n = (off_t)FLD(c,(*shdr_p),sh_size)) ) {
697+ errlogPrintf("elfRead - getscn() -- no section data\n");
698+ goto bail;
699+ }
700+
701+ if ( ! (rval = (MMap) malloc(sizeof(*rval))) ) {
702+ errlogPrintf("elfRead - getscn() -- no memory for section map\n");
703+ goto bail;
704+ }
705+
706+ rval->freeMap = freeMapMmap;
707+
708+ rval->off = (off_t) (FLD(c,(*shdr_p),sh_offset) & (pgsz-1));
709+ rval->len = (n + rval->off + (pgsz - 1)) & ~(pgsz - 1);
710+ rval->max = rval->len - rval->off;
711+
712+ if ( MAP_FAILED == (rval->addr = mmap(0, rval->len, PROT_READ, MAP_SHARED, fd, (off_t) (FLD(c,(*shdr_p),sh_offset) & ~(pgsz-1)))) ) {
713+ errlogPrintf("elfRead - getscn() -- mapping section contents: %s\n", strerror(errno));
714+ goto bail;
715+ }
716+
717+ return rval;
718+
719+bail:
720+ freeMap(rval);
721+ return 0;
722+}
723+#else
724+static MMap getscn_mmap(int fd, uint8_t c, Shrd *shdr_p)
725+{
726+ return 0;
727+}
728+#endif
729+
730+/* Destructor for data that is read into a malloc()ed buffer */
731+static void
732+freeMapMalloc(MMap m)
733+{
734+ free(m->addr);
735+}
736+
737+/* Read section data into a malloc()ed buffer */
738+static MMap
739+getscn_read(int fd, uint8_t c, Shdr *shdr_p)
740+{
741+ssize_t n;
742+MMap rval = 0;
743+
744+ if ( 0 == (n = (ssize_t) FLD(c,(*shdr_p),sh_size)) ) {
745+ errlogPrintf("elfRead - getscn() -- no section data\n");
746+ goto bail;
747+ }
748+
749+ if ( ! (rval = (MMap) malloc(sizeof(*rval))) ) {
750+ errlogPrintf("elfRead - getscn() -- no memory for section map\n");
751+ goto bail;
752+ }
753+
754+ rval->freeMap = freeMapMalloc;
755+
756+ if ( ! (rval->addr = malloc(n)) ) {
757+ errlogPrintf("elfRead - getscn() -- no memory for section data\n");
758+ goto bail;
759+ }
760+
761+ rval->off = 0;
762+ rval->len = n;
763+ rval->max = rval->len - rval->off;
764+
765+ /* seek to symbol table contents */
766+ if ( (off_t)-1 == lseek(fd, (off_t) FLD(c,(*shdr_p),sh_offset), SEEK_SET) ) {
767+ errlogPrintf("elfRead - getscn() -- seeking to sh_offset: %s\n", strerror(errno));
768+ goto bail;
769+ }
770+
771+ if ( n != do_read(fd, rval->addr, n) ) {
772+ errlogPrintf("elfRead - getscn() -- reading section contents: %s\n", strerror(errno));
773+ goto bail;
774+ }
775+
776+ return rval;
777+
778+bail:
779+ freeMap(rval);
780+ return 0;
781+}
782+
783+static MMap
784+getscn(int fd, uint8_t c, Shdr *shdr_p)
785+{
786+MMap rval = getscn_mmap(fd, c, shdr_p);
787+
788+ if ( ! rval )
789+ rval = getscn_read(fd, c, shdr_p);
790+
791+ return rval;
792+}
793+
794+/* Release resources but keep filename so that
795+ * a file w/o symbol table is not read over and over again.
796+ */
797+static void
798+elfSymsRelease(ESyms es)
799+{
800+ if ( es ) {
801+ freeMap(es->symMap);
802+ es->symMap = 0;
803+ freeMap(es->strMap);
804+ es->strMap = 0;
805+ if ( es->fd >= 0 )
806+ close(es->fd);
807+ es->fd = -1;
808+ es->nsyms = 0;
809+ }
810+}
811+
812+static ESyms
813+elfRead(const char *fname, uintptr_t fbase)
814+{
815+int i;
816+Ehdr ehdr;
817+Shdr shdr;
818+uint8_t c;
819+ESyms es;
820+ssize_t idx,n;
821+const char *cp;
822+struct stat stat_b;
823+
824+ if ( !(es = (ESyms) malloc(sizeof(*es))) ) {
825+ /* no memory -- give up */
826+ return 0;
827+ }
828+
829+ memset(es, 0, sizeof(*es));
830+ es->fd = -1;
831+ es->fname = fname;
832+
833+ if ( (es->fd = open(fname, O_RDONLY)) < 0 ) {
834+ errlogPrintf("elfRead() -- unable to open file: %s\n", strerror(errno));
835+ goto bail;
836+ }
837+
838+ if ( EI_NIDENT != do_read(es->fd, &ehdr, EI_NIDENT) ) {
839+ errlogPrintf("elfRead() -- unable to read ELF e_ident: %s\n", strerror(errno));
840+ goto bail;
841+ }
842+
843+ if ( ELFMAG0 != ehdr.e32.e_ident[EI_MAG0]
844+ || ELFMAG1 != ehdr.e32.e_ident[EI_MAG1]
845+ || ELFMAG2 != ehdr.e32.e_ident[EI_MAG2]
846+ || ELFMAG3 != ehdr.e32.e_ident[EI_MAG3] ) {
847+ errlogPrintf("bad ELF magic number\n");
848+ goto bail;
849+ }
850+
851+ if ( EV_CURRENT != ehdr.e32.e_ident[EI_VERSION] ) {
852+ errlogPrintf("bad ELF version\n");
853+ goto bail;
854+ }
855+
856+ switch ( (es->eclss = c = ehdr.e32.e_ident[EI_CLASS]) ) {
857+ default:
858+ errlogPrintf("bad ELF class\n");
859+ goto bail;
860+
861+ case ELFCLASS32:
862+ n = sizeof(Elf32_Ehdr);
863+ break;
864+ case ELFCLASS64:
865+ n = sizeof(Elf64_Ehdr);
866+ break;
867+ }
868+ n -= EI_NIDENT;
869+
870+ if ( 0 == fstat(es->fd, &stat_b) ) {
871+ if ( stat_b.st_mtime >= prog_start_time ) {
872+ errlogPrintf("elfRead() -- WARNING: '%s' was modified after program start -- symbol information may be inaccurate or invalid\n", fname);
873+ }
874+ }
875+
876+ /* read rest */
877+ if ( n != do_read(es->fd, ehdr.e32.e_ident + EI_NIDENT, n) ) {
878+ errlogPrintf("elfRead() -- unable to read ELF ehdr: %s\n", strerror(errno));
879+ goto bail;
880+ }
881+
882+ /* seek to section header table */
883+ if ( (off_t)-1 == lseek(es->fd, (off_t) FLD(c,ehdr,e_shoff), SEEK_SET) ) {
884+ errlogPrintf("elfRead() -- unable to seek to shoff: %s\n", strerror(errno));
885+ goto bail;
886+ }
887+
888+ n = ELFCLASS32 == c ? sizeof(shdr.e32) : sizeof(shdr.e64);
889+
890+ for ( i = 0; i<FLD(c,ehdr,e_shnum); i++ ) {
891+ if ( n != do_read(es->fd, &shdr, n) ) {
892+ errlogPrintf("elfRead() -- unable to read section header: %s\n", strerror(errno));
893+ goto bail;
894+ }
895+ if ( SHT_SYMTAB == FLD(c,shdr,sh_type) )
896+ break;
897+ }
898+
899+ if ( i>=FLD(c,ehdr,e_shnum) ) {
900+ /* no SYMTAB -- try dynamic symbols */
901+
902+ if ( (off_t)-1 == lseek(es->fd, (off_t) FLD(c,ehdr,e_shoff), SEEK_SET) ) {
903+ errlogPrintf("elfRead() -- unable to seek to shoff: %s\n", strerror(errno));
904+ goto bail;
905+ }
906+
907+ for ( i = 0; i<FLD(c,ehdr,e_shnum); i++ ) {
908+ if ( n != do_read(es->fd, &shdr, n) ) {
909+ errlogPrintf("elfRead() -- unable to read section header: %s\n", strerror(errno));
910+ goto bail;
911+ }
912+ if ( SHT_DYNSYM == FLD(c,shdr,sh_type) )
913+ break;
914+ }
915+ }
916+
917+ if ( i>=FLD(c,ehdr,e_shnum) ) {
918+ errlogPrintf("elfRead() -- no symbol table found\n");
919+ goto bail;
920+ }
921+
922+ if ( 0 == (n = (ssize_t) FLD(c,shdr,sh_size)) ) {
923+ errlogPrintf("elfRead() -- no symbol table data\n");
924+ goto bail;
925+ }
926+
927+ if ( !(es->symMap = getscn(es->fd, c, &shdr)) ) {
928+ errlogPrintf("elfRead() -- unable to read ELF symtab\n");
929+ goto bail;
930+ }
931+
932+ es->nsyms = n / (ELFCLASS32==c ? sizeof(Elf32_Sym) : sizeof(Elf64_Sym));
933+
934+ /* find and read string table */
935+
936+ n = ELFCLASS32 == c ? sizeof(shdr.e32) : sizeof(shdr.e64);
937+
938+ /* seek to section header table */
939+ if ( (off_t)-1 == lseek(es->fd, (off_t) (FLD(c,ehdr,e_shoff) + n * FLD(c,shdr,sh_link)), SEEK_SET) ) {
940+ errlogPrintf("elfRead() -- unable to lseek to ELF e_shoff: %s\n", strerror(errno));
941+ goto bail;
942+ }
943+
944+ if ( n != do_read(es->fd, &shdr, n) ) {
945+ errlogPrintf("elfRead() -- unable to read ELF strtab section header: %s\n", strerror(errno));
946+ goto bail;
947+ }
948+
949+ if ( !(es->strMap = getscn(es->fd,c,&shdr)) ) {
950+ errlogPrintf("elfRead() -- unable to read ELF strtab\n");
951+ goto bail;
952+ }
953+
954+ /* Make sure there is a terminating NUL - unfortunately, memrchr is not portable */
955+ cp = (char*)es->strMap->addr + es->strMap->off;
956+ for ( idx = es->strMap->max - 1; i >= 0; i-- ) {
957+ if ( !cp[i] )
958+ break;
959+ }
960+ es->strMap->max = idx + 1;
961+
962+ switch ( FLD(c,ehdr,e_type) ) {
963+ case ET_EXEC:
964+ /* Symbols in an executable already has absolute addresses */
965+ es->addr = 0;
966+ break;
967+ case ET_DYN:
968+ /* Symbols in an shared library are relative to base address */
969+ es->addr = fbase;
970+ break;
971+ default:
972+ errlogPrintf("dlLookupAddr(): Unexpected ELF object file type %u\n", FLD(c,ehdr,e_type));
973+ goto bail;
974+ }
975+
976+ return es;
977+
978+bail:
979+ elfSymsRelease(es);
980+ return es;
981+}
982+
983+/* Destroy a cached ELF symbol table */
984+static void
985+elfSymsDestroy(ESyms es)
986+{
987+ if ( es ) {
988+ elfSymsRelease(es);
989+ free(es);
990+ }
991+}
992+
993+/* Destroy all cached ELF symbol tables
994+ *
995+ * However - w/o proper locking for read access
996+ * this must not be used. Otherwise, readers
997+ * will hold stale pointers...
998+ *
999+ * We leave the commented code here to show
1000+ * how the tables can be torn down.
1001+
1002+void
1003+elfSymTblFlush()
1004+{
1005+ESyms es;
1006+
1007+ elfsLockWrite();
1008+ while ( (es = elfs) ) {
1009+ elfs = es->next;
1010+ es->next = 0;
1011+ elfsUnlockWrite();
1012+ elfSymsDestroy(es);
1013+ elfsLockWrite();
1014+ }
1015+ elfsUnlockWrite();
1016+}
1017+
1018+*/
1019+
1020+
1021+/* This routine must be called with the write-lock held */
1022+static ESyms
1023+elfSymsFind(const char *fname)
1024+{
1025+ESyms es;
1026+ for ( es=elfs; es && strcmp(fname, es->fname); es = es->next )
1027+ /* nothing else to do */;
1028+ return es;
1029+}
1030+
1031+int
1032+epicsFindAddr(void *addr, epicsSymbol *sym_p)
1033+{
1034+Dl_info inf;
1035+ESyms es,nes = 0;
1036+uintptr_t minoff,off;
1037+size_t i;
1038+Sym sym;
1039+Sym nearest;
1040+const char *strtab;
1041+uint8_t c;
1042+size_t idx;
1043+
1044+ if ( ! dladdr(addr, &inf) || (!inf.dli_fname && !inf.dli_sname) ) {
1045+ sym_p->f_nam = 0;
1046+ sym_p->s_nam = 0;
1047+ /* unable to lookup */
1048+ return 0;
1049+ }
1050+
1051+ sym_p->f_nam = inf.dli_fname;
1052+
1053+ /* If the symbol is in the main executable then solaris' dladdr returns bogus info */
1054+#ifndef __sun
1055+ if ( (sym_p->s_nam = inf.dli_sname) ) {
1056+ sym_p->s_val = inf.dli_saddr;
1057+ /* Have a symbol name - just use it and be done */
1058+ return 0;
1059+ }
1060+#endif
1061+
1062+ /* No symbol info; try to access ELF file and ready symbol table from there */
1063+
1064+ elfsLockWrite();
1065+
1066+ /* See if we have loaded this file already */
1067+ es = elfSymsFind(inf.dli_fname);
1068+
1069+ if ( !es ) {
1070+
1071+ elfsUnlockWrite();
1072+
1073+ if ( ! (nes = elfRead(inf.dli_fname, (uintptr_t)inf.dli_fbase)) ) {
1074+ /* this path can only be taken if there is no memory for '*nes' */
1075+ return 0;
1076+ }
1077+
1078+ elfsLockWrite();
1079+
1080+ /* Has someone else intervened and already added this file while we were reading ? */
1081+ es = elfSymsFind(inf.dli_fname);
1082+
1083+ if ( es ) {
1084+ /* will undo our work in the unlikely event... */
1085+ } else {
1086+ nes->next = elfs;
1087+ es = elfs = nes;
1088+ nes = 0;
1089+ }
1090+ }
1091+
1092+ elfsUnlockWrite();
1093+
1094+ /* Undo our work in the unlikely event that it was redundant */
1095+ if ( nes )
1096+ elfSymsDestroy( nes );
1097+
1098+ nearest.raw = 0;
1099+ minoff = (uintptr_t)-1LL;
1100+
1101+ if ( es->nsyms ) {
1102+ c = es->eclss;
1103+ sym.raw = (char*)es->symMap->addr + es->symMap->off;
1104+ strtab = (char*)es->strMap->addr + es->strMap->off;
1105+
1106+ /* Do a brute-force search through the symbol table; if this is executed
1107+ * very often then it would be worthwhile constructing a sorted list of
1108+ * symbol addresses but for the stack trace we don't care...
1109+ */
1110+#if (FIND_ADDR_DEBUG & 1)
1111+ printf("Looking for %p\n", addr);
1112+#endif
1113+
1114+ if ( ELFCLASS32 == c ) {
1115+ for ( i=0; i<es->nsyms; i++ ) {
1116+ if ( STT_FUNC != ELF32_ST_TYPE(sym.e32[i].st_info) )
1117+ continue;
1118+ /* don't bother about undefined symbols */
1119+ if ( 0 == sym.e32[i].st_shndx )
1120+ continue;
1121+#if (FIND_ADDR_DEBUG & 1)
1122+ printf("Trying: %s (0x%lx)\n", strtab + sym.e32[i].st_name, (unsigned long)(sym.e32[i].st_value + es->addr));
1123+#endif
1124+ if ( (uintptr_t)addr >= (uintptr_t)sym.e32[i].st_value + es->addr ) {
1125+ off = (uintptr_t)addr - ((uintptr_t)sym.e32[i].st_value + es->addr);
1126+ if ( off < minoff ) {
1127+ minoff = off;
1128+ nearest.e32 = &sym.e32[i];
1129+ }
1130+ }
1131+ }
1132+ } else {
1133+ for ( i=0; i<es->nsyms; i++ ) {
1134+ if ( STT_FUNC != ELF64_ST_TYPE(sym.e64[i].st_info) )
1135+ continue;
1136+ /* don't bother about undefined symbols */
1137+ if ( 0 == sym.e64[i].st_shndx )
1138+ continue;
1139+#if (FIND_ADDR_DEBUG & 1)
1140+ printf("Trying: %s (0x%llx)\n", strtab + sym.e64[i].st_name, (unsigned long long)(sym.e64[i].st_value + es->addr));
1141+#endif
1142+ if ( (uintptr_t)addr >= (uintptr_t)sym.e64[i].st_value + es->addr ) {
1143+ off = (uintptr_t)addr - ((uintptr_t)sym.e64[i].st_value + es->addr);
1144+ if ( off < minoff ) {
1145+ minoff = off;
1146+ nearest.e64 = &sym.e64[i];
1147+ }
1148+ }
1149+ }
1150+ }
1151+ }
1152+
1153+ if ( nearest.raw && ( (idx = ARR(c,nearest,0,st_name)) < es->strMap->max ) ) {
1154+ sym_p->s_nam = strtab + idx;
1155+ sym_p->s_val = (char*) ARR(c, nearest, 0, st_value) + es->addr;
1156+ }
1157+
1158+ return 0;
1159+}
1160+
1161+int epicsFindAddrGetFeatures(void)
1162+{
1163+ /* The static information given here may not be correct;
1164+ * it also depends on
1165+ * - compilation (frame pointer optimization)
1166+ * - linkage (static vs. dynamic)
1167+ * - stripping
1168+ */
1169+ return EPICS_STACKTRACE_LCL_SYMBOLS
1170+ | EPICS_STACKTRACE_GBL_SYMBOLS
1171+ | EPICS_STACKTRACE_DYN_SYMBOLS;
1172+}
1173
1174=== added file 'src/libCom/osi/os/posix/osdExecinfoBackTrace.cpp'
1175--- src/libCom/osi/os/posix/osdExecinfoBackTrace.cpp 1970-01-01 00:00:00 +0000
1176+++ src/libCom/osi/os/posix/osdExecinfoBackTrace.cpp 2014-09-19 17:20:51 +0000
1177@@ -0,0 +1,18 @@
1178+/*
1179+ * Copyright: Stanford University / SLAC National Laboratory.
1180+ *
1181+ * EPICS BASE is distributed subject to a Software License Agreement found
1182+ * in file LICENSE that is included with this distribution.
1183+ *
1184+ * Author: Till Straumann <strauman@slac.stanford.edu>, 2011, 2014
1185+ */
1186+
1187+#include <execinfo.h>
1188+
1189+#define epicsExportSharedSymbols
1190+#include "epicsStackTracePvt.h"
1191+
1192+int epicsBackTrace(void **buf, int buf_sz)
1193+{
1194+ return backtrace(buf, buf_sz);
1195+}
1196
1197=== added file 'src/libCom/osi/os/solaris/osdBackTrace.cpp'
1198--- src/libCom/osi/os/solaris/osdBackTrace.cpp 1970-01-01 00:00:00 +0000
1199+++ src/libCom/osi/os/solaris/osdBackTrace.cpp 2014-09-19 17:20:51 +0000
1200@@ -0,0 +1,46 @@
1201+/*
1202+ * Copyright: Stanford University / SLAC National Laboratory.
1203+ *
1204+ * EPICS BASE is distributed subject to a Software License Agreement found
1205+ * in file LICENSE that is included with this distribution.
1206+ *
1207+ * Author: Till Straumann <strauman@slac.stanford.edu>, 2014
1208+ */
1209+
1210+#include <ucontext.h>
1211+
1212+#define epicsExportSharedSymbols
1213+#include "epicsStackTracePvt.h"
1214+
1215+struct wlk {
1216+ void **buf;
1217+ int max;
1218+ int cur;
1219+};
1220+
1221+
1222+extern "C" {
1223+
1224+static int
1225+walker(uintptr_t addr, int sig, void *arg)
1226+{
1227+struct wlk *w_p = (struct wlk *)arg;
1228+ if ( w_p->cur < w_p->max )
1229+ w_p->buf[w_p->cur++] = (void*)addr;
1230+ return 0;
1231+}
1232+
1233+}
1234+
1235+int epicsBackTrace(void **buf, int buf_sz)
1236+{
1237+ucontext_t u;
1238+struct wlk d;
1239+ d.buf = buf;
1240+ d.max = buf_sz;
1241+ d.cur = 0;
1242+ if ( getcontext(&u) )
1243+ return -1;
1244+ walkcontext( &u, walker, &d );
1245+ return d.cur;
1246+}
1247
1248=== added file 'src/libCom/osi/os/solaris/osdFindAddr.cpp'
1249--- src/libCom/osi/os/solaris/osdFindAddr.cpp 1970-01-01 00:00:00 +0000
1250+++ src/libCom/osi/os/solaris/osdFindAddr.cpp 2014-09-19 17:20:51 +0000
1251@@ -0,0 +1,10 @@
1252+/*
1253+ * Copyright: Stanford University / SLAC National Laboratory.
1254+ *
1255+ * EPICS BASE is distributed subject to a Software License Agreement found
1256+ * in file LICENSE that is included with this distribution.
1257+ *
1258+ * Author: Till Straumann <strauman@slac.stanford.edu>, 2011, 2014
1259+ */
1260+
1261+#include "osdElfFindAddr.cpp"
1262
1263=== modified file 'src/libCom/test/Makefile'
1264--- src/libCom/test/Makefile 2014-07-29 21:05:24 +0000
1265+++ src/libCom/test/Makefile 2014-09-19 17:20:51 +0000
1266@@ -184,6 +184,11 @@
1267 testHarness_SRCS += epicsMessageQueueTest.cpp
1268 TESTS += epicsMessageQueueTest
1269
1270+TESTPROD_HOST += epicsStackTraceTest
1271+epicsStackTraceTest_SRCS += epicsStackTraceTest.c
1272+testHarness_SRCS += epicsStackTraceTest.c
1273+TESTS += epicsStackTraceTest
1274+
1275
1276 # The testHarness runs all the test programs in a known working order.
1277 testHarness_SRCS += epicsRunLibComTests.c
1278
1279=== added file 'src/libCom/test/epicsStackTraceTest.c'
1280--- src/libCom/test/epicsStackTraceTest.c 1970-01-01 00:00:00 +0000
1281+++ src/libCom/test/epicsStackTraceTest.c 2014-09-19 17:20:51 +0000
1282@@ -0,0 +1,242 @@
1283+/*
1284+ * Copyright: Stanford University / SLAC National Laboratory.
1285+ *
1286+ * EPICS BASE is distributed subject to a Software License Agreement found
1287+ * in file LICENSE that is included with this distribution.
1288+ *
1289+ * Author: Till Straumann <strauman@slac.stanford.edu>, 2014
1290+ */
1291+
1292+/*
1293+ * Check stack trace functionality
1294+ */
1295+
1296+#include "epicsStackTrace.h"
1297+#include "errlog.h"
1298+#include "epicsUnitTest.h"
1299+#include "testMain.h"
1300+
1301+#include <string.h>
1302+#include <stdlib.h>
1303+#include <ctype.h>
1304+
1305+#define TST_BUFSZ 10000
1306+
1307+#define MAXP 10
1308+
1309+/* estimated size of (compiled) epicsStackTraceRecurseGbl */
1310+#define WINDOW_SZ 400
1311+
1312+static int test_debug = 0;
1313+
1314+typedef struct TestDataRec_ {
1315+ char buf[TST_BUFSZ];
1316+ int pos;
1317+} TestDataRec, *TestData;
1318+
1319+typedef void (*RecFn)(int);
1320+
1321+/* We want a stack trace and need a few nested routines.
1322+ * The whole magic here is intended to prevent a compiler
1323+ * from optimizing the call stack away:
1324+ * - call via a volatile pointer
1325+ * - add a call to a no-op function at the end so that
1326+ * tail-call optimization doesn't eliminate the call
1327+ * stack.
1328+ *
1329+ * We use a local (static) and a global routine to test
1330+ * if the stacktrace supports either flavor.
1331+ */
1332+
1333+void epicsStackTraceRecurseGbl(int lvl);
1334+static void epicsStackTraceRecurseLcl(int lvl);
1335+
1336+void nopFn(int lvl)
1337+{
1338+}
1339+
1340+RecFn volatile lfp = epicsStackTraceRecurseLcl;
1341+RecFn volatile gfp = epicsStackTraceRecurseGbl;
1342+RecFn volatile nfp = nopFn;
1343+
1344+static void
1345+epicsStackTraceRecurseLcl(int lvl)
1346+{
1347+ if ( lvl )
1348+ gfp(lvl-1);
1349+ else
1350+ epicsStackTrace();
1351+
1352+ /* call something so that the call through gfp() doesn't
1353+ * get optimized into a jump (tail-call optimization)
1354+ */
1355+ nfp(0);
1356+}
1357+
1358+void epicsStackTraceRecurseGbl(int lvl)
1359+{
1360+ if ( lvl )
1361+ lfp(lvl-1);
1362+ else
1363+ epicsStackTrace();
1364+
1365+ /* call something so that the call through gfp() doesn't
1366+ * get optimized into a jump (tail-call optimization)
1367+ */
1368+ nfp(0);
1369+}
1370+
1371+static void logClient(void *ptr, const char *msg)
1372+{
1373+TestData td = ptr;
1374+size_t sz = strlen(msg);
1375+size_t mx = sizeof(td->buf) - td->pos - 1;
1376+
1377+ if ( sz > mx )
1378+ sz = mx;
1379+ strncpy( td->buf+td->pos, msg, sz );
1380+ td->pos += sz;
1381+}
1382+
1383+static int
1384+findStringOcc(const char *buf, const char *what)
1385+{
1386+int rval;
1387+size_t l = strlen(what);
1388+int ch;
1389+
1390+ rval = 0;
1391+ while ( (buf = strstr(buf, what)) ) {
1392+ /* Is it just a prefix? */
1393+ ch = buf[l];
1394+ if ( ! isalnum(ch) && '_' != ch ) {
1395+ rval++;
1396+ }
1397+ buf += l;
1398+ }
1399+
1400+ if ( test_debug )
1401+ testDiag("found %i x %s\n", rval, what);
1402+
1403+ return rval;
1404+}
1405+
1406+static int
1407+findNumOcc(const char *buf)
1408+{
1409+void *ptrs[MAXP];
1410+int n_ptrs = 0;
1411+int i,j;
1412+int rval = 0;
1413+
1414+ while ( n_ptrs < sizeof(ptrs)/sizeof(ptrs[0]) && (buf=strchr(buf,'[')) ) {
1415+ if ( 1 == sscanf(buf+1,"%p", &ptrs[n_ptrs]) )
1416+ n_ptrs++;
1417+ buf++;
1418+ }
1419+ /* We should find an address close to epicsStackTraceRecurseGbl twice */
1420+ for (i=0; i<n_ptrs-1; i++) {
1421+ /* I got a (unjustified) index-out-of-bound warning
1422+ * when setting j=i+1 here. Thus the weird j!= i check...
1423+ */
1424+ j = i;
1425+ while ( j < n_ptrs ) {
1426+ if ( j != i && ptrs[j] == ptrs[i] ) {
1427+ if ( (char*)ptrs[i] >= (char*)epicsStackTraceRecurseGbl && (char*)ptrs[i] < (char*)epicsStackTraceRecurseGbl + WINDOW_SZ ) {
1428+ rval ++;
1429+ if ( test_debug )
1430+ testDiag("found address %p again\n", ptrs[i]);
1431+ }
1432+ }
1433+ j++;
1434+ }
1435+ }
1436+ return rval;
1437+}
1438+
1439+MAIN(epicsStackTraceTest)
1440+{
1441+int features, all_features;
1442+TestDataRec testData;
1443+int gblFound, lclFound, numFound, dynFound;
1444+char *nl, *p;
1445+
1446+ if ( getenv("EPICS_STACK_TRACE_TEST_DEBUG") )
1447+ test_debug = 1;
1448+
1449+ testData.pos = 0;
1450+
1451+ testPlan(5);
1452+
1453+ features = epicsStackTraceGetFeatures();
1454+
1455+ all_features = EPICS_STACKTRACE_LCL_SYMBOLS
1456+ | EPICS_STACKTRACE_GBL_SYMBOLS
1457+ | EPICS_STACKTRACE_DYN_SYMBOLS
1458+ | EPICS_STACKTRACE_ADDRESSES;
1459+
1460+ if ( ! testOk( (features & ~all_features) == 0,
1461+ "epicsStackTraceGetFeatures() obtains features") )
1462+ testAbort("epicsStackTraceGetFeatures() not working as expected");
1463+
1464+ testData.pos = 0;
1465+
1466+ testDiag("calling a few nested routines and eventually dump a stack trace");
1467+
1468+ eltc(0);
1469+ errlogAddListener( logClient, &testData );
1470+ gfp(3);
1471+ errlogRemoveListeners( logClient, &testData );
1472+ eltc(1);
1473+
1474+ /* ensure there's a terminating NUL -- we have reserved space for it */
1475+ testData.buf[testData.pos] = 0;
1476+
1477+ testDiag("now scan the result for what we expect");
1478+
1479+ dynFound = findStringOcc( testData.buf, "epicsStackTrace" );
1480+ gblFound = findStringOcc( testData.buf, "epicsStackTraceRecurseGbl" );
1481+ lclFound = findStringOcc( testData.buf, "epicsStackTraceRecurseLcl" );
1482+ numFound = findNumOcc ( testData.buf );
1483+
1484+ if ( (features & EPICS_STACKTRACE_DYN_SYMBOLS) ) {
1485+ testOk( dynFound == 1, "dumping symbol from library" );
1486+ } else {
1487+ testOk( 1 , "no support for dumping library symbols on this platform");
1488+ }
1489+
1490+
1491+ if ( (features & EPICS_STACKTRACE_GBL_SYMBOLS) ) {
1492+ testOk( gblFound == 2, "dumping global symbols" );
1493+ } else {
1494+ testOk( 1 , "no support for dumping global symbols on this platform");
1495+ }
1496+
1497+ if ( (features & EPICS_STACKTRACE_LCL_SYMBOLS) ) {
1498+ testOk( lclFound == 2, "dumping local symbols" );
1499+ } else {
1500+ testOk( 1 , "no support for dumping local symbols on this platform");
1501+ }
1502+
1503+ if ( (features & EPICS_STACKTRACE_ADDRESSES) ) {
1504+ testOk( numFound > 0, "dumping addresses" );
1505+ } else {
1506+ testOk( 1 , "no support for dumping addresses on this platform");
1507+ }
1508+
1509+
1510+ if ( test_debug ) {
1511+ p = testData.buf;
1512+ while ( (nl = strchr(p,'\n')) ) {
1513+ *nl = 0;
1514+ testDiag("%s",p);
1515+ *nl = '\n';
1516+ p = nl+1;
1517+ }
1518+ testDiag("%s", p);
1519+ }
1520+
1521+ testDone();
1522+
1523+ return 0;
1524+}

Subscribers

People subscribed via source and target branches