I have a problem with custom SIGSEGV handlers. It seems that on
an X86 box (I haven't checked other CPUs), that a segfault
caused by the %esp register growing down and touching a
protected page, does not call the registered handler.
Linux version is 2.4.9-21.
In the attached small test program, I want to test out
a 'stack' structure. It allocates a range by mmaping
and then protects a page, just under the first page
of the stack.
If you enable CASE2 and disable CASE1 by commenting it
out. You will see the behaviour I want, but it doesn't use
the stack pointer register... This behaviour
is induced by filling a pointer and growing it downwards,
towards lower memory addresses. The handler is called
untill the final page has been unprotected and it will give
up. What follows is the normal output:
result = 0
pagesize 4096 bytes
here...
Stack capacity is 3
would get 16384 bytes starting from range 0x40019000
start would then be 0x4001c000
ALL is OK here....
OK, top 0x4001cffc, first 0x4001c000, guard 0x4001b000, final
0x40019000, capacity 3
OK, 0
OK, 256
OK, 512
OK, 768
OK, 1024
Segfault caught 11, 0xbfffe620 0xbfffe6a0, address 0x4001bffc, si_code =
2
SEGV_MAPERR = 1, SEGV_ACCERR = 2, errno = 0
Used 2 pages
OK, 1280
OK, 1536
OK, 1792
OK, 2048
Segfault caught 11, 0xbfffe620 0xbfffe6a0, address 0x4001affc, si_code =
2
SEGV_MAPERR = 1, SEGV_ACCERR = 2, errno = 0
Final call...
Used 3 pages
OK, 2304
OK, 2560
OK, 2816
OK, 3072
Segfault caught 11, 0xbfffe620 0xbfffe6a0, address 0x40019ffc, si_code =
2
SEGV_MAPERR = 1, SEGV_ACCERR = 2, errno = 0
Givin up...
So the segfault is called and the page unprotected. If in the code,
you would enable CASE1, the segfault handler is not called, although,
when you would define MUNGESIZE to a value of e.g. 4, it works, since
the single page is large enough to hold the stack. The output with
MUNGESIZE 100 and CASE1 enabled is:
result = 0
pagesize 4096 bytes
here...
Stack capacity is 3
would get 16384 bytes starting from range 0x40019000
start would then be 0x4001c000
ALL is OK here....
OK, top 0x4001cffc, first 0x4001c000, guard 0x4001b000, final
0x40019000, capacity 3
Before esp = 0xbfffe588
After esp = 0x4001cff4
this is a test sp... sp = 0x4001cfd4 arg address = 0x4001cff0
Segmentation fault
If you would do an 'info reg' from within a gdb session, the
stack pointer register contains:
esp 0x4001be8c
So it touches a protected page.
The question; "How can I handle these stack overflow situations
in the most portable way, i.e. using mmap (eventually mprotect)
and have some consistent behaviour accross OS's and CPU's?" So
having a new system call is not an option. I even want this stuff
to work under the MS Windows family...
Regards & Thanks,
Sample program follows:
/* --------------------- */
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
/*
** CASE1 will trigger a segmentation violation when MUNGESIZE is set
high enough. The segfault
** induced will refer to a stack push, i.e. involve the stack pointer
register, but will NOT
** call our custom stackfault handler...
*/
#define CASE1
#define MUNGESIZE 100 // Try e.g. 4 and the test program for case1 will
work, e.g. 100 will fail
/*
** Case2 will fill a stack 'manually', growing down, with values and
will call our segfault
** handler 'nicely' as it should be...
*/
#define CASE2
static struct sigaction segfault;
static int pagesize;
/*
Stack example layout, stack grows down, starts at top of first page,
rounded down...
+-------------------+ -+--+----
| | | | top = 0x7ffc
| | | |
first --->| | | | 0x7000
+-------------------+ | |
| | = size 0x3000
| | | |
| | | | 0x6000
+-------------------+ | |
| | | |
| | | = maxsize 0x5000
used = 3 | | | | 0x5000
+-------------------+ -+- |
| | |
| GUARD PAGE | |
guard --->| | | 0x4000
+-------------------+ |
| | |
| (unused) | |
capacity = 5 | | | 0x3000
+-------------------+ ----+----
| | |
| FINAL PAGE | = pagesize
final --->| | | 0x2000
+-------------------+ ----+----
*/
typedef struct x_Stack * x_stack;
typedef struct x_Stack {
x_stack next;
x_stack previous;
void * thread; /* The thread we are associated with.
*/
unsigned int * top; /* Address of the first word of the
stack that is useable. */
unsigned char * first; /* Address of first page of the stack.
*/
unsigned char * guard; /* Current guard page that is
write/read protected. */
unsigned char * final; /* Final protection page; only used
for checking overruns. */
unsigned short used; /* Number of pages allocated. */
unsigned short capacity; /* Maximum number of pages allocatable
for stack use. */
} x_Stack;
static x_Stack Stack;
inline static unsigned int round_up(unsigned int value, unsigned int
rounding) {
return (value + (rounding - 1)) & ~(rounding - 1);
}
inline static unsigned int round_down(unsigned int value, unsigned int
rounding) {
return (value) & (unsigned int)(0 - rounding);
}
int x_stack_delete(x_stack stack) {
munmap(stack->final, (stack->capacity + 1) * pagesize);
}
int x_stack_create(x_stack stack, unsigned int size, void * thread) {
unsigned char * start;
size = round_up(size, pagesize);
stack->thread = thread;
stack->capacity = size / pagesize;
printf("Stack capacity is %d\n", stack->capacity);
/*
** Let Linux decide where she wants to allocate this virtual range, in
one swoop. We
** will chop it up later below and add guarding pages. Note that we
allocate 1 extra
** page for the final guard page. We have to do this workaround in
case somebody wants
** to use pthreads in some native code. Yes, these lunatics do
exist...
**
** The mapping we do first, for the complete area will be
automatically unmapped in the
** lines following, because we use the MAP_FIXED flag. On Linux, we
don't need to unmap
** explicitely; I wonder how other OS's implement their variation of
the mmap call...
*/
start = mmap(NULL, (stack->capacity + 1) * pagesize, PROT_NONE,
MAP_ANON | MAP_PRIVATE, -1, 0);
printf("would get %d bytes starting from range %p\n", (stack->capacity
+ 1) * pagesize, start);
start += stack->capacity * pagesize;
printf("start would then be %p\n", start);
/*
** Allocate a single page; the top pointer of the page is rounded down
to a word pointer
** and is the starting point for the stack, on which elements are
pushed down to lower
** addresses.
*/
stack->first = mmap(start, pagesize, PROT_READ | PROT_WRITE |
PROT_EXEC, MAP_FIXED | MAP_ANON | MAP_PRIVATE, -1, 0);
if (stack->first == MAP_FAILED) {
return 1;
}
stack->used = 1;
stack->top = (unsigned int *) round_down((unsigned int)(stack->first +
pagesize - 1), 4);
/*
** Allocate a guard page to trap overwrites, just after the first
page; 'after' must be intepreted
** in a downwards growing stack. So 'after' means, lower in memory.
Therefore the minus sign.
*/
stack->guard = mmap(stack->first - pagesize, pagesize, PROT_NONE,
MAP_FIXED | MAP_ANON | MAP_PRIVATE, -1, 0);
if (stack->guard == MAP_FAILED) {
return 1;
}
/*
** Allocate a page that acts as the unmoveable barrier for stack
growth. This page signals the
** final call for more stack...
*/
stack->final = mmap(stack->first - (pagesize * stack->capacity),
pagesize, PROT_NONE, MAP_FIXED | MAP_ANON, -1, 0);
if (stack->final == MAP_FAILED) {
return 1;
}
printf("ALL is OK here....\n");
printf("OK, top %p, first %p, guard %p, final %p, capacity %d\n",
stack->top, stack->first, stack->guard, stack->final, stack->capacity);
return 0;
}
void stack_segfault_handler(int signum, siginfo_t * sis, void * arg) {
x_stack stack = &Stack;
unsigned char * page;
printf("Segfault caught %d, %p %p, address %p, si_code = %d\n",
signum, sis, arg, sis->si_addr, sis->si_code);
printf("SEGV_MAPERR = %d, SEGV_ACCERR = %d, errno = %d\n",
SEGV_MAPERR, SEGV_ACCERR, sis->si_errno);
if (stack->guard == stack->final) {
printf("Givin up...\n");
exit(0);
}
else {
page = mmap(stack->guard, pagesize, PROT_READ | PROT_WRITE |
PROT_EXEC, MAP_FIXED | MAP_ANON | MAP_PRIVATE, -1, 0);
if (page == MAP_FAILED) {
printf("failed 1\n");
}
stack->used += 1;
page = mmap(stack->guard - pagesize, pagesize, PROT_NONE, MAP_FIXED
| MAP_ANON | MAP_PRIVATE, -1, 0);
if (page == MAP_FAILED) {
printf("failed 2\n");
}
stack->guard = page;
if (stack->guard == stack->final) {
printf("Final call...\n");
}
}
printf("Used %d pages\n", stack->used);
}
void munger(int number) {
int words[MUNGESIZE];
int i;
if(number != 0) {
munger(number-1);
}
printf("munger %d\n", number);
if (number == 0) {
printf("Unwinding...\n");
return;
}
else {
printf("Pushing... %d\n", number);
}
for (i = 0; i < MUNGESIZE; i++) {
words[i] = number - 1;
}
munger(words[0]);
}
void test(void * arg) {
register int * stackpointer asm("%esp");
printf("this is a test sp... sp = %p arg address = %p\n",
stackpointer, arg);
munger(5);
}
int main(int argc, char * argv[]) {
size_t ps = getpagesize();
void * start;
int result;
int i;
int z;
int * fill;
register int * stackpointer asm("%esp");
segfault.sa_sigaction = stack_segfault_handler;
segfault.sa_flags = SA_SIGINFO;
result = sigaction(SIGSEGV, &segfault, NULL);
printf("result = %d\n", result);
pagesize = ps;
printf("pagesize %d bytes\n", pagesize);
printf("here...\n");
x_stack_create(&Stack, (1024 * 12) - 100, NULL);
#ifdef CASE1
printf("Before esp = %p\n", stackpointer);
stackpointer = Stack.top;
printf("After esp = %p\n", stackpointer);
test(stackpointer);
#endif
#ifdef CASE2
fill = Stack.top;
for (i = 0; i < 1024 * 12; i++) {
if (i % 256 == 0) {
printf("OK, %d\n", i);
}
*fill-- = 1;
}
#endif
return 0;
}
--
Steven Buytaert steven....@acunia.com
Imagination is more important than knowledge. (A. Einstein)
Sounds reasonable. Where did you expect the stackframe for the
signal handler to be placed when there is no more free space
on the stack?
--
Kasper Dupont -- der bruger for meget tid på usenet.
For sending spam use mailto:razor-...@daimi.au.dk
Cheers,
Juergen
--
\ Real name : Juergen Heinzl \ no flames /
\ EMail Private : jue...@manannan.org \ send money instead /
Sure, I actually knew that would be possible. But since the man
page about sigaction didn't mention that at all, I didn't think
that feature existed.
Perhaps it is about time the sigaction man page gets updated to
include sigaltstack in the SEE ALSO section.
The original program didn't use sigaltstack(), perhaps that
would solve the problem.
In the program included, I replace the original stack, with
my own one. The signal handler should (I think) run on that
original stack. The stack I did provide myself in the program
does exist, but it is write protected, so the signal handler
can solve the problem perfectly, by unprotecting the
page.
On CPUs like the ARM, there exists different stacks for
handling exceptions (irq, software interrupt, ...).
So, I think my question is still valid; there is a
situation that I can solve from within a handler.
Thanks for the reply.
I'll look into this call, although I see it is only for
XPG4-UNIX compatability. I still would search for a more
portable solution, though. I intend to use this stack
stuff for my own scheduler OS, called Oswald, that drives
a VM. I'm looking for a portable way to throw StackOverflow
as an exception when the stack space has been exhausted.
Other threads running (from within a single user process
on Linux) should not be bothered by this exception.
Thanks for your view on the subject.
But it doesn't have a pointer to the free part of the original
stack. You have changed the pointer to your own stack lower in
memory. From this stack pointer it is not possible to see if
the stack hass grown all the way down or has been moved. From
the memory mappings the hole between the stacks could be seen,
but this doesn't tell us where the free memory in the stack is.
> The stack I did provide myself in the program
> does exist, but it is write protected, so the signal handler
> can solve the problem perfectly, by unprotecting the
> page.
Sure, if you could get a handler executed it could do so. Of
course the kernel is not going to unprotect a write protected
page on an attempt to write.
>
> On CPUs like the ARM, there exists different stacks for
> handling exceptions (irq, software interrupt, ...).
I don't know how Linux on ARM invokes signal handlers, but I
wouldn't expect it to be very different from on x86. The
alternate stack for signal handlers really shouldn't be
architecture dependend, it can be implented on any
architecture where Linux can run. It is really just a matter
of changing the userspace stackpointer before starting the
handler. (And of course save the old stackpointer in the
context.)
>
> So, I think my question is still valid; there is a
> situation that I can solve from within a handler.
Sure, what you need is a way to have the signal handler use
an alternate stack.
I have to thank both Kasper and Juergen! I have tried the
proposed solution with the sigaltstack call and it works.
If anybody is interested, I can re-post the small program
with a working solution.
I'm still wondering and would appreciate any insight, on how
this can be done on a MS Windows OS. Signals are pretty
standard :-/ but the protection mechanisms are very different.
How standard are the sigaltstack and SA_ONSTACK flags for signal
handling, for example?
Kind regards,