Skip to content

Commit 3e996d9

Browse files
mujacicaclaude
andcommitted
Fix review findings: harden GPU info, disable by default
- Use RTLD_NOW instead of RTLD_LAZY for immediate symbol validation - Add dlerror() logging on Unix when Vulkan library fails to load - Add macOS fallback when MoltenVK portability extensions unavailable - Guard against UINT64_MAX overflow when summing GPU memory heaps - Handle string allocation failures in create_gpu_info_from_device - Log when a GPU device is skipped during enumeration - Increase context_key buffer from 16 to 32 bytes - Add ARM (0x13B5) and Samsung (0x144D) vendor IDs - Improve thread safety documentation with specific lock references - Document Vulkan API version requirement and driver version encoding - Disable SENTRY_WITH_GPU_INFO by default (enable with -DSENTRY_WITH_GPU_INFO=ON) - Update CHANGELOG and README to reflect opt-in behavior Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent f86dae5 commit 3e996d9

6 files changed

Lines changed: 79 additions & 46 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@
7575

7676
**Features**:
7777

78-
- Implement the GPU Info gathering within the Native SDK ([#1336](https://github.com/getsentry/sentry-native/pull/1336))
78+
- Implement GPU info context gathering for the Native SDK. Supported on Windows, macOS, and Linux via Vulkan. Disabled by default — enable with `-DSENTRY_WITH_GPU_INFO=ON`. ([#1336](https://github.com/getsentry/sentry-native/pull/1336))
7979

8080
**Fixes**:
8181

CMakeLists.txt

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -103,22 +103,7 @@ option(SENTRY_PIC "Build sentry (and dependent) libraries as position independen
103103

104104
option(SENTRY_TRANSPORT_COMPRESSION "Enable transport gzip compression" OFF)
105105

106-
# GPU information gathering support - enabled by default on supported platforms only
107-
set(SENTRY_GPU_INFO_DEFAULT OFF)
108-
109-
# Only enable GPU info on supported platforms
110-
if(WIN32)
111-
set(SENTRY_GPU_INFO_DEFAULT ON)
112-
elseif(APPLE AND NOT IOS)
113-
set(SENTRY_GPU_INFO_DEFAULT ON)
114-
elseif(LINUX)
115-
set(SENTRY_GPU_INFO_DEFAULT ON)
116-
else()
117-
# Disable GPU info on all other platforms (Android, iOS, AIX, etc.)
118-
message(STATUS "GPU Info: Platform not supported, GPU information gathering disabled")
119-
endif()
120-
121-
option(SENTRY_WITH_GPU_INFO "Build with GPU information gathering support" ${SENTRY_GPU_INFO_DEFAULT})
106+
option(SENTRY_WITH_GPU_INFO "Build with GPU information gathering support (supported on Windows, macOS, Linux)" OFF)
122107

123108
# GPU info enabled - no longer requires Vulkan SDK (uses headers submodule + dynamic linking)
124109
if(SENTRY_WITH_GPU_INFO)

README.md

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -303,12 +303,10 @@ using `cmake -D BUILD_SHARED_LIBS=OFF ..`.
303303
tuning the thread stack guarantee parameters. Warnings and errors in the process of setting thread stack guarantees
304304
will always be logged.
305305

306-
- `SENTRY_WITH_GPU_INFO` (Default: `ON` on Windows, macOS, and Linux, otherwise `OFF`):
307-
Enables GPU information collection and reporting. When enabled, the SDK will attempt to gather GPU details such as
308-
GPU name, vendor, memory size, and driver version, which are included in event contexts. The implementation uses
309-
the Vulkan API for cross-platform GPU detection. **Requires the Vulkan SDK to be installed** - if not found,
310-
GPU information gathering will be automatically disabled during build. Setting this to `OFF` disables GPU
311-
information collection entirely, which can reduce dependencies and binary size.
306+
- `SENTRY_WITH_GPU_INFO` (Default: `OFF`):
307+
Enables GPU information collection and reporting. Supported on Windows, macOS, and Linux. When enabled, the SDK
308+
will attempt to gather GPU details such as GPU name, vendor, memory size, and driver version, which are included
309+
in event contexts. The implementation uses the Vulkan API with dynamic loading for cross-platform GPU detection.
312310

313311
### Support Matrix
314312

src/gpu/sentry_gpu_common.c

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ sentry__gpu_vendor_id_to_name(unsigned int vendor_id)
2020
case 0x5143:
2121
case 0x17CB:
2222
return sentry__string_clone("Qualcomm");
23+
case 0x13B5:
24+
return sentry__string_clone("ARM");
25+
case 0x144D:
26+
return sentry__string_clone("Samsung Electronics");
2327
case 0x1AE0:
2428
return sentry__string_clone("Google");
2529
case 0x1010:
@@ -147,7 +151,8 @@ sentry__add_gpu_contexts(sentry_value_t contexts)
147151
sentry_value_t gpu_context
148152
= create_gpu_context_from_info(gpu_list->gpus[i]);
149153
if (!sentry_value_is_null(gpu_context)) {
150-
char context_key[16];
154+
// "gpu" + up to 10 digits for uint32 + NUL = 14 bytes max
155+
char context_key[32];
151156
if (i == 0) {
152157
snprintf(context_key, sizeof(context_key), "gpu");
153158
} else {

src/gpu/sentry_gpu_vulkan.c

Lines changed: 57 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,10 @@
1212
# define SENTRY_LOAD_LIBRARY(name) LoadLibraryA(name)
1313
# define SENTRY_GET_PROC_ADDRESS(handle, name) GetProcAddress(handle, name)
1414
# define SENTRY_FREE_LIBRARY(handle) FreeLibrary(handle)
15-
#elif defined(__APPLE__)
16-
# include <dlfcn.h>
17-
# define SENTRY_LIBRARY_HANDLE void *
18-
# define SENTRY_LOAD_LIBRARY(name) dlopen(name, RTLD_LAZY)
19-
# define SENTRY_GET_PROC_ADDRESS(handle, name) dlsym(handle, name)
20-
# define SENTRY_FREE_LIBRARY(handle) dlclose(handle)
2115
#else
2216
# include <dlfcn.h>
2317
# define SENTRY_LIBRARY_HANDLE void *
24-
# define SENTRY_LOAD_LIBRARY(name) dlopen(name, RTLD_LAZY)
18+
# define SENTRY_LOAD_LIBRARY(name) dlopen(name, RTLD_NOW)
2519
# define SENTRY_GET_PROC_ADDRESS(handle, name) dlsym(handle, name)
2620
# define SENTRY_FREE_LIBRARY(handle) dlclose(handle)
2721
#endif
@@ -32,10 +26,10 @@
3226
#endif
3327

3428
// Dynamic function pointers
35-
// Note: These are not thread-safe, but this is not a concern for our use case.
36-
// We are only accessing these during scope initialization, which is explicitly
37-
// locked, so it's fair to assume that only single-threadded access is happening
38-
// here.
29+
// Thread safety: These statics are only accessed from sentry__add_gpu_contexts()
30+
// which is called during scope initialization in get_scope() (sentry_scope.c).
31+
// get_scope() is only reachable through sentry__scope_lock() which holds
32+
// g_lock, so concurrent access cannot happen.
3933
static PFN_vkCreateInstance pfn_vkCreateInstance = NULL;
4034
static PFN_vkDestroyInstance pfn_vkDestroyInstance = NULL;
4135
static PFN_vkEnumeratePhysicalDevices pfn_vkEnumeratePhysicalDevices = NULL;
@@ -57,6 +51,7 @@ load_vulkan_library(void)
5751
#ifdef _WIN32
5852
vulkan_library = SENTRY_LOAD_LIBRARY("vulkan-1.dll");
5953
#elif defined(__APPLE__)
54+
// MoltenVK / Vulkan SDK install paths on macOS
6055
vulkan_library = SENTRY_LOAD_LIBRARY("libvulkan.1.dylib");
6156
if (!vulkan_library) {
6257
vulkan_library = SENTRY_LOAD_LIBRARY("libvulkan.dylib");
@@ -73,7 +68,13 @@ load_vulkan_library(void)
7368
#endif
7469

7570
if (!vulkan_library) {
71+
#ifndef _WIN32
72+
const char *dl_err = dlerror();
73+
SENTRY_WARNF("Failed to load Vulkan library: %s",
74+
dl_err ? dl_err : "unknown error");
75+
#else
7676
SENTRY_WARN("Failed to load Vulkan library");
77+
#endif
7778
return false;
7879
}
7980

@@ -128,28 +129,43 @@ create_vulkan_instance(void)
128129
app_info.applicationVersion = VK_MAKE_VERSION(1, 0, 0);
129130
app_info.pEngineName = "Sentry";
130131
app_info.engineVersion = VK_MAKE_VERSION(1, 0, 0);
132+
// Vulkan 1.0 is the minimum version we require; all physical device
133+
// property and memory queries we use are part of the 1.0 core API.
131134
app_info.apiVersion = VK_API_VERSION_1_0;
132135

133136
VkInstanceCreateInfo create_info = { 0 };
134137
create_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
135138
create_info.pApplicationInfo = &app_info;
136139

140+
VkInstance instance = VK_NULL_HANDLE;
141+
VkResult result;
142+
137143
#ifdef __APPLE__
138-
// Required extensions for MoltenVK on macOS
144+
// On macOS, Vulkan is provided through MoltenVK which requires the
145+
// portability enumeration extension and flag to expose MoltenVK devices.
146+
// We try with extensions first, and fall back to a plain instance if
147+
// the extensions are not available (e.g. native Vulkan without MoltenVK).
139148
const char *extensions[] = { "VK_KHR_portability_enumeration",
140149
"VK_KHR_get_physical_device_properties2" };
141150
create_info.enabledExtensionCount = 2;
142151
create_info.ppEnabledExtensionNames = extensions;
143-
144-
// Required flag for MoltenVK on macOS
145152
create_info.flags = VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR;
146-
147-
// Disable validation layers on macOS as they may not be available
148153
create_info.enabledLayerCount = 0;
154+
155+
result = pfn_vkCreateInstance(&create_info, NULL, &instance);
156+
if (result == VK_SUCCESS) {
157+
return instance;
158+
}
159+
160+
// Fallback: try without portability extensions
161+
SENTRY_DEBUG(
162+
"MoltenVK extensions not available, retrying without portability");
163+
create_info.enabledExtensionCount = 0;
164+
create_info.ppEnabledExtensionNames = NULL;
165+
create_info.flags = 0;
149166
#endif
150167

151-
VkInstance instance = VK_NULL_HANDLE;
152-
VkResult result = pfn_vkCreateInstance(&create_info, NULL, &instance);
168+
result = pfn_vkCreateInstance(&create_info, NULL, &instance);
153169
if (result != VK_SUCCESS) {
154170
SENTRY_DEBUGF("Failed to create Vulkan instance: %d", result);
155171
return VK_NULL_HANDLE;
@@ -174,11 +190,16 @@ create_gpu_info_from_device(VkPhysicalDevice device)
174190

175191
memset(gpu_info, 0, sizeof(sentry_gpu_info_t));
176192

193+
// deviceName is a fixed char array in VkPhysicalDeviceProperties,
194+
// guaranteed to be null-terminated by the Vulkan spec.
177195
gpu_info->name = sentry__string_clone(properties.deviceName);
178196
gpu_info->vendor_id = properties.vendorID;
179197
gpu_info->device_id = properties.deviceID;
180198
gpu_info->vendor_name = sentry__gpu_vendor_id_to_name(properties.vendorID);
181199

200+
// The Vulkan driverVersion encoding is vendor-specific. We use the
201+
// standard VK_VERSION macros which work for most drivers; on some
202+
// proprietary drivers the output may not match the marketed version.
182203
char driver_version_str[64];
183204
uint32_t driver_version = properties.driverVersion;
184205
snprintf(driver_version_str, sizeof(driver_version_str), "%u.%u.%u",
@@ -187,11 +208,26 @@ create_gpu_info_from_device(VkPhysicalDevice device)
187208

188209
gpu_info->driver_version = sentry__string_clone(driver_version_str);
189210

211+
// Any NULL string fields (from allocation failure) are handled gracefully
212+
// by create_gpu_context_from_info which checks each field before use.
213+
if (!gpu_info->name && !gpu_info->vendor_name
214+
&& !gpu_info->driver_version) {
215+
SENTRY_WARN("All string allocations failed for GPU info");
216+
sentry_free(gpu_info);
217+
return NULL;
218+
}
219+
190220
uint64_t total_memory = 0;
191221
for (uint32_t i = 0; i < memory_properties.memoryHeapCount; i++) {
192222
if (memory_properties.memoryHeaps[i].flags
193223
& VK_MEMORY_HEAP_DEVICE_LOCAL_BIT) {
194-
total_memory += memory_properties.memoryHeaps[i].size;
224+
uint64_t heap_size = memory_properties.memoryHeaps[i].size;
225+
// Guard against overflow when summing heap sizes
226+
if (total_memory > UINT64_MAX - heap_size) {
227+
total_memory = UINT64_MAX;
228+
break;
229+
}
230+
total_memory += heap_size;
195231
}
196232
}
197233

@@ -265,6 +301,8 @@ sentry__get_gpu_info(void)
265301
if (gpu_info) {
266302
gpu_list->gpus[gpu_list->count] = gpu_info;
267303
gpu_list->count++;
304+
} else {
305+
SENTRY_DEBUGF("Skipped GPU device %u (info creation failed)", i);
268306
}
269307
}
270308

tests/unit/test_gpu.c

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,9 @@ SENTRY_TEST(gpu_info_vendor_id_known)
8181
// Test the common vendor ID to name mapping function with all supported
8282
// vendors
8383
unsigned int test_vendor_ids[]
84-
= { 0x10DE, 0x1002, 0x8086, 0x106B, 0x1414, 0x5143, 0x1AE0, 0x1010,
85-
0x1023, 0x102B, 0x121A, 0x18CA, 0x1039, 0x126F, 0x0000, 0xFFFF };
84+
= { 0x10DE, 0x1002, 0x8086, 0x106B, 0x1414, 0x5143, 0x13B5, 0x144D,
85+
0x1AE0, 0x1010, 0x1023, 0x102B, 0x121A, 0x18CA, 0x1039, 0x126F,
86+
0x0000, 0xFFFF };
8687

8788
for (size_t i = 0; i < sizeof(test_vendor_ids) / sizeof(test_vendor_ids[0]);
8889
i++) {
@@ -109,6 +110,12 @@ SENTRY_TEST(gpu_info_vendor_id_known)
109110
case 0x5143:
110111
TEST_CHECK(strstr(vendor_name, "Qualcomm") != NULL);
111112
break;
113+
case 0x13B5:
114+
TEST_CHECK(strstr(vendor_name, "ARM") != NULL);
115+
break;
116+
case 0x144D:
117+
TEST_CHECK(strstr(vendor_name, "Samsung") != NULL);
118+
break;
112119
case 0x1AE0:
113120
TEST_CHECK(strstr(vendor_name, "Google") != NULL);
114121
break;
@@ -273,7 +280,7 @@ SENTRY_TEST(gpu_context_scope_integration)
273280

274281
// Check for additional GPUs (gpu2, gpu3, etc.)
275282
for (int i = 2; i <= 4; i++) {
276-
char context_key[16];
283+
char context_key[32];
277284
snprintf(context_key, sizeof(context_key), "gpu%d", i);
278285
sentry_value_t additional_gpu
279286
= sentry_value_get_by_key(contexts, context_key);

0 commit comments

Comments
 (0)