mirror of
https://github.com/charles-lunarg/vk-bootstrap.git
synced 2024-11-22 15:24:34 +00:00
d759d3d575
Allows the tests to set the exact values vk-bootstrap will receieve rather than require a real vulkan driver to be present on the system. This is done by creating a `vulkan-1.dll` on windows that implements all of the API functions, and on linux/macOS intercepts `dlsym` to make it return the mock's functions.
415 lines
17 KiB
C++
415 lines
17 KiB
C++
|
|
|
|
#include "vulkan_mock.hpp"
|
|
|
|
#include <cstring>
|
|
#include <algorithm>
|
|
|
|
#if defined(__linux__) || defined(__APPLE__)
|
|
#include <dlfcn.h>
|
|
#endif
|
|
|
|
#define GPA_IMPL(x) \
|
|
if (strcmp(pName, #x) == 0) { \
|
|
return reinterpret_cast<PFN_vkVoidFunction>(shim_##x); \
|
|
}
|
|
|
|
|
|
VulkanMock mock;
|
|
|
|
EXPORT_MACRO VulkanMock& get_vulkan_mock() {
|
|
mock = VulkanMock{};
|
|
return mock;
|
|
}
|
|
|
|
template <typename T> VkResult fill_out_count_pointer_pair(std::vector<T> const& data_vec, uint32_t* pCount, T* pData) {
|
|
if (pCount == nullptr) {
|
|
return VK_ERROR_OUT_OF_HOST_MEMORY;
|
|
}
|
|
if (pData == nullptr) {
|
|
if (pCount) *pCount = static_cast<uint32_t>(data_vec.size());
|
|
return VK_SUCCESS;
|
|
} else {
|
|
uint32_t amount_written = 0;
|
|
uint32_t amount_to_write = static_cast<uint32_t>(data_vec.size());
|
|
if (*pCount < data_vec.size()) {
|
|
amount_to_write = *pCount;
|
|
}
|
|
for (size_t i = 0; i < amount_to_write; i++) {
|
|
pData[i] = data_vec[i];
|
|
amount_written++;
|
|
}
|
|
if (*pCount < data_vec.size()) {
|
|
*pCount = amount_written;
|
|
return VK_INCOMPLETE;
|
|
}
|
|
*pCount = amount_written;
|
|
return VK_SUCCESS;
|
|
}
|
|
}
|
|
|
|
VKAPI_ATTR VkResult VKAPI_CALL shim_vkEnumerateInstanceVersion(uint32_t* pApiVersion) {
|
|
if (pApiVersion == nullptr) {
|
|
return VK_ERROR_DEVICE_LOST;
|
|
}
|
|
*pApiVersion = mock.api_version;
|
|
return VK_SUCCESS;
|
|
}
|
|
|
|
VKAPI_ATTR VkResult VKAPI_CALL shim_vkEnumerateInstanceExtensionProperties(
|
|
const char* pLayerName, uint32_t* pPropertyCount, VkExtensionProperties* pProperties) {
|
|
if (pLayerName) {
|
|
for (size_t i = 0; i < mock.instance_layers.size(); i++) {
|
|
if (strcmp(mock.instance_layers[i].layerName, pLayerName) == 0) {
|
|
return fill_out_count_pointer_pair(mock.per_layer_instance_extension_properties[i], pPropertyCount, pProperties);
|
|
}
|
|
}
|
|
// Layer not found, fill out with empty list
|
|
return fill_out_count_pointer_pair({}, pPropertyCount, pProperties);
|
|
}
|
|
return fill_out_count_pointer_pair(mock.instance_extensions, pPropertyCount, pProperties);
|
|
}
|
|
|
|
VKAPI_ATTR VkResult VKAPI_CALL shim_vkEnumerateInstanceLayerProperties(uint32_t* pPropertyCount, VkLayerProperties* pProperties) {
|
|
return fill_out_count_pointer_pair(mock.instance_layers, pPropertyCount, pProperties);
|
|
}
|
|
|
|
VKAPI_ATTR VkResult VKAPI_CALL shim_vkCreateInstance([[maybe_unused]] const VkInstanceCreateInfo* pCreateInfo,
|
|
[[maybe_unused]] const VkAllocationCallbacks* pAllocator,
|
|
VkInstance* pInstance) {
|
|
if (pInstance == nullptr) {
|
|
return VK_ERROR_INITIALIZATION_FAILED;
|
|
}
|
|
*pInstance = reinterpret_cast<VkInstance>(0x0000ABCD);
|
|
return VK_SUCCESS;
|
|
}
|
|
VKAPI_ATTR void VKAPI_CALL shim_vkDestroyInstance(
|
|
[[maybe_unused]] VkInstance instance, [[maybe_unused]] const VkAllocationCallbacks* pAllocator) {}
|
|
|
|
VKAPI_ATTR VkResult VKAPI_CALL shim_vkCreateDebugUtilsMessengerEXT([[maybe_unused]] VkInstance instance,
|
|
[[maybe_unused]] const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo,
|
|
[[maybe_unused]] const VkAllocationCallbacks* pAllocator,
|
|
VkDebugUtilsMessengerEXT* pMessenger) {
|
|
if (instance == nullptr) {
|
|
return VK_ERROR_INITIALIZATION_FAILED;
|
|
}
|
|
*pMessenger = reinterpret_cast<VkDebugUtilsMessengerEXT>(0xDEBE0000DEBE0000);
|
|
return VK_SUCCESS;
|
|
}
|
|
|
|
VKAPI_ATTR void VKAPI_CALL shim_vkDestroyDebugUtilsMessengerEXT([[maybe_unused]] VkInstance instance,
|
|
[[maybe_unused]] VkDebugUtilsMessengerEXT messenger,
|
|
[[maybe_unused]] const VkAllocationCallbacks* pAllocator) {}
|
|
|
|
VKAPI_ATTR VkResult VKAPI_CALL shim_vkEnumeratePhysicalDevices(
|
|
VkInstance instance, uint32_t* pPhysicalDeviceCount, VkPhysicalDevice* pPhysicalDevices) {
|
|
if (instance == nullptr) {
|
|
return VK_ERROR_INITIALIZATION_FAILED;
|
|
}
|
|
return fill_out_count_pointer_pair(mock.physical_device_handles, pPhysicalDeviceCount, pPhysicalDevices);
|
|
}
|
|
|
|
VulkanMock::PhysicalDeviceDetails& get_physical_device_details(VkPhysicalDevice physicalDevice) {
|
|
for (size_t i = 0; i < mock.physical_device_handles.size(); i++) {
|
|
if (mock.physical_device_handles[i] == physicalDevice) return mock.physical_devices_details[i];
|
|
}
|
|
assert(false && "should never reach here!");
|
|
return mock.physical_devices_details.front();
|
|
}
|
|
|
|
VKAPI_ATTR void VKAPI_CALL shim_vkGetPhysicalDeviceFeatures(VkPhysicalDevice physicalDevice, VkPhysicalDeviceFeatures* pFeatures) {
|
|
*pFeatures = get_physical_device_details(physicalDevice).features;
|
|
}
|
|
VKAPI_ATTR void VKAPI_CALL shim_vkGetPhysicalDeviceProperties(VkPhysicalDevice physicalDevice, VkPhysicalDeviceProperties* pProperties) {
|
|
*pProperties = get_physical_device_details(physicalDevice).properties;
|
|
}
|
|
|
|
VKAPI_ATTR void VKAPI_CALL shim_vkGetPhysicalDeviceQueueFamilyProperties(
|
|
VkPhysicalDevice physicalDevice, uint32_t* pQueueFamilyPropertyCount, VkQueueFamilyProperties* pQueueFamilyProperties) {
|
|
fill_out_count_pointer_pair(
|
|
get_physical_device_details(physicalDevice).queue_family_properties, pQueueFamilyPropertyCount, pQueueFamilyProperties);
|
|
}
|
|
|
|
VKAPI_ATTR void VKAPI_CALL shim_vkGetPhysicalDeviceMemoryProperties(
|
|
VkPhysicalDevice physicalDevice, VkPhysicalDeviceMemoryProperties* pMemoryProperties) {
|
|
*pMemoryProperties = get_physical_device_details(physicalDevice).memory_properties;
|
|
}
|
|
|
|
VKAPI_ATTR void VKAPI_CALL shim_vkGetPhysicalDeviceFeatures2KHR(VkPhysicalDevice physicalDevice, VkPhysicalDeviceFeatures2* pFeatures) {
|
|
const auto& phys_dev = get_physical_device_details(physicalDevice);
|
|
VkBaseOutStructure* current = static_cast<VkBaseOutStructure*>(pFeatures->pNext);
|
|
while (current) {
|
|
for (const auto& features_pNext : phys_dev.features_pNextChain) {
|
|
if (features_pNext->sType == current->sType) {
|
|
VkBaseOutStructure* next = static_cast<VkBaseOutStructure*>(current->pNext);
|
|
std::memcpy(current, features_pNext.get(), get_pnext_chain_struct_size(features_pNext->sType));
|
|
current->pNext = next;
|
|
break;
|
|
}
|
|
}
|
|
current = static_cast<VkBaseOutStructure*>(current->pNext);
|
|
}
|
|
}
|
|
VKAPI_ATTR void VKAPI_CALL shim_vkGetPhysicalDeviceFeatures2(VkPhysicalDevice physicalDevice, VkPhysicalDeviceFeatures2* pFeatures) {
|
|
shim_vkGetPhysicalDeviceFeatures2KHR(physicalDevice, pFeatures);
|
|
}
|
|
|
|
VKAPI_ATTR VkResult VKAPI_CALL shim_vkEnumerateDeviceExtensionProperties(
|
|
VkPhysicalDevice physicalDevice, const char* pLayerName, uint32_t* pPropertyCount, VkExtensionProperties* pProperties) {
|
|
if (pLayerName) {
|
|
for (size_t i = 0; i < mock.instance_layers.size(); i++) {
|
|
if (strcmp(mock.instance_layers[i].layerName, pLayerName) == 0) {
|
|
return fill_out_count_pointer_pair(mock.per_layer_device_extension_properties[i], pPropertyCount, pProperties);
|
|
}
|
|
}
|
|
// Layer not found, fill out with empty list
|
|
return fill_out_count_pointer_pair({}, pPropertyCount, pProperties);
|
|
}
|
|
return fill_out_count_pointer_pair(get_physical_device_details(physicalDevice).extensions, pPropertyCount, pProperties);
|
|
}
|
|
|
|
|
|
VKAPI_ATTR VkResult VKAPI_CALL shim_vkCreateDevice(VkPhysicalDevice physicalDevice,
|
|
[[maybe_unused]] const VkDeviceCreateInfo* pCreateInfo,
|
|
[[maybe_unused]] const VkAllocationCallbacks* pAllocator,
|
|
VkDevice* pDevice) {
|
|
if (physicalDevice == nullptr) {
|
|
return VK_ERROR_INITIALIZATION_FAILED;
|
|
}
|
|
*pDevice = reinterpret_cast<VkDevice>(0x00FEDC00);
|
|
get_physical_device_details(physicalDevice).created_devices.push_back(*pDevice);
|
|
return VK_SUCCESS;
|
|
}
|
|
|
|
VKAPI_ATTR void VKAPI_CALL shim_vkDestroyDevice(
|
|
[[maybe_unused]] VkDevice device, [[maybe_unused]] const VkAllocationCallbacks* pAllocator) {
|
|
for (auto& physical_devices : mock.physical_devices_details) {
|
|
auto it = std::find(std::begin(physical_devices.created_devices), std::end(physical_devices.created_devices), device);
|
|
if (it != std::end(physical_devices.created_devices)) {
|
|
physical_devices.created_devices.erase(it);
|
|
}
|
|
}
|
|
}
|
|
|
|
VKAPI_ATTR void VKAPI_CALL shim_vkGetDeviceQueue(VkDevice device, uint32_t queueFamilyIndex, uint32_t queueIndex, VkQueue* pQueue) {
|
|
for (auto& physical_devices : mock.physical_devices_details) {
|
|
auto it = std::find(std::begin(physical_devices.created_devices), std::end(physical_devices.created_devices), device);
|
|
if (it != std::end(physical_devices.created_devices)) {
|
|
if (queueFamilyIndex < physical_devices.queue_family_properties.size() &&
|
|
queueIndex < physical_devices.queue_family_properties[queueFamilyIndex].queueCount) {
|
|
*pQueue = reinterpret_cast<VkQueue>(0x0000CCEE);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
VKAPI_ATTR VkResult VKAPI_CALL shim_vkCreateCommandPool([[maybe_unused]] VkDevice device,
|
|
[[maybe_unused]] const VkCommandPoolCreateInfo* pCreateInfo,
|
|
[[maybe_unused]] const VkAllocationCallbacks* pAllocator,
|
|
VkCommandPool* pCommandPool) {
|
|
*pCommandPool = reinterpret_cast<VkCommandPool>(0x0000ABBB);
|
|
return VK_SUCCESS;
|
|
}
|
|
|
|
VKAPI_ATTR VkResult VKAPI_CALL shim_vkCreateFence([[maybe_unused]] VkDevice device,
|
|
[[maybe_unused]] const VkFenceCreateInfo* pCreateInfo,
|
|
[[maybe_unused]] const VkAllocationCallbacks* pAllocator,
|
|
VkFence* pFence) {
|
|
*pFence = reinterpret_cast<VkFence>(0x0000AAAC);
|
|
return VK_SUCCESS;
|
|
}
|
|
VKAPI_ATTR void VKAPI_CALL shim_vkDestroyFence(
|
|
[[maybe_unused]] VkDevice device, [[maybe_unused]] VkFence fence, [[maybe_unused]] const VkAllocationCallbacks* pAllocator) {}
|
|
|
|
VKAPI_ATTR VkResult VKAPI_CALL shim_vkCreateSwapchainKHR([[maybe_unused]] VkDevice device,
|
|
[[maybe_unused]] const VkSwapchainCreateInfoKHR* pCreateInfo,
|
|
[[maybe_unused]] const VkAllocationCallbacks* pAllocator,
|
|
VkSwapchainKHR* pSwapchain) {
|
|
*pSwapchain = reinterpret_cast<VkSwapchainKHR>(0x0000FFFE);
|
|
return VK_SUCCESS;
|
|
}
|
|
VKAPI_ATTR VkResult VKAPI_CALL shim_vkGetSwapchainImagesKHR(
|
|
[[maybe_unused]] VkDevice device, [[maybe_unused]] VkSwapchainKHR swapchain, uint32_t* pSwapchainImageCount, VkImage* pSwapchainImages) {
|
|
|
|
std::vector<VkImage> images = { reinterpret_cast<VkImage>(0x0000EDD0),
|
|
reinterpret_cast<VkImage>(0x0000EDD1),
|
|
reinterpret_cast<VkImage>(0x0000EDD1) };
|
|
return fill_out_count_pointer_pair(images, pSwapchainImageCount, pSwapchainImages);
|
|
}
|
|
|
|
VKAPI_ATTR VkResult VKAPI_CALL shim_vkCreateImageView([[maybe_unused]] VkDevice device,
|
|
[[maybe_unused]] const VkImageViewCreateInfo* pCreateInfo,
|
|
[[maybe_unused]] const VkAllocationCallbacks* pAllocator,
|
|
VkImageView* pView) {
|
|
if (pView) *pView = reinterpret_cast<VkImageView>(0x0000CCCE);
|
|
return VK_SUCCESS;
|
|
}
|
|
|
|
VKAPI_ATTR void VKAPI_CALL shim_vkDestroyImageView([[maybe_unused]] VkDevice device,
|
|
[[maybe_unused]] VkImageView imageView,
|
|
[[maybe_unused]] const VkAllocationCallbacks* pAllocator) {}
|
|
|
|
VKAPI_ATTR void VKAPI_CALL shim_vkDestroySwapchainKHR([[maybe_unused]] VkDevice device,
|
|
[[maybe_unused]] VkSwapchainKHR swapchain,
|
|
[[maybe_unused]] const VkAllocationCallbacks* pAllocator) {}
|
|
|
|
VKAPI_ATTR VkResult VKAPI_CALL shim_vkAcquireNextImageKHR([[maybe_unused]] VkDevice device,
|
|
[[maybe_unused]] VkSwapchainKHR swapchain,
|
|
[[maybe_unused]] uint64_t timeout,
|
|
[[maybe_unused]] VkSemaphore semaphore,
|
|
[[maybe_unused]] VkFence fence,
|
|
[[maybe_unused]] uint32_t* pImageIndex) {
|
|
return VK_SUCCESS;
|
|
}
|
|
|
|
VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL shim_vkGetDeviceProcAddr(VkDevice device, const char* pName) {
|
|
if (device == VK_NULL_HANDLE) {
|
|
return nullptr;
|
|
}
|
|
GPA_IMPL(vkDestroyDevice)
|
|
GPA_IMPL(vkGetDeviceQueue)
|
|
GPA_IMPL(vkCreateCommandPool)
|
|
GPA_IMPL(vkCreateFence)
|
|
GPA_IMPL(vkDestroyFence)
|
|
GPA_IMPL(vkCreateSwapchainKHR)
|
|
GPA_IMPL(vkGetSwapchainImagesKHR)
|
|
GPA_IMPL(vkCreateImageView)
|
|
GPA_IMPL(vkDestroyImageView)
|
|
GPA_IMPL(vkDestroySwapchainKHR)
|
|
GPA_IMPL(vkAcquireNextImageKHR)
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
VKAPI_ATTR void VKAPI_CALL shim_vkDestroySurfaceKHR([[maybe_unused]] VkInstance instance,
|
|
[[maybe_unused]] VkSurfaceKHR surface,
|
|
[[maybe_unused]] const VkAllocationCallbacks* pAllocator) {}
|
|
|
|
VKAPI_ATTR VkResult VKAPI_CALL shim_vkGetPhysicalDeviceSurfaceSupportKHR(
|
|
VkPhysicalDevice physicalDevice, uint32_t queueFamilyIndex, VkSurfaceKHR surface, VkBool32* pSupported) {
|
|
|
|
for (size_t i = 0; i < mock.physical_device_handles.size(); i++) {
|
|
if (physicalDevice == mock.physical_device_handles[i]) {
|
|
if (queueFamilyIndex >= mock.physical_devices_details[i].queue_family_properties.size()) {
|
|
return VK_ERROR_FORMAT_NOT_SUPPORTED;
|
|
}
|
|
}
|
|
}
|
|
if (surface && pSupported) *pSupported = true;
|
|
return VK_SUCCESS;
|
|
}
|
|
VKAPI_ATTR VkResult VKAPI_CALL shim_vkGetPhysicalDeviceSurfaceFormatsKHR(
|
|
[[maybe_unused]] VkPhysicalDevice physicalDevice, VkSurfaceKHR surface, uint32_t* pSurfaceFormatCount, VkSurfaceFormatKHR* pSurfaceFormats) {
|
|
for (size_t i = 0; i < mock.surface_handles.size(); i++) {
|
|
if (mock.surface_handles[i] == surface) {
|
|
return fill_out_count_pointer_pair(mock.surface_details[i].surface_formats, pSurfaceFormatCount, pSurfaceFormats);
|
|
}
|
|
}
|
|
return VK_ERROR_SURFACE_LOST_KHR;
|
|
}
|
|
|
|
VKAPI_ATTR VkResult VKAPI_CALL shim_vkGetPhysicalDeviceSurfacePresentModesKHR(
|
|
[[maybe_unused]] VkPhysicalDevice physicalDevice, VkSurfaceKHR surface, uint32_t* pPresentModeCount, VkPresentModeKHR* pPresentModes) {
|
|
for (size_t i = 0; i < mock.surface_handles.size(); i++) {
|
|
if (mock.surface_handles[i] == surface) {
|
|
return fill_out_count_pointer_pair(mock.surface_details[i].present_modes, pPresentModeCount, pPresentModes);
|
|
}
|
|
}
|
|
return VK_ERROR_SURFACE_LOST_KHR;
|
|
}
|
|
|
|
VKAPI_ATTR VkResult VKAPI_CALL shim_vkGetPhysicalDeviceSurfaceCapabilitiesKHR(
|
|
[[maybe_unused]] VkPhysicalDevice physicalDevice, VkSurfaceKHR surface, VkSurfaceCapabilitiesKHR* pSurfaceCapabilities) {
|
|
for (size_t i = 0; i < mock.surface_handles.size(); i++) {
|
|
if (mock.surface_handles[i] == surface) {
|
|
*pSurfaceCapabilities = mock.surface_details[i].capabilities;
|
|
return VK_SUCCESS;
|
|
}
|
|
}
|
|
return VK_ERROR_SURFACE_LOST_KHR;
|
|
}
|
|
|
|
PFN_vkVoidFunction shim_vkGetInstanceProcAddr([[maybe_unused]] VkInstance instance, [[maybe_unused]] const char* pName) {
|
|
GPA_IMPL(vkEnumerateInstanceVersion)
|
|
GPA_IMPL(vkEnumerateInstanceExtensionProperties)
|
|
GPA_IMPL(vkEnumerateInstanceLayerProperties)
|
|
GPA_IMPL(vkCreateInstance)
|
|
|
|
GPA_IMPL(vkDestroyInstance)
|
|
GPA_IMPL(vkCreateDebugUtilsMessengerEXT)
|
|
GPA_IMPL(vkDestroyDebugUtilsMessengerEXT)
|
|
GPA_IMPL(vkEnumeratePhysicalDevices)
|
|
GPA_IMPL(vkGetPhysicalDeviceFeatures)
|
|
GPA_IMPL(vkGetPhysicalDeviceFeatures2)
|
|
GPA_IMPL(vkGetPhysicalDeviceFeatures2KHR)
|
|
GPA_IMPL(vkGetPhysicalDeviceProperties)
|
|
GPA_IMPL(vkGetPhysicalDeviceQueueFamilyProperties)
|
|
GPA_IMPL(vkGetPhysicalDeviceMemoryProperties)
|
|
GPA_IMPL(vkEnumerateDeviceExtensionProperties)
|
|
|
|
GPA_IMPL(vkCreateDevice)
|
|
GPA_IMPL(vkGetDeviceProcAddr)
|
|
GPA_IMPL(vkGetDeviceQueue)
|
|
GPA_IMPL(vkDestroyDevice)
|
|
|
|
GPA_IMPL(vkDestroySurfaceKHR)
|
|
GPA_IMPL(vkGetPhysicalDeviceSurfaceSupportKHR)
|
|
GPA_IMPL(vkGetPhysicalDeviceSurfaceFormatsKHR)
|
|
GPA_IMPL(vkGetPhysicalDeviceSurfacePresentModesKHR)
|
|
GPA_IMPL(vkGetPhysicalDeviceSurfaceCapabilitiesKHR)
|
|
|
|
// Only used by the tests, not by vk-bootstrap
|
|
GPA_IMPL(vkCreateCommandPool)
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
extern "C" {
|
|
|
|
EXPORT_MACRO VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL vkGetInstanceProcAddr(VkInstance instance, const char* pName) {
|
|
return shim_vkGetInstanceProcAddr(instance, pName);
|
|
}
|
|
|
|
#if defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__GNU__)
|
|
#define DLSYM_FUNC_NAME dlsym
|
|
|
|
#elif defined(__APPLE__)
|
|
#define DLSYM_FUNC_NAME my_dlsym
|
|
#endif
|
|
|
|
using PFN_DLSYM = void* (*)(void* handle, const char* symbol);
|
|
|
|
|
|
#if defined(__APPLE__)
|
|
#define real_dlsym dlsym
|
|
#else
|
|
PFN_DLSYM real_dlsym = nullptr;
|
|
#endif
|
|
|
|
void* DLSYM_FUNC_NAME([[maybe_unused]] void* handle, const char* symbol) {
|
|
if (strcmp(symbol, "vkGetInstanceProcAddr") == 0) {
|
|
return reinterpret_cast<void*>(shim_vkGetInstanceProcAddr);
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
/* Shiming functions on apple is limited by the linker prefering to not use functions in the
|
|
* executable in loaded dylibs. By adding an interposer, we redirect the linker to use our
|
|
* version of the function over the real one, thus shimming the system function.
|
|
*/
|
|
#if defined(__APPLE__)
|
|
#define MACOS_ATTRIB __attribute__((section("__DATA,__interpose")))
|
|
#define VOIDCP_CAST(_func) reinterpret_cast<const void*>(&_func)
|
|
|
|
struct Interposer {
|
|
const void* shim_function;
|
|
const void* underlying_function;
|
|
};
|
|
|
|
__attribute__((used)) static Interposer _interpose_dlsym MACOS_ATTRIB = { VOIDCP_CAST(my_dlsym), VOIDCP_CAST(dlsym) };
|
|
|
|
#endif
|
|
}
|