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

[Question] Program terminated when throwing a customized exception in validation layer callback #1847

Open
Stehsaer opened this issue Apr 15, 2024 · 8 comments

Comments

@Stehsaer
Copy link

I'm currently rebuilding my vulkan utility library with vulkan-hpp, and I intend to throw an customized error class in the callback of validation layer. VULKAN_HPP_NO_EXCEPTIONS was defined.

struct Validation_error : public std::runtime_error
{
	std::string msg;
	Message_severity_t severity;
	...
};

// callback.cpp
VkBool32 validation_callback(
	VkDebugUtilsMessageSeverityFlagBitsEXT      message_severity,
	VkDebugUtilsMessageTypeFlagsEXT             message_types,
	const VkDebugUtilsMessengerCallbackDataEXT* callback_data,
	void*                                       user_data [[maybe_unused]]
)
{
	// std::cerr << callback_data->pMessage << std::endl;
	throw Validation_error(message_severity, message_types, callback_data->pMessage);
}

// main.cpp
int main()
{
	...
	try
	{
		// actual work...
	}
	catch(Validation_error e)
	{
		std::cout << e.msg << std::endl;

		return EXIT_FAILURE;
	}
	catch(std::exception e)
	{
		// other error handling code...
	}
	...
}

What's wrong here?

Debugger Output

Exception 0x80000003 encountered at address 0x7ffb5c342fb5

image
Note: I intentionally trigger the validation layer by assigning a queue priority of 10000.0f in the vk::DeviceQueueCreateInfo.

@asuessenbach
Copy link
Contributor

Well, I can't interpret what happens here or what should... The only thing that might be an issue here: you should catch by const-reference, not by value.

@Stehsaer
Copy link
Author

Well, I can't interpret what happens here or what should... The only thing that might be an issue here: you should catch by const-reference, not by value.

Thanks for your comment! I changed to use const-ref in my catch statement and re-enabled exceptions in vulkan-hpp, but the issue isn't solved. The program terminates before the exception thrown from the validation callback is caught by the catch statement in main.cpp, confirmed by setting a breakpoint.

@asuessenbach
Copy link
Contributor

Have you actually created the DebugUtilsMessenger?

    vk::DebugUtilsMessengerEXT        debugUtilsMessenger =
      instance.createDebugUtilsMessengerEXT( vk::DebugUtilsMessengerCreateInfoEXT( {}, severityFlags, messageTypeFlags, &debugMessageFunc ) );

@Stehsaer
Copy link
Author

Have you actually created the DebugUtilsMessenger?

    vk::DebugUtilsMessengerEXT        debugUtilsMessenger =
      instance.createDebugUtilsMessengerEXT( vk::DebugUtilsMessengerCreateInfoEXT( {}, severityFlags, messageTypeFlags, &debugMessageFunc ) );

Yes I do. It works perfectly as intended if I'm using a callback function of

VkBool32 good_validation_callback(
	VkDebugUtilsMessageSeverityFlagBitsEXT      message_severity,
	VkDebugUtilsMessageTypeFlagsEXT             message_types,
	const VkDebugUtilsMessengerCallbackDataEXT* callback_data,
	void*                                       user_data [[maybe_unused]]
)
{
	std::cerr << callback_data->pMessage << std::endl;
	// throw Validation_error(message_severity, message_types, callback_data->pMessage);
}

Issue appears only when I switch to this callback function

VkBool32 bad_validation_callback(
	VkDebugUtilsMessageSeverityFlagBitsEXT      message_severity,
	VkDebugUtilsMessageTypeFlagsEXT             message_types,
	const VkDebugUtilsMessengerCallbackDataEXT* callback_data,
	void*                                       user_data [[maybe_unused]]
)
{
	// std::cerr << callback_data->pMessage << std::endl;
	throw Validation_error(message_severity, message_types, callback_data->pMessage);
}

@asuessenbach
Copy link
Contributor

I assume, you don't provoke that error from within a destructor? A destructor should not throw.
Besides that, I can't repro your issue. Could you provide more sources for inspection?

@Stehsaer
Copy link
Author

Stehsaer commented Apr 24, 2024

I assume, you don't provoke that error from within a destructor? A destructor should not throw. Besides that, I can't repro your issue. Could you provide more sources for inspection?

Here's how I structure my code:
class Debug_utility:

// Vulkan Debug Utility
class Debug_utility : public Child_resource<vk::DebugUtilsMessengerEXT, Instance>
{
	using Child_resource<vk::DebugUtilsMessengerEXT, Instance>::Child_resource;

  public:

	/* ===== Type Definitions =====  */

	using Message_severity_t   = vk::DebugUtilsMessageSeverityFlagsEXT;
	using Message_severity_bit = vk::DebugUtilsMessageSeverityFlagBitsEXT;
	using Message_type_t       = vk::DebugUtilsMessageTypeFlagsEXT;
	using Callback_data_t      = vk::DebugUtilsMessengerCallbackDataEXT;

	using callback_t = VkBool32(
		VkDebugUtilsMessageSeverityFlagBitsEXT      message_severity,
		VkDebugUtilsMessageTypeFlagsEXT             message_types,
		const VkDebugUtilsMessengerCallbackDataEXT* callback_data,
		void*                                       user_data
	);

