How to debug a silent crash?

I’m writing a script which calls a C library and the program is just stopping prematurely with no output.

How do I debug this?

A little context:
I’m passing a Julia function as a callback to the SimpleBLE C library that is called when data is received from a Bluetooth device. The callback is called once and seems to execute successfully and a few seconds later the process stops.
The callback contains an unsafe_wrap to read the incoming data, then calls println to display it and finally calls the library’s free function on the received data.

Is julia garbage collecting the callback closure? You may need to setup some GC preserve, or flush the c buffer in order for julia to print thd output

Do you mean the callback itself can get garbage collected after C uses it exactly once?

Here is the offending function

# The following functions call on each other in sequence
# This reformats the callback to only need to take one argument
function peripheral_notify(handle, service, characteristic, callback)
	function c_callback(handle, service, characteristic, data, data_length, userdata)
		jdata = unsafe_wrap(Vector{UInt8}, data, data_length)
		callback(jdata)
		simpleble_free(data)
	end
	peripheral_notify(handle, service, characteristic, c_callback, C_NULL)
end
# This converts the callback into a c function
function peripheral_notify(handle, service, characteristic, callback, userdata)
	c_callback = @cfunction($callback, Cvoid, (SBLEPeripheral, SBLEUUID, SBLEUUID, Ptr{UInt8},Csize_t, Ptr{Cvoid}))
	_peripheral_notify(handle, service, characteristic, c_callback, userdata)
end
# This calls ccall 
_peripheral_notify(handle, service, characteristic, callback, userdata) =
	@ccall simpleblepath.simpleble_peripheral_notify(
		handle::SBLEPeripheral,
		service::SBLEUUID,
		characteristic::SBLEUUID,
		callback::Ptr{Cvoid},
		userdata::Ptr{Cvoid})::SimpleBLE_Error

Adding a GC.@preserve for the callbacks does not seem to fix it.

Ok, removing the simpleble_free seems to have solved the issue, but the docs say I should free the memory given to callbacks so now I’m confused.

stash the cfunction pointer somewhere the GC can see it. $callback in @cfunction

const _active_callbacks = Dict{Ptr{Cvoid}, Base.CFunction}()

function peripheral_notify(handle, service, characteristic, callback, userdata)
    c_callback = @cfunction($callback, Cvoid, (SBLEPeripheral, SBLEUUID, SBLEUUID, Ptr{UInt8}, Csize_t, Ptr{Cvoid}))
    _active_callbacks[handle] = c_callback  # prevent GC
    _peripheral_notify(handle, service, characteristic, c_callback, userdata)
end

Actually you might be freeing the memory twice, and causing a heap corruption, why not freeing it works.

1 Like

Does not seem to have fixed it. But it seems like good practice so I’m keeping it.

I’m making a MWE to send to the author to ask him.

  1. Function returns a pointer — who owns it? Factory functions return owned pointers (caller must free). Accessor functions return borrowed pointers (caller must NOT free). Interior pointers have lifetime tied to a parent.
  2. Function takes a pointer argument — does it consume it? Most functions borrow their arguments. Some sink functions take ownership (callee frees). The caller needs to know so it doesn’t double-free.

obsidian_notes