Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stack overflow: stack trace and nix debugger support #8879

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions src/libexpr/eval.cc
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@

#include <sys/resource.h>
#include <nlohmann/json.hpp>
#include <pthread.h>

#if HAVE_BOEHMGC

Expand All @@ -45,6 +46,47 @@ using json = nlohmann::json;

namespace nix {

// Custom stack protection in addition to virtual memory-assisted stack overflow check (nix::sigsegvHandler)
// This has the benefit of being able to throw a proper exception, and support the debugger.

// TODO: move into evalstate
static void * stackProtectLow = nullptr;
static void * stackProtectHigh = nullptr;

// TODO: make public in evalstate. setCurrentStack?
static void initStack() {
pthread_attr_t attr;
size_t stackSize = 0;
void * stackLow = nullptr;
size_t guardSize = 0;

pthread_t self = pthread_self();
pthread_getattr_np(self, &attr);

pthread_attr_getstack(&attr, &stackLow, &stackSize);

// The guard page should be irrelevant according to POSIX, but on Linux it's within the `getstack` region!
pthread_attr_getguardsize(&attr, &guardSize);

stackProtectLow = stackLow;

// TODO: 32k seems high. Why is that?
// TODO: make this handle small stacks better?
// TODO: require a minimum stack size? The language does not define a max recursion depth, but for an evaluator to be practical, a stack that's too small is a problem that should be reported as such.
stackProtectHigh = (char*)stackLow + guardSize + std::min<size_t>(1024 * 32, stackSize / 4);
}

static inline void checkStack() {
int dummy;
void *sp = &dummy;
if (sp >= stackProtectLow && sp < stackProtectHigh) {
// No pos. A pos would be a red herring because the cause is never near the exception for stack overflows.
// TODO move into evalstate so we can do
// error("stack overflow", path).debugThrow<EvalError>();
throw EvalError("stack overflow");
}
}

static char * allocString(size_t size)
{
char * t;
Expand Down Expand Up @@ -524,6 +566,8 @@ EvalState::EvalState(

static_assert(sizeof(Env) <= 16, "environment must be <= 16 bytes");

initStack();

/* Initialise the Nix expression search path. */
if (!evalSettings.pureEval) {
for (auto & i : _searchPath.elements)
Expand Down Expand Up @@ -1512,6 +1556,8 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
? std::make_unique<FunctionCallTrace>(positions[pos])
: nullptr;

checkStack();

forceValue(fun, pos);

Value vCur(fun);
Expand Down
2 changes: 1 addition & 1 deletion src/libutil/error.cc
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@ std::ostream & showErrorInfo(std::ostream & out, const ErrorInfo & einfo, bool s
size_t count = 0;
for (const auto & trace : einfo.traces) {
if (trace.hint.str().empty()) continue;
if (frameOnly && !trace.frame) continue;
// if (frameOnly && !trace.frame) continue;

if (!showTrace && count > 3) {
oss << "\n" << ANSI_WARNING "(stack trace truncated; use '--show-trace' to show the full trace)" ANSI_NORMAL << "\n";
Expand Down
Loading