Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Using sigsetjmp/siglongjmp from multithreaded application

934 views
Skip to first unread message

Michael Podolsky

unread,
Jul 20, 2012, 5:42:22 PM7/20/12
to
Hi All,

I am writing a stack tracing code and to make this code safe I want to protect myself from a SIGSEGV.

So I chose to process SIGSEGV and jump from a signal handler to a position set by sigsetjmp. Then, to my disappointment I found that there is no straightforward solution to associate a sigjmp_buf buffer with a particular thread.

I can't use standard thread local storage or implement it by myself with mutexes because these functions should not be called from a signal hanlder.

And, I don't want to run stack tracing code under a mutex as the performance of application may degrade -- otherwise I could solve my task by having one thread only at risk of SIGSEGV and one sigjmp_buf per whole application.

So, going back, is there a robust way in linux to write a multithread application in which some critical parts of code are SIGSEGV-tolerant?

Thanks in advance!

Ersek, Laszlo

unread,
Jul 20, 2012, 9:32:00 PM7/20/12
to
On Fri, 20 Jul 2012, Michael Podolsky wrote:

> to my disappointment I found that there is no straightforward solution
> to associate a sigjmp_buf buffer with a particular thread.

I'm not sure if it's a good idea, but I can think of three hacks:


(1)
<http://gcc.gnu.org/onlinedocs/gcc/Thread_002dLocal.html#Thread_002dLocal>


(2)
<http://pubs.opengroup.org/onlinepubs/000095399/functions/sigaltstack.html>
<http://pubs.opengroup.org/onlinepubs/000095399/functions/sigaction.html>

(I'm referencing v3 of the SUS on purpose, because I think it might be
better supported in practice.)

#define _XOPEN_SOURCE 600

#include <signal.h>
#include <stdlib.h>
#include <setjmp.h>


static char unsigned *stacks;
static sigjmp_buf *bufs;


static void
handler(int);


static void
main_setup(size_t num_threads)
{
struct sigaction sa;

stacks = malloc(num_threads * SIGSTKSZ);
bufs = malloc(num_threads * sizeof *bufs);

sa.sa_handler = &handler;
if (-1 == sigemptyset(&sa.sa_mask)) {
abort();
}
sa.sa_flags = SA_ONSTACK;
if (-1 == sigaction(SIGSEGV, &sa, 0)) {
abort();
}
}


static void
thread_setup(unsigned my_id)
{
stack_t stk;

stk.ss_sp = stacks + my_id * SIGSTKSZ;
stk.ss_size = SIGSTKSZ;
stk.ss_flags = 0;

if (-1 == sigaltstack(&stk, 0)) {
abort();
}
}


static void
handler(int signo)
{
stack_t stk;
size_t my_id;

if (-1 == sigaltstack(0, &stk) || 0 == (stk.ss_flags & SS_ONSTACK)) {
abort();
}
my_id = ((char unsigned *)stk.ss_sp - stacks) / SIGSTKSZ;
siglongjmp(bufs[my_id], 1);
abort();
}

(sigaltstack() is not listed as async signal safe, but I'm not sure how
SS_ONSTACK can make sense otherwise.)

(3)

#define _XOPEN_SOURCE 600

#include <signal.h>
#include <stdlib.h>
#include <setjmp.h>
#include <pthread.h>
#include <ucontext.h>


static void **sps;
static sigjmp_buf *bufs;
static pthread_barrier_t init;


static void
handler(int, siginfo_t *, void *);


static void
main_setup(size_t num_threads)
{
struct sigaction sa;

sps = malloc((num_threads + 1u) * sizeof *sps);
sps[num_threads] = 0;

bufs = malloc(num_threads * sizeof *bufs);

if (-1 == sigemptyset(&sa.sa_mask)) {
abort();
}
sa.sa_flags = SA_SIGINFO;
sa.sa_sigaction = handler;
if (-1 == sigaction(SIGSEGV, &sa, 0)) {
abort();
}

if (-1 == pthread_barrier_init(&init, 0, num_threads)) {
abort();
}
}


static void
thread_setup(unsigned my_id)
{
ucontext_t context;

getcontext(&context);
sps[my_id] = context.uc_stack.ss_sp;
pthread_barrier_wait(&init);
}


