Skip to content

Commit

Permalink
Warn on potential stack overflow in SIGSEGV handler
Browse files Browse the repository at this point in the history
  • Loading branch information
damaxwell committed Oct 13, 2018
1 parent b75baba commit e578379
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 2 deletions.
19 changes: 19 additions & 0 deletions spec/std/kernel_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -220,3 +220,22 @@ describe "at_exit" do
OUTPUT
end
end

describe "seg fault" do
it "detects stack overflow" do
status, _, error = build_and_run <<-'CODE'
def foo
foo
end

spawn do
foo
end

sleep 60.seconds
CODE

status.success?.should be_false
error.should contain("Stack overflow")
end
end
20 changes: 19 additions & 1 deletion src/fiber.cr
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
require "c/sys/mman"
{% unless flag?(:win32) %}
require "c/sys/resource"
{% end %}
require "thread/linked_list"

# Load the arch-specific methods to create a context and to swap from one
Expand Down Expand Up @@ -86,11 +89,26 @@ class Fiber
# :nodoc:
def initialize
@proc = Proc(Void).new { }
@stack = Pointer(Void).null
@stack_top = _fiber_get_stack_top
@stack_bottom = GC.stack_bottom
@name = "main"

# Determine location of the top of the stack.
# The technique here works only for the main stack on a POSIX platform.
# TODO: implement for Windows with GetCurrentThreadStackLimits
# TODO: implement for pthreads using
# linux-glibc/musl: pthread_getattr_np
# macosx: pthread_get_stackaddr_np, pthread_get_stacksize_np
# freebsd: pthread_attr_get_np
# openbsd: pthread_stackseg_np
@stack = Pointer(Void).null
{% unless flag?(:win32) %}
if LibC.getrlimit(LibC::RLIMIT_STACK, out rlim) == 0
stack_size = rlim.rlim_cur
@stack = Pointer(Void).new(@stack_bottom.address - stack_size)
end
{% end %}

@@fibers.push(self)
end

Expand Down
13 changes: 12 additions & 1 deletion src/signal.cr
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,18 @@ end
# :nodoc:
fun __crystal_sigfault_handler(sig : LibC::Int, addr : Void*)
# Capture fault signals (SEGV, BUS) and finish the process printing a backtrace first
LibC.dprintf 2, "Invalid memory access (signal %d) at address 0x%lx\n", sig, addr

# Determine if the SEGV was inside or 'near' the top of the stack
# to check for potential stack overflow. 'Near' is a small
# amount larger than a typical stack frame, 4096 bytes here.
stack_top = Pointer(Void).new(Fiber.current.@stack.address - 4096)

if stack_top <= addr < Fiber.current.@stack_bottom
LibC.dprintf 2, "Stack overflow (e.g., infinite or very deep recursion)\n"
else
LibC.dprintf 2, "Invalid memory access (signal %d) at address 0x%lx\n", sig, addr
end

CallStack.print_backtrace
LibC._exit(sig)
end

0 comments on commit e578379

Please sign in to comment.