1 /*
2 * Copyright (c) 2022 The Khronos Group Inc.
3 * Copyright (c) 2022 Valve Corporation
4 * Copyright (c) 2022 LunarG, Inc.
5 *
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 *
18 * Author: Jon Ashburn <jon@lunarg.com>
19 * Author: Courtney Goeltzenleuchter <courtney@LunarG.com>
20 * Author: Mark Young <marky@lunarg.com>
21 * Author: Lenny Komow <lenny@lunarg.com>
22 * Author: Charles Giessen <charles@lunarg.com>
23 */
24
25 #include "unknown_function_handling.h"
26
27 // If the assembly code necessary for unknown functions isn't supported, then replace all of the functions with stubs.
28 // This way, if an application queries for an unknown function, they receive NULL and can act accordingly.
29 // Previously, there was a fallback path written in C. However, it depended on the compiler optimizing the functions
30 // in such a way as to not disturb the callstack. This reliance on implementation defined behavior is unsustainable and was only
31 // known to work with GCC.
32 #if !defined(UNKNOWN_FUNCTIONS_SUPPORTED)
33
loader_init_dispatch_dev_ext(struct loader_instance * inst,struct loader_device * dev)34 void loader_init_dispatch_dev_ext(struct loader_instance *inst, struct loader_device *dev) {
35 (void)inst;
36 (void)dev;
37 }
loader_dev_ext_gpa_tramp(struct loader_instance * inst,const char * funcName)38 void *loader_dev_ext_gpa_tramp(struct loader_instance *inst, const char *funcName) {
39 (void)inst;
40 (void)funcName;
41 return NULL;
42 }
loader_dev_ext_gpa_term(struct loader_instance * inst,const char * funcName)43 void *loader_dev_ext_gpa_term(struct loader_instance *inst, const char *funcName) {
44 (void)inst;
45 (void)funcName;
46 return NULL;
47 }
48
loader_phys_dev_ext_gpa_tramp(struct loader_instance * inst,const char * funcName)49 void *loader_phys_dev_ext_gpa_tramp(struct loader_instance *inst, const char *funcName) {
50 (void)inst;
51 (void)funcName;
52 return NULL;
53 }
loader_phys_dev_ext_gpa_term(struct loader_instance * inst,const char * funcName)54 void *loader_phys_dev_ext_gpa_term(struct loader_instance *inst, const char *funcName) {
55 (void)inst;
56 (void)funcName;
57 return NULL;
58 }
59
loader_free_dev_ext_table(struct loader_instance * inst)60 void loader_free_dev_ext_table(struct loader_instance *inst) { (void)inst; }
loader_free_phys_dev_ext_table(struct loader_instance * inst)61 void loader_free_phys_dev_ext_table(struct loader_instance *inst) { (void)inst; }
62
63 #else
64
65 #include "allocation.h"
66 #include "log.h"
67
68 // Forward declarations
69 void *loader_get_dev_ext_trampoline(uint32_t index);
70 void *loader_get_phys_dev_ext_tramp(uint32_t index);
71 void *loader_get_phys_dev_ext_termin(uint32_t index);
72
73 // Device function handling
74
75 // Initialize device_ext dispatch table entry as follows:
76 // If dev == NULL find all logical devices created within this instance and
77 // init the entry (given by idx) in the ext dispatch table.
78 // If dev != NULL only initialize the entry in the given dev's dispatch table.
79 // The initialization value is gotten by calling down the device chain with
80 // GDPA.
81 // If GDPA returns NULL then don't initialize the dispatch table entry.
loader_init_dispatch_dev_ext_entry(struct loader_instance * inst,struct loader_device * dev,uint32_t idx,const char * funcName)82 void loader_init_dispatch_dev_ext_entry(struct loader_instance *inst, struct loader_device *dev, uint32_t idx, const char *funcName)
83
84 {
85 void *gdpa_value;
86 if (dev != NULL) {
87 gdpa_value = dev->loader_dispatch.core_dispatch.GetDeviceProcAddr(dev->chain_device, funcName);
88 if (gdpa_value != NULL) dev->loader_dispatch.ext_dispatch[idx] = (PFN_vkDevExt)gdpa_value;
89 } else {
90 for (struct loader_icd_term *icd_term = inst->icd_terms; icd_term != NULL; icd_term = icd_term->next) {
91 struct loader_device *ldev = icd_term->logical_device_list;
92 while (ldev) {
93 gdpa_value = ldev->loader_dispatch.core_dispatch.GetDeviceProcAddr(ldev->chain_device, funcName);
94 if (gdpa_value != NULL) ldev->loader_dispatch.ext_dispatch[idx] = (PFN_vkDevExt)gdpa_value;
95 ldev = ldev->next;
96 }
97 }
98 }
99 }
100
101 // Find all dev extension in the function names array and initialize the dispatch table
102 // for dev for each of those extension entrypoints found in function names array.
loader_init_dispatch_dev_ext(struct loader_instance * inst,struct loader_device * dev)103 void loader_init_dispatch_dev_ext(struct loader_instance *inst, struct loader_device *dev) {
104 for (uint32_t i = 0; i < MAX_NUM_UNKNOWN_EXTS; i++) {
105 if (inst->dev_ext_disp_functions[i] != NULL)
106 loader_init_dispatch_dev_ext_entry(inst, dev, i, inst->dev_ext_disp_functions[i]);
107 }
108 }
109
loader_check_icds_for_dev_ext_address(struct loader_instance * inst,const char * funcName)110 bool loader_check_icds_for_dev_ext_address(struct loader_instance *inst, const char *funcName) {
111 struct loader_icd_term *icd_term;
112 icd_term = inst->icd_terms;
113 while (NULL != icd_term) {
114 if (icd_term->scanned_icd->GetInstanceProcAddr(icd_term->instance, funcName))
115 // this icd supports funcName
116 return true;
117 icd_term = icd_term->next;
118 }
119
120 return false;
121 }
122
123 // Look in the layers list of device extensions, which contain names of entry points. If funcName is present, return true
124 // If not, call down the first layer's vkGetInstanceProcAddr to determine if any layers support the function
loader_check_layer_list_for_dev_ext_address(struct loader_instance * inst,const char * funcName)125 bool loader_check_layer_list_for_dev_ext_address(struct loader_instance *inst, const char *funcName) {
126 // Iterate over the layers.
127 for (uint32_t layer = 0; layer < inst->expanded_activated_layer_list.count; ++layer) {
128 // Iterate over the extensions.
129 const struct loader_device_extension_list *const extensions =
130 &(inst->expanded_activated_layer_list.list[layer]->device_extension_list);
131 for (uint32_t extension = 0; extension < extensions->count; ++extension) {
132 // Iterate over the entry points.
133 const struct loader_dev_ext_props *const property = &(extensions->list[extension]);
134 for (uint32_t entry = 0; entry < property->entrypoints.count; ++entry) {
135 if (strcmp(property->entrypoints.list[entry], funcName) == 0) {
136 return true;
137 }
138 }
139 }
140 }
141 // If the function pointer doesn't appear in the layer manifest for intercepted device functions, look down the
142 // vkGetInstanceProcAddr chain
143 if (inst->expanded_activated_layer_list.count > 0) {
144 const struct loader_layer_functions *const functions = &(inst->expanded_activated_layer_list.list[0]->functions);
145 if (NULL != functions->get_instance_proc_addr) {
146 return NULL != functions->get_instance_proc_addr((VkInstance)inst->instance, funcName);
147 }
148 }
149
150 return false;
151 }
152
loader_free_dev_ext_table(struct loader_instance * inst)153 void loader_free_dev_ext_table(struct loader_instance *inst) {
154 for (uint32_t i = 0; i < inst->dev_ext_disp_function_count; i++) {
155 loader_instance_heap_free(inst, inst->dev_ext_disp_functions[i]);
156 }
157 memset(inst->dev_ext_disp_functions, 0, sizeof(inst->dev_ext_disp_functions));
158 }
159
160 /*
161 * This function returns generic trampoline code address for unknown entry points.
162 * Presumably, these unknown entry points (as given by funcName) are device extension
163 * entrypoints.
164 * A function name array is used to keep a list of unknown entry points and their
165 * mapping to the device extension dispatch table.
166 * \returns
167 * For a given entry point string (funcName), if an existing mapping is found the
168 * trampoline address for that mapping is returned.
169 * Otherwise, this unknown entry point has not been seen yet.
170 * Next check if an ICD supports it, and if is_tramp is true, check if any layer
171 * supports it by calling down the chain.
172 * If so then a new entry in the function name array is added and that trampoline
173 * address for the new entry is returned.
174 * NULL is returned if the function name array is full or if no discovered layer or
175 * ICD returns a non-NULL GetProcAddr for it.
176 */
loader_dev_ext_gpa_impl(struct loader_instance * inst,const char * funcName,bool is_tramp)177 void *loader_dev_ext_gpa_impl(struct loader_instance *inst, const char *funcName, bool is_tramp) {
178 // Linearly look through already added functions to make sure we haven't seen it before
179 // if we have, return the function at the index found
180 for (uint32_t i = 0; i < inst->dev_ext_disp_function_count; i++) {
181 if (inst->dev_ext_disp_functions[i] && !strcmp(inst->dev_ext_disp_functions[i], funcName))
182 return loader_get_dev_ext_trampoline(i);
183 }
184
185 // Check if funcName is supported in either ICDs or a layer library
186 if (!loader_check_icds_for_dev_ext_address(inst, funcName)) {
187 if (!is_tramp || !loader_check_layer_list_for_dev_ext_address(inst, funcName)) {
188 // if support found in layers continue on
189 return NULL;
190 }
191 }
192 if (inst->dev_ext_disp_function_count >= MAX_NUM_UNKNOWN_EXTS) {
193 loader_log(inst, VULKAN_LOADER_ERROR_BIT, 0, "loader_dev_ext_gpa: Exhausted the unknown device function array!");
194 return NULL;
195 }
196
197 // add found function to dev_ext_disp_functions;
198 size_t funcName_len = strlen(funcName) + 1;
199 inst->dev_ext_disp_functions[inst->dev_ext_disp_function_count] =
200 (char *)loader_instance_heap_alloc(inst, funcName_len, VK_SYSTEM_ALLOCATION_SCOPE_INSTANCE);
201 if (NULL == inst->dev_ext_disp_functions[inst->dev_ext_disp_function_count]) {
202 // failed to allocate memory, return NULL
203 return NULL;
204 }
205 loader_strncpy(inst->dev_ext_disp_functions[inst->dev_ext_disp_function_count], funcName_len, funcName, funcName_len);
206 // init any dev dispatch table entries as needed
207 loader_init_dispatch_dev_ext_entry(inst, NULL, inst->dev_ext_disp_function_count, funcName);
208 void *out_function = loader_get_dev_ext_trampoline(inst->dev_ext_disp_function_count);
209 inst->dev_ext_disp_function_count++;
210 return out_function;
211 }
212
loader_dev_ext_gpa_tramp(struct loader_instance * inst,const char * funcName)213 void *loader_dev_ext_gpa_tramp(struct loader_instance *inst, const char *funcName) {
214 return loader_dev_ext_gpa_impl(inst, funcName, true);
215 }
216
loader_dev_ext_gpa_term(struct loader_instance * inst,const char * funcName)217 void *loader_dev_ext_gpa_term(struct loader_instance *inst, const char *funcName) {
218 return loader_dev_ext_gpa_impl(inst, funcName, false);
219 }
220
221 // Physical Device function handling
222
loader_check_icds_for_phys_dev_ext_address(struct loader_instance * inst,const char * funcName)223 bool loader_check_icds_for_phys_dev_ext_address(struct loader_instance *inst, const char *funcName) {
224 struct loader_icd_term *icd_term;
225 icd_term = inst->icd_terms;
226 while (NULL != icd_term) {
227 if (icd_term->scanned_icd->interface_version >= MIN_PHYS_DEV_EXTENSION_ICD_INTERFACE_VERSION &&
228 icd_term->scanned_icd->GetPhysicalDeviceProcAddr &&
229 icd_term->scanned_icd->GetPhysicalDeviceProcAddr(icd_term->instance, funcName))
230 // this icd supports funcName
231 return true;
232 icd_term = icd_term->next;
233 }
234
235 return false;
236 }
237
loader_check_layer_list_for_phys_dev_ext_address(struct loader_instance * inst,const char * funcName)238 bool loader_check_layer_list_for_phys_dev_ext_address(struct loader_instance *inst, const char *funcName) {
239 for (uint32_t layer = 0; layer < inst->expanded_activated_layer_list.count; layer++) {
240 struct loader_layer_properties *layer_prop_list = inst->expanded_activated_layer_list.list[layer];
241 // Find the first layer in the call chain which supports vk_layerGetPhysicalDeviceProcAddr
242 // and call that, returning whether it found a valid pointer for this function name.
243 // We return if the topmost layer supports GPDPA since the layer should call down the chain for us.
244 if (layer_prop_list->interface_version > 1) {
245 const struct loader_layer_functions *const functions = &(layer_prop_list->functions);
246 if (NULL != functions->get_physical_device_proc_addr) {
247 return NULL != functions->get_physical_device_proc_addr((VkInstance)inst->instance, funcName);
248 }
249 }
250 }
251 return false;
252 }
253
loader_free_phys_dev_ext_table(struct loader_instance * inst)254 void loader_free_phys_dev_ext_table(struct loader_instance *inst) {
255 for (uint32_t i = 0; i < MAX_NUM_UNKNOWN_EXTS; i++) {
256 loader_instance_heap_free(inst, inst->phys_dev_ext_disp_functions[i]);
257 }
258 memset(inst->phys_dev_ext_disp_functions, 0, sizeof(inst->phys_dev_ext_disp_functions));
259 }
260
261 // This function returns a generic trampoline or terminator function
262 // address for any unknown physical device extension commands. An array
263 // is used to keep a list of unknown entry points and their
264 // mapping to the physical device extension dispatch table (struct
265 // loader_phys_dev_ext_dispatch_table).
266 // For a given entry point string (funcName), if an existing mapping is
267 // found, then the address for that mapping is returned. The is_tramp
268 // parameter is used to decide whether to return a trampoline or terminator
269 // If it has not been seen before check if a layer or and ICD supports it.
270 // If so then a new entry in the function name array is added.
271 // Null is returned if discovered layer or ICD returns a non-NULL GetProcAddr for it
272 // or if the function name table is full.
loader_phys_dev_ext_gpa_impl(struct loader_instance * inst,const char * funcName,bool is_tramp)273 void *loader_phys_dev_ext_gpa_impl(struct loader_instance *inst, const char *funcName, bool is_tramp) {
274 assert(NULL != inst);
275
276 // We should always check to see if any ICD supports it.
277 if (!loader_check_icds_for_phys_dev_ext_address(inst, funcName)) {
278 // If we're not checking layers, or we are and it's not in a layer, just
279 // return
280 if (!is_tramp || !loader_check_layer_list_for_phys_dev_ext_address(inst, funcName)) {
281 return NULL;
282 }
283 }
284
285 bool has_found = false;
286 uint32_t new_function_index = 0;
287 // Linearly look through already added functions to make sure we haven't seen it before
288 // if we have, return the function at the index found
289 for (uint32_t i = 0; i < inst->phys_dev_ext_disp_function_count; i++) {
290 if (inst->phys_dev_ext_disp_functions[i] && !strcmp(inst->phys_dev_ext_disp_functions[i], funcName)) {
291 has_found = true;
292 new_function_index = i;
293 break;
294 }
295 }
296
297 // A never before seen function name, store it in the array
298 if (!has_found) {
299 if (inst->phys_dev_ext_disp_function_count >= MAX_NUM_UNKNOWN_EXTS) {
300 loader_log(inst, VULKAN_LOADER_ERROR_BIT, 0,
301 "loader_dev_ext_gpa: Exhausted the unknown physical device function array!");
302 return NULL;
303 }
304
305 loader_log(inst, VULKAN_LOADER_DEBUG_BIT, 0,
306 "loader_phys_dev_ext_gpa: Adding unknown physical function %s to internal store at index %u", funcName,
307 inst->phys_dev_ext_disp_function_count);
308
309 // add found function to phys_dev_ext_disp_functions;
310 size_t funcName_len = strlen(funcName) + 1;
311 inst->phys_dev_ext_disp_functions[inst->phys_dev_ext_disp_function_count] =
312 (char *)loader_instance_heap_alloc(inst, funcName_len, VK_SYSTEM_ALLOCATION_SCOPE_INSTANCE);
313 if (NULL == inst->phys_dev_ext_disp_functions[inst->phys_dev_ext_disp_function_count]) {
314 // failed to allocate memory, return NULL
315 return NULL;
316 }
317 loader_strncpy(inst->phys_dev_ext_disp_functions[inst->phys_dev_ext_disp_function_count], funcName_len, funcName,
318 funcName_len);
319
320 new_function_index = inst->phys_dev_ext_disp_function_count;
321 // increment the count so that the subsequent logic includes the newly added entry point when searching for functions
322 inst->phys_dev_ext_disp_function_count++;
323 }
324
325 // Setup the ICD function pointers
326 struct loader_icd_term *icd_term = inst->icd_terms;
327 while (NULL != icd_term) {
328 if (MIN_PHYS_DEV_EXTENSION_ICD_INTERFACE_VERSION <= icd_term->scanned_icd->interface_version &&
329 NULL != icd_term->scanned_icd->GetPhysicalDeviceProcAddr) {
330 icd_term->phys_dev_ext[new_function_index] =
331 (PFN_PhysDevExt)icd_term->scanned_icd->GetPhysicalDeviceProcAddr(icd_term->instance, funcName);
332 if (NULL != icd_term->phys_dev_ext[new_function_index]) {
333 // Make sure we set the instance dispatch to point to the loader's terminator now since we can at least handle
334 // it in one ICD.
335 inst->disp->phys_dev_ext[new_function_index] = loader_get_phys_dev_ext_termin(new_function_index);
336
337 loader_log(inst, VULKAN_LOADER_DEBUG_BIT, 0, "loader_phys_dev_ext_gpa: Driver %s returned ptr %p for %s",
338 icd_term->scanned_icd->lib_name, inst->disp->phys_dev_ext[new_function_index], funcName);
339 }
340 } else {
341 icd_term->phys_dev_ext[new_function_index] = NULL;
342 }
343
344 icd_term = icd_term->next;
345 }
346
347 // Now if this is being run in the trampoline, search for the first layer attached and query using it to get the first entry
348 // point. Only set the instance dispatch table to it if it isn't NULL.
349 if (is_tramp) {
350 for (uint32_t i = 0; i < inst->expanded_activated_layer_list.count; i++) {
351 struct loader_layer_properties *layer_prop = inst->expanded_activated_layer_list.list[i];
352 if (layer_prop->interface_version > 1 && NULL != layer_prop->functions.get_physical_device_proc_addr) {
353 void *layer_ret_function =
354 (PFN_PhysDevExt)layer_prop->functions.get_physical_device_proc_addr(inst->instance, funcName);
355 if (NULL != layer_ret_function) {
356 inst->disp->phys_dev_ext[new_function_index] = layer_ret_function;
357 loader_log(inst, VULKAN_LOADER_DEBUG_BIT, 0, "loader_phys_dev_ext_gpa: Layer %s returned ptr %p for %s",
358 layer_prop->info.layerName, inst->disp->phys_dev_ext[new_function_index], funcName);
359 break;
360 }
361 }
362 }
363 }
364
365 if (is_tramp) {
366 return loader_get_phys_dev_ext_tramp(new_function_index);
367 } else {
368 return loader_get_phys_dev_ext_termin(new_function_index);
369 }
370 }
371 // Main interface functions, makes it clear whether it is getting a terminator or trampoline
loader_phys_dev_ext_gpa_tramp(struct loader_instance * inst,const char * funcName)372 void *loader_phys_dev_ext_gpa_tramp(struct loader_instance *inst, const char *funcName) {
373 return loader_phys_dev_ext_gpa_impl(inst, funcName, true);
374 }
loader_phys_dev_ext_gpa_term(struct loader_instance * inst,const char * funcName)375 void *loader_phys_dev_ext_gpa_term(struct loader_instance *inst, const char *funcName) {
376 return loader_phys_dev_ext_gpa_impl(inst, funcName, false);
377 }
378
379 #endif
380