static void
handler(int signo, siginfo_t *info, void *context)
{
void *my_sp;
size_t my_id;

my_sp = ((ucontext_t *)context)->uc_stack.ss_sp;
for (my_id = 0; 0 != sps[my_id]; ++my_id) {
if (sps[my_id] == my_sp) {
siglongjmp(bufs[my_id], 1);
}
}
abort();
}



Laszlo

Ersek, Laszlo

unread,
Jul 20, 2012, 10:05:57 PM7/20/12
to
On Sat, 21 Jul 2012, Ersek, Laszlo wrote:

> On Fri, 20 Jul 2012, Michael Podolsky wrote:
>
>> to my disappointment I found that there is no straightforward solution to
>> associate a sigjmp_buf buffer with a particular thread.
>
> I'm not sure if it's a good idea, but I can think of three hacks:

(Of course you could search a table with pthread_self() + pthread_equal()
as well, but neither is required to be async signal safe... Then again,
nor is siglongjmp().)

Laszlo

Marcel Müller

unread,
Jul 21, 2012, 3:10:18 AM7/21/12
to
Hi,

On 20.07.2012 23:42, Michael Podolsky wrote:
> I am writing a stack tracing code and to make this code safe I want to protect myself from a SIGSEGV.
>
> So I chose to process SIGSEGV and jump from a signal handler to a position set by sigsetjmp. Then, to my disappointment I found that there is no straightforward solution to associate a sigjmp_buf buffer with a particular thread.
>
> I can't use standard thread local storage or implement it by myself with mutexes because these functions should not be called from a signal hanlder.

Depending on your platform, it might be not that complicated.

First of all, some platforms always use rather small thread IDs. In this
case a reasonably large static array would do well. The array index is
the thread ID. While this is not the best memory layout with respect to
multi core cache performance, as long as write access comes not that
often you are fine. OK, if you want to serve any 16 bit thread ID it can
take in the order of 10 MB. But it is only virtual address space unless
you access it.

Secondly you could use lock free data structures. A common approach is
to have some kind of dictionary which is atomically replaced by a new
one with CompareAndSwap. Once a dictionary is "published" it is
immutable. However, its values are thread-local and therefore might be
accessed without a mutex. The drawback is that the life-time management
of the dictionaries is not that easy since you need strong thread safety.
If you need not to serve a large number of threads a single, lock-free
linked list may be sufficient too. The only drawback is the O(n) access
to the thread local storage.

Last but not least you could simply use a spin-lock. As long as your
critical sections are quite small, you will not run into significant
trouble.


> And, I don't want to run stack tracing code under a mutex as the performance of application may degrade -- otherwise I could solve my task by having one thread only at risk of SIGSEGV and one sigjmp_buf per whole application.

As far as I can see, the TLS is only accessed occasionally when you set
the jump target or when SIGSEGV occurs. So I do not see where the
performance counts much.


> So, going back, is there a robust way in linux to write a multithread application in which some critical parts of code are SIGSEGV-tolerant?

Of course, see above. It depends on how much effort you like to spent.


Marcel

Michael Podolsky

unread,
Jul 24, 2012, 1:22:26 PM7/24/12
to
Thanks for providing so diverse approaches and detailed
implementations. It was quite interesting for me to read through this
code.
As for the first approach you presented, I can't use language-level
tls, as it is not supported on my embedded linux platform.

As for the 2nd and 3rd they look wonderful! One limitation (may be,
more of academical sort) puzzles me here - if there is a way to
run this code for a case when a possible number of threads in a
process is unlimited or not known in advance. Of course,
some lock free container may be used for such a task, yet
unfortunately I have no CAS on my platform and, besides, implementing
lock
free container for such a task looks overkill a bit. I could think of
a linked list container in which elements are added to its tail and
never removed,
so that the elements of list may be reused by


BTW, I never realized siglongjmp is not safe to call from sig handler.

Thanks again for your help,

Michael

Michael Podolsky

unread,
Jul 24, 2012, 1:34:32 PM7/24/12
to
On Jul 21, 3:10 am, Marcel Müller <news.5.ma...@spamgourmet.com>
wrote:
> Hi,
>
> On 20.07.2012 23:42, Michael Podolsky wrote:
>
> > I am writing a stack tracing code and to make this code safe I want to protect myself from a SIGSEGV.
>
> > So I chose to process SIGSEGV and jump from a signal handler to a position set by sigsetjmp. Then, to my disappointment I found that there is no straightforward solution to associate a sigjmp_buf buffer with a particular thread.
>
> > I can't use standard thread local storage or implement it by myself with mutexes because these functions should not be called from a signal hanlder.
>
> Depending on your platform, it might be not that complicated.
>
> First of all, some platforms always use rather small thread IDs. In this
> case a reasonably large static array would do well.

I doubt if I take a risk to use a thread ID as an index to an array of
fixed size. May be as a last resort...

> Secondly you could use lock free data structures.

Yep. I thought of it generally. Yet it looks a little overkill for
such a task, and, besides, I have no CAS on my platform.

> Last but not least you could simply use a spin-lock. As long as your
> critical sections are quite small, you will not run into significant
> trouble.

Somehow it fell completely out of my mind. Thanks for pointing at this
option.


> > And, I don't want to run stack tracing code under a mutex as the performance of application may degrade -- otherwise I could solve my task by having one thread only at risk of SIGSEGV and one sigjmp_buf per whole application.
>
> As far as I can see, the TLS is only accessed occasionally when you set
> the jump target or when SIGSEGV occurs. So I do not see where the
> performance counts much.

Oh, I just meant a different thing. To resolve a problem of a TLS
unaccessible from sighandler, I could decide to have only one
sigjmp_buf per application and allow (with a help of mutex) only one
thread to access the code which is allowed to SIGSEGV. But as this
code is relatively long and many threads may call it, an unnecessary
serialization and performance degradation may happen. I did not mean
performance degradation because of TLS usage.


> Of course, see above. It depends on how much effort you like to spent.

Yep. Implementing lock-free data is a fun. I could try it, but I don't
have CAS on my platform. ((

Anyway, now I understand the options in much better way.

Thanks,
Michael


Marcel Müller

unread,
Jul 24, 2012, 4:58:43 PM7/24/12
to
On 24.07.2012 19:34, Michael Podolsky wrote:
>> Secondly you could use lock free data structures.
>
> Yep. I thought of it generally. Yet it looks a little overkill for
> such a task, and, besides, I have no CAS on my platform.

Oh, I wonder if semaphores are possible at all without CAS. AFAIK
without CAS (or something similar) you are restricted to one CPU core.

>> Last but not least you could simply use a spin-lock. As long as your
>> critical sections are quite small, you will not run into significant
>> trouble.
>
> Somehow it fell completely out of my mind. Thanks for pointing at this
> option.

Hmm, the basic instruction of a spin-lock is CAS, isn't it?


Marcel

Drazen Kacar

unread,
Jul 24, 2012, 5:23:29 PM7/24/12
to
Marcel M�ller wrote:
> On 24.07.2012 19:34, Michael Podolsky wrote:
> >> Secondly you could use lock free data structures.
> >
> > Yep. I thought of it generally. Yet it looks a little overkill for
> > such a task, and, besides, I have no CAS on my platform.
>
> Oh, I wonder if semaphores are possible at all without CAS. AFAIK
> without CAS (or something similar) you are restricted to one CPU core.

There are software algorithms for mutual exclusion which don't rely on
read-modify-write instructions, like Dekker's or Lamport's algorithms.
Dekker's doesn't work with non-sequential memory model, but processors
which reorder memory access usually have read-modify-write instructions,
so it's not an issue.

--
.-. .-. Yes, I am an agent of Satan, but my duties are largely
(_ \ / _) ceremonial.
|
| da...@fly.srk.fer.hr

Michael Podolsky

unread,
Jul 24, 2012, 6:48:09 PM7/24/12
to
On Jul 24, 4:58 pm, Marcel Müller <news.5.ma...@spamgourmet.com>
wrote:

> Oh, I wonder if semaphores are possible at all without CAS. AFAIK
> without CAS (or something similar) you are restricted to one CPU core.

I actually have one core. Even if you have many cores, you can
implement
the kernel and all the things around having a spinlock.

> Hmm, the basic instruction of a spin-lock is CAS, isn't it?

No it is not. For instance, for i86, "lock xchg" is enough. Just have
a zero value for non-locked spinlock and exchange it with 1 when you
attempt locking. Then analyze the old value. ))

Thanks,
Michael

Ersek, Laszlo

