Prior to calling makecontext
why do we need to set the stack size ss_size
?
I just had an unit test case for makecontext/swapcontext
snippet and it failed with SIGSEGV
. What happened was that stack size was too small and unrelated memory (happened to be some unique pointers) got corrupted and reported segfault. So the segfault was on these unrelated pointers, I could have had e.g. some string and then the memory corruption would have been unnoticed.
I would have expected that SIGSEGV
is beeing raised immediately when stack size ss_size
does not suffice, but
considering the memory corruption described above, I conclude its impossible to recover from SIGSEGV
here. That brings me back to the question, why do we need to set the stack size then in first place, when it is not being used to signal overflows? What is it used for?
EDIT:
Well it's all about makecontext(3). These functions are still being used for green threads, coroutines etc. There is just no real replacement for them considering these tasks (in my opinion) also not in c++.
ss_size
defined in sigaltstack(2) is being needed for uc_stack
in ucontext_t
defined in getcontext(3).
Following a minimal verifiable example that shows the memory corruption, by "painting" the memory, described above.
#include <iostream>
#include <ucontext.h>
#include <memory>
#include <cstring>
#include <stdio.h>
#include <unistd.h>
ucontext_t caller, callee;
void cb(void){
//paint stack with 2
char tmp[7000];
std::memset(tmp,2,7000);
//note stack size specified 6k bytes in size
//this should not be allowed.
//furthermore there is not even some signal raised here
//i expected raised SIGSEGV when this call stack exceeds ss_size
//it makes ss_size useless no?
}
int main(){
//
std::memset(&caller,0,sizeof(caller));
std::memset(&callee,0,sizeof(callee));
//create stack and paint 0
std::unique_ptr<std::byte[]> stack(new std::byte[10000]());
std::memset(stack.get(),0,10000);//paint stack 0
//make context
//note stack specified to [2000,8000)
//that means [0,2000) and [8000,10000) should not be touched
if(getcontext(&callee) == -1) {std::cout << errno << ":" << std::strerror(errno) << std::endl; return 1;}
callee.uc_link = &caller;
callee.uc_stack.ss_sp = stack.get()+2000;
callee.uc_stack.ss_size = 6000; //what is this line good for, what is it guarding?
makecontext(&callee,cb,0);
//swap to callee
if(swapcontext(&caller,&callee) == -1) {std::cout << errno << ":" << std::strerror(errno) << std::endl; return 1;}
//print color - should be 0
//if 2 then memory corrupted by callee
std::cout << int(stack[996]) << std::endl;
std::cout << int(stack[997]) << std::endl;
std::cout << int(stack[998]) << std::endl;
std::cout << int(stack[999]) << std::endl;
return 0;
}
Once again what I don't understand is why we need to set the stack size ss_size
, because it looks like that it is not being used to guard against memory corruption or anything else. It looks like it is just there to be there but without any use. But I can't believe that it has no use. So what is it "guarding" / good for?
Well, I don't want to bring more confusion into this. The goal is to get away from a fixed size function call stack by either being able to recover by installing SIGSEGV
signal handler, but this looks like mission impossible due to this memory corruption; or to have a growable stack e.g. using mmap(2) with MAP_GROWSDOWN
flag, but this looks broken and therefore not an option.