Works in debugger, segfaults when compiled

I’m trying to translate some C/C++ code to take a screenshot of an SDL window. My code works in the debugger, but not in normal compiled mode. It’s based on the following C/C++ code:

void SosariaInputController::SaveScreenshot()
{
    const Uint32 format = SDL_PIXELFORMAT_ARGB8888;
    const int width = 640;
    const int height = 400;
    auto renderer = sdl2Core->GetRenderer();
 
    SDL_Surface *surface = SDL_CreateRGBSurfaceWithFormat(0, width, height, 32, format);
    SDL_RenderReadPixels(renderer, NULL, format, surface->pixels, surface->pitch);
    SDL_SaveBMP(surface, "screenshot.bmp");
    SDL_FreeSurface(surface);
}

My Julia code does a bit more, as it takes a screen shot and renders the image to the window :

function flip2(win::Window, screenshot::Bool)
 
    if screenshot == true
        w = Ref{Cint}()
        h = Ref{Cint}()
        SDL_GL_GetDrawableSize(win.win, w, h)
        
        mySurfacePtr = SDL_CreateRGBSurfaceWithFormat(0, w[], h[], 32, SDL_PIXELFORMAT_ARGB8888);           # create empy mySurfacePtr

        dereference(T::DataType, ptr::Ptr) = unsafe_load(Ptr{T}(ptr))       # generic function
        surfaceStruct = dereference(SDL_Surface, mySurfacePtr)                  # try to put the C-based SDL_Surface struct into a Julia struct

        result = SDL_RenderReadPixels(win.renderer, C_NULL, SDL_PIXELFORMAT_ARGB8888, surfaceStruct.pixels, surfaceStruct.pitch);

        if result != 0
            error("SDL_RenderReadPixels failed: ", unsafe_string(SDL_GetError()) )
        end
            
        surfaceStructPtr = Ptr{SDL_Surface}(pointer_from_objref(Ref(surfaceStruct)))

        if surfaceStructPtr == C_NULL
            println("surfaceStructPtr is C_NULL" )
        else
            println(logFile, "surfaceStructPtr is NOT C_NULL" )
        end

        result = IMG_SavePNG(surfaceStructPtr,  "screenshot.png")
        if result != 0
            error("IMG_SavePNG failed: ", unsafe_string(SDL_GetError()) )
        end 
    end
    SDL_RenderPresent(win.renderer)                 # Present the backbuffer (memory) to the screen
    SDL_PumpEvents()
    SDL_SetRenderDrawColor(win.renderer, win.color[1], win.color[2], win.color[3], 255)         # sets window background color
    SDL_RenderClear(win.renderer)                                                               # Clears the window in memory, getting it read for the next drawing session.
end

When I run it outside the debugger, I get a segmentation fault, with this as the error log. The segfault seems to occur when I call IMG_SavePNG(). Any ideas why this is happening, and why it would work just fine when running it in the debugger, but crash-and-burn when running the compiled version?

[56036] signal (11.2): Segmentation fault: 11
in expression starting at /Users/SomeGuysAccount/.julia/dev/PsychoJL/Examples/complex occlusions/ComplexOcclusionSearchMain.jl:613
IMG_SavePNG_RW at /Users/SomeGuysAccount/.julia/artifacts/5ebb4b9114677f6288d27366c03555cca9d7ab98/lib/libSDL2_image-2.0.601.2.0.dylib (unknown line)
IMG_SavePNG at /Users/SomeGuysAccount/.julia/packages/SimpleDirectMediaLayer/wjMsP/src/LibSDL2.jl:5846 [inlined]
flip2 at /Users/SomeGuysAccount/.julia/dev/PsychoJL/src/window.jl:420
doATrial at /Users/SomeGuysAccount/.julia/dev/PsychoJL/Examples/complex occlusions/ComplexOcclusionSearchMain.jl:352
main at /Users/SomeGuysAccount/.julia/dev/PsychoJL/Examples/complex occlusions/ComplexOcclusionSearchMain.jl:83
unknown function (ip: 0x2a8788467)
_jl_invoke at /Users/julia/.julia/scratchspaces/a66863c6-20e8-4ff4-8a62-49f30b1f605e/agent-cache/default-honeycrisp-HL2F7YQ3XH.0/build/default-honeycrisp-HL2F7YQ3XH-0/julialang/julia-release-1-dot-10/src/gf.c:0 [inlined]
ijl_apply_generic at /Users/julia/.julia/scratchspaces/a66863c6-20e8-4ff4-8a62-49f30b1f605e/agent-cache/default-honeycrisp-HL2F7YQ3XH.0/build/default-honeycrisp-HL2F7YQ3XH-0/julialang/julia-release-1-dot-10/src/gf.c:3076
jl_apply at /Users/julia/.julia/scratchspaces/a66863c6-20e8-4ff4-8a62-49f30b1f605e/agent-cache/default-honeycrisp-HL2F7YQ3XH.0/build/default-honeycrisp-HL2F7YQ3XH-0/julialang/julia-release-1-dot-10/src/./julia.h:1982 [inlined]
do_call at /Users/julia/.julia/scratchspaces/a66863c6-20e8-4ff4-8a62-49f30b1f605e/agent-cache/default-honeycrisp-HL2F7YQ3XH.0/build/default-honeycrisp-HL2F7YQ3XH-0/julialang/julia-release-1-dot-10/src/interpreter.c:126
eval_body at /Users/julia/.julia/scratchspaces/a66863c6-20e8-4ff4-8a62-49f30b1f605e/agent-cache/default-honeycrisp-HL2F7YQ3XH.0/build/default-honeycrisp-HL2F7YQ3XH-0/julialang/julia-release-1-dot-10/src/interpreter.c:0
jl_interpret_toplevel_thunk at /Users/julia/.julia/scratchspaces/a66863c6-20e8-4ff4-8a62-49f30b1f605e/agent-cache/default-honeycrisp-HL2F7YQ3XH.0/build/default-honeycrisp-HL2F7YQ3XH-0/julialang/julia-release-1-dot-10/src/interpreter.c:775
jl_toplevel_eval_flex at /Users/julia/.julia/scratchspaces/a66863c6-20e8-4ff4-8a62-49f30b1f605e/agent-cache/default-honeycrisp-HL2F7YQ3XH.0/build/default-honeycrisp-HL2F7YQ3XH-0/julialang/julia-release-1-dot-10/src/toplevel.c:934
jl_toplevel_eval_flex at /Users/julia/.julia/scratchspaces/a66863c6-20e8-4ff4-8a62-49f30b1f605e/agent-cache/default-honeycrisp-HL2F7YQ3XH.0/build/default-honeycrisp-HL2F7YQ3XH-0/julialang/julia-release-1-dot-10/src/toplevel.c:877
ijl_toplevel_eval at /Users/julia/.julia/scratchspaces/a66863c6-20e8-4ff4-8a62-49f30b1f605e/agent-cache/default-honeycrisp-HL2F7YQ3XH.0/build/default-honeycrisp-HL2F7YQ3XH-0/julialang/julia-release-1-dot-10/src/toplevel.c:943 [inlined]
ijl_toplevel_eval_in at /Users/julia/.julia/scratchspaces/a66863c6-20e8-4ff4-8a62-49f30b1f605e/agent-cache/default-honeycrisp-HL2F7YQ3XH.0/build/default-honeycrisp-HL2F7YQ3XH-0/julialang/julia-release-1-dot-10/src/toplevel.c:985
eval at ./boot.jl:385 [inlined]
include_string at ./loading.jl:2070
_jl_invoke at /Users/julia/.julia/scratchspaces/a66863c6-20e8-4ff4-8a62-49f30b1f605e/agent-cache/default-honeycrisp-HL2F7YQ3XH.0/build/default-honeycrisp-HL2F7YQ3XH-0/julialang/julia-release-1-dot-10/src/gf.c:0 [inlined]
ijl_apply_generic at /Users/julia/.julia/scratchspaces/a66863c6-20e8-4ff4-8a62-49f30b1f605e/agent-cache/default-honeycrisp-HL2F7YQ3XH.0/build/default-honeycrisp-HL2F7YQ3XH-0/julialang/julia-release-1-dot-10/src/gf.c:3076
_include at ./loading.jl:2130
include at ./Base.jl:495
jfptr_include_46486 at /Users/SomeGuysAccount/.julia/juliaup/julia-1.10.0+0.aarch64.apple.darwin14/lib/julia/sys.dylib (unknown line)
_jl_invoke at /Users/julia/.julia/scratchspaces/a66863c6-20e8-4ff4-8a62-49f30b1f605e/agent-cache/default-honeycrisp-HL2F7YQ3XH.0/build/default-honeycrisp-HL2F7YQ3XH-0/julialang/julia-release-1-dot-10/src/gf.c:0 [inlined]
ijl_apply_generic at /Users/julia/.julia/scratchspaces/a66863c6-20e8-4ff4-8a62-49f30b1f605e/agent-cache/default-honeycrisp-HL2F7YQ3XH.0/build/default-honeycrisp-HL2F7YQ3XH-0/julialang/julia-release-1-dot-10/src/gf.c:3076
exec_options at ./client.jl:318
_start at ./client.jl:552
jfptr__start_82831 at /Users/SomeGuysAccount/.julia/juliaup/julia-1.10.0+0.aarch64.apple.darwin14/lib/julia/sys.dylib (unknown line)
_jl_invoke at /Users/julia/.julia/scratchspaces/a66863c6-20e8-4ff4-8a62-49f30b1f605e/agent-cache/default-honeycrisp-HL2F7YQ3XH.0/build/default-honeycrisp-HL2F7YQ3XH-0/julialang/julia-release-1-dot-10/src/gf.c:0 [inlined]
ijl_apply_generic at /Users/julia/.julia/scratchspaces/a66863c6-20e8-4ff4-8a62-49f30b1f605e/agent-cache/default-honeycrisp-HL2F7YQ3XH.0/build/default-honeycrisp-HL2F7YQ3XH-0/julialang/julia-release-1-dot-10/src/gf.c:3076
jl_apply at /Users/julia/.julia/scratchspaces/a66863c6-20e8-4ff4-8a62-49f30b1f605e/agent-cache/default-honeycrisp-HL2F7YQ3XH.0/build/default-honeycrisp-HL2F7YQ3XH-0/julialang/julia-release-1-dot-10/src/./julia.h:1982 [inlined]
true_main at /Users/julia/.julia/scratchspaces/a66863c6-20e8-4ff4-8a62-49f30b1f605e/agent-cache/default-honeycrisp-HL2F7YQ3XH.0/build/default-honeycrisp-HL2F7YQ3XH-0/julialang/julia-release-1-dot-10/src/jlapi.c:582
jl_repl_entrypoint at /Users/julia/.julia/scratchspaces/a66863c6-20e8-4ff4-8a62-49f30b1f605e/agent-cache/default-honeycrisp-HL2F7YQ3XH.0/build/default-honeycrisp-HL2F7YQ3XH-0/julialang/julia-release-1-dot-10/src/jlapi.c:731
Allocations: 7895382 (Pool: 7886666; Big: 8716); GC: 11


This is illegal. You create an object Ref(surfaceStruct), then observe the pointer, and use the the pointer after the object is no longer reachable.

It depends on the definition of IMG_SavePNG, but it may suffice passing the Ref directly,

Otherwise a safe pattern is:

r_surfacestruct = Ref(surfaceStruct)
GC.@preserve r_surfacestruct begin
    ptr_surfacestruct = Base.unsafe_convert(Ptr{SDL_Surface}, r_surfacestruct)
    result = IMG_SavePNG(ptr_surfacestruct,  "screenshot.png")
end

@preserve extends the lifetime of the object across the begin/end.

3 Likes

Thanks, that worked!

Pointers and handles were my least favorite part of C/C++. I wish there was the equivalent of -> for dereferencing a field of a pointer to a C struct in Julia.

We really do not want users to be messing with raw pointers in Julia due to the potential for exactly the kind of error you stumbled upon. Ref or specifically Base.RefValue is the closest you will get. The syntax is then []..

julia> struct Foo
           x::Int64
       end

julia> r = Ref{Foo}()
Base.RefValue{Foo}(Foo(480270463568))

julia> r[].x
480270463568