unread,
Jul 24, 2012, 9:28:31 PM7/24/12
to
On Tue, 24 Jul 2012, Michael Podolsky wrote:

> BTW, I never realized siglongjmp is not safe to call from sig handler.

I may have misrepresented it.

http://pubs.opengroup.org/onlinepubs/9699919799/functions/sigaction.html#tag_16_540_07

APPLICATION USAGE

[...] Note that longjmp() and siglongjmp() are not in the list of
async-signal-safe functions. This is because the code executing after
longjmp() and siglongjmp() can call any unsafe functions with the same
danger as calling those unsafe functions directly from the signal
handler. Applications that use longjmp() and siglongjmp() from within
signal handlers require rigorous protection in order to be portable.
[...]

AFAIK this section ("application usage") is informative, not normative,
but at any rate it seems to imply that leaving a handler with siglongjmp()
is not undefined behavior per se. Lack of the safety guarantee does not
imply undefined behavior immediately.

http://pubs.opengroup.org/onlinepubs/9699919799/functions/V2_chap02.html#tag_15_04_03

[...] All functions not in the above table are considered to be unsafe
with respect to signals. In the presence of signals, all functions
defined by this volume of POSIX.1-2008 shall behave as defined when called
from or interrupted by a signal-catching function, with a single
exception: when a signal interrupts an unsafe function and the
signal-catching function calls an unsafe function, the behavior is
undefined. [...]

For example, if the thread is currently blocked in pause() on the "normal"
stack, and it is interrupted by delivery of a caught signal, then it
should be perfectly safe to leave the handler with siglongjmp(), because
pause() itself is safe. Whatever runs after sigsetjmp() returns can't
interfere with pause().

Laszlo

gyh...@ucdavis.edu

unread,
Mar 31, 2015, 2:58:46 PM3/31/15
to
Hi
I met a similar issue. Finally I use thread specific key as follows:
function(){
Sigjmp_buf localbuf;
pthread_setspecific(pthread_key_t key, (const void *)&localbuf);

if (sigsetjmp(localbuf, 0) != 0) {
//assign error
//return error
}
//done with everything
pthread_setspecific(pthread_key_t key, NULL);
}

void signal_handler(int sig)
{
if (sig == SIGSEGV){
void * sigjmplocal =pthread_getspecific(pthread_key_t key);
if (sigjmplocal)
siglongjmp(*(Sigjmp_buf*)sigjmplocal, 1);

}
}

From book, SIGSEGV is sent to the thread that triggers it.
Is it safe to use this approach? It seems working with simple UT. But I can not foresee its risk.

Thanks a lot.

Douglas Wells (USENET)

unread,
Apr 1, 2015, 4:03:21 PM4/1/15
to
(I'm retaining the three-year old original article.)

In article <d0f72c4d-f70c-440e...@googlegroups.com>,
No, not according to the rules established by the earlier poster:
>>I can't use standard thread local storage or implement it by
>>myself with mutexes because these functions should not be
>>called from a signal hanlder.

You're using the function pthread_setspecific in a signal handler.
This is not allowed by POSIX. (And I believe that Linux just follows
POSIX in this regard.) There are only a very few functions that you
are allowed to execute in a signal handler. Look up the concept
"async-signal-safe." You might start in section "2.4.3 Signal
Actions" of
<http://pubs.opengroup.org/onlinepubs/9699919799/functions/V2_chap02.html#tag_15_04_04>.

It's not at all clear that there is a way to solve this problem in
general. Here are a few suggestions:
- Your current code is likely (but not guaranteed) to work on many
current platforms, including Linux. You could just live with
that restriction.
- If your rate of thread creation is low, you might be able to
create a static, shared list of task-id to storage pointer
associations (i.e., reimplement pthread's thread-specific
storage). pthread_self() IS on the list of routines allowed to
be used in a signal handler. But, the maintenance of this list
will be complicated and require lots of use of atomic-variables
in order to comply with POSIX.
- It's possible that the thread-storage mechanism in C11 and C++11
can be used for this. The standard is not clear to me. Some
sections hint that it might be supported; other hint that it
might not. I don't know of an interpretation on this.

Good luck,

- dmw
--
Douglas Wells . Connection Technologies .
Internet: -sp201504- -at- -gmail.com- .
0 new messages