	/* ===== Static Constants ===== */

	// Validation Layer Name, add to array of layers when creating Instance
	static constexpr const char* validation_layer_name = "VK_LAYER_KHRONOS_validation";

	// Debug Utility Extension Name, add to array of extensions when creating Instance
	static constexpr const char* debug_utils_ext_name = VK_EXT_DEBUG_UTILS_EXTENSION_NAME;

	// Query if extension is supported
	static bool is_supported();

	/* ===== Constructor & Destructor ===== */

	Debug_utility(
		const Instance&    instance,
		Message_severity_t severity,
		Message_type_t     msg_type,
		callback_t*        callback = validation_callback
	);

	void clean() override;
	~Debug_utility() override { clean(); }

	// Callback, generates a Validation_error when called (broken)
	static VkBool32 validation_callback(
		VkDebugUtilsMessageSeverityFlagBitsEXT      message_severity,
		VkDebugUtilsMessageTypeFlagsEXT             message_types,
		const VkDebugUtilsMessengerCallbackDataEXT* callback_data,
		void*                                       user_data
	);
};

The Child_resource<> base class is just a wrapper of resource management that provides assignment operator and protected constructors. It has nothing to do with actual vulkan functionality.
The implementation:

static PFN_vkCreateDebugUtilsMessengerEXT  pfn_vk_create_debug_utils_messenger_ext  = nullptr;
static PFN_vkDestroyDebugUtilsMessengerEXT pfn_vk_destroy_debug_utils_messenger_ext = nullptr;

VKAPI_ATTR VkResult VKAPI_CALL vkCreateDebugUtilsMessengerEXT(
	VkInstance                                instance,
	const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo,
	const VkAllocationCallbacks*              pAllocator,
	VkDebugUtilsMessengerEXT*                 pMessenger
)
{
	return pfn_vk_create_debug_utils_messenger_ext(instance, pCreateInfo, pAllocator, pMessenger);
}

VKAPI_ATTR void VKAPI_CALL vkDestroyDebugUtilsMessengerEXT(
	VkInstance                   instance,
	VkDebugUtilsMessengerEXT     messenger,
	VkAllocationCallbacks const* pAllocator
)
{
	return pfn_vk_destroy_debug_utils_messenger_ext(instance, messenger, pAllocator);
}

Debug_utility::Debug_utility(
	const Instance&    instance,
	Message_severity_t severity,
	Message_type_t     msg_type,
	callback_t*        callback
)
{
	vk::DebugUtilsMessengerCreateInfoEXT create_info;
	create_info.setMessageSeverity(severity);
	create_info.setMessageType(msg_type);
	create_info.setPfnUserCallback(callback);

    // Query the function handle
	pfn_vk_create_debug_utils_messenger_ext = reinterpret_cast<PFN_vkCreateDebugUtilsMessengerEXT>(
		instance->getProcAddr("vkCreateDebugUtilsMessengerEXT")
	);

    // Function handle not found
	if (pfn_vk_create_debug_utils_messenger_ext == nullptr)
		throw General_exception("Function: vkCreateDebugUtilsMessengerEXT not found ");

    // Create DebugUtils
	auto handle = instance->createDebugUtilsMessengerEXT(create_info);
	*this       = Debug_utility(handle, instance); // Assign raw objects and parents to current object
}

void Debug_utility::clean()
{
	if (is_unique())
	{
		pfn_vk_destroy_debug_utils_messenger_ext = reinterpret_cast<PFN_vkDestroyDebugUtilsMessengerEXT>(
			parent()->getProcAddr("vkDestroyDebugUtilsMessengerEXT")
		);

		if (pfn_vk_destroy_debug_utils_messenger_ext == nullptr) return;

		parent()->destroyDebugUtilsMessengerEXT(*this);
	}
}

VkBool32 Debug_utility::validation_callback(
	VkDebugUtilsMessageSeverityFlagBitsEXT      message_severity [[maybe_unused]],
	VkDebugUtilsMessageTypeFlagsEXT             message_types [[maybe_unused]],
	const VkDebugUtilsMessengerCallbackDataEXT* callback_data [[maybe_unused]],
	void*                                       user_data [[maybe_unused]]
)
{
	// std::cerr << callback_data->pMessage << std::endl; // My current work-around for this issue
    throw Validation_error(message_severity, message_types, callback_data->pMessage); // the issue
	return true;
}

The class Instance is my own RAII wrapper for vk::Instance, which can be implicitly converted into vk::Instance. Calling instance->createDebugUtilsMessengerEXT is equivalent to calling vk::Instance(<some instance>).createDebugUtilsMessengerEXT.

It doesn't seem that I called the validation_callback(...) function in any destructor 🤔 , however the behavior of this issue perfectly match the phenomenon when you throw an error in a destructor.

@asuessenbach
Copy link
Contributor

Hm... I don't know if that matters, but the callback should be VKAPI_ATTR VkBool32 VKAPI_CALL.

@Stehsaer
Copy link
Author

Hm... I don't know if that matters, but the callback should be VKAPI_ATTR VkBool32 VKAPI_CALL.

Just made a quick test, it doesn't seem to help. On my platform, VKAPI_ATTR is defined as empty, and VKAPI_CALL is defined as __stdcall.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants