[OpenMP][OMPD] Add symbol lookup helper for GDB plugin (#221956)

On LLVM today, `libompd.so` exists with `dlopen`, finds each API with
`dlsym` and checks error with `dlerror`.

**Missing piece**: There is no single place that:

load this library
look up this name
give me a real error string

**Issues**:

A later change would have to edit 30+ calls sites,
`dlerror()` is easy to misuse,
failures often drop the OS error string.

**Fix**: Add these helpers:

`ompd_load_library(path)` – load `libompd.so`
`ompd_get_symbol(name)` – look up one function
`ompd_get_dl_error()` – last error text or NULL

The plugin still uses `dlopen/dlsym` inside that helper. No LLVM
Support. No `setup.py`.

GitOrigin-RevId: 14e941a0566ae06e982c5d8ddd085697c48fd413
diff --git a/libompd/gdb-plugin/CMakeLists.txt b/libompd/gdb-plugin/CMakeLists.txt
index d4fd03d..bfa1128 100644
--- a/libompd/gdb-plugin/CMakeLists.txt
+++ b/libompd/gdb-plugin/CMakeLists.txt
@@ -16,7 +16,7 @@
 include_directories (${OMPD_INCLUDE_PATH})
 include_directories (${LIBOMP_INCLUDE_DIR})
 add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/python-module/ompd/__init__.py
-                   DEPENDS ompdModule.c ompdAPITests.c ompd/frame_filter.py ompd/__init__.py ompd/ompd_address_space.py ompd/ompd_callbacks.py ompd/ompd_handles.py ompd/ompd.py
+                   DEPENDS ompdModule.c ompdDLService.c ompdAPITests.c ompd/frame_filter.py ompd/__init__.py ompd/ompd_address_space.py ompd/ompd_callbacks.py ompd/ompd_handles.py ompd/ompd.py
                    COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR}/ompd ${CMAKE_CURRENT_BINARY_DIR}/python-module/ompd/
                    WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
 
@@ -24,7 +24,7 @@
                   DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/python-module/ompd/__init__.py
                   COMMENT "Building the OMPD GDB plugin")
 
-add_library (ompdModule MODULE ompdModule.c ompdAPITests.c)
+add_library (ompdModule MODULE ompdModule.c ompdAPITests.c ompdDLService.c)
 include_directories (
         ${LIBOMP_INCLUDE_DIR}
         ${LIBOMP_SRC_DIR}
diff --git a/libompd/gdb-plugin/ompd/ompd.py b/libompd/gdb-plugin/ompd/ompd.py
index 8355865..a1250b6 100644
--- a/libompd/gdb-plugin/ompd/ompd.py
+++ b/libompd/gdb-plugin/ompd/ompd.py
@@ -57,13 +57,15 @@
             lib_list = gdb.parse_and_eval("(char**)ompd_dll_locations")
 
             i = 0
+            last_dl_error = None
             while lib_list[i]:
                 ret = ompdModule.ompd_open(lib_list[i].string())
                 if ret == -1:
                     raise ValueError("Handle of OMPD library is not a valid string!")
                 if ret == -2:
+                    last_dl_error = ompdModule.ompd_get_dl_error()
                     print("ret == -2")
-                    pass  # It's ok to fail on dlopen
+                    pass  # It's ok to fail on dlopen; try the next path
                 if ret == -3:
                     print("ret == -3")
                     pass  # It's ok to fail on dlsym
@@ -80,6 +82,8 @@
                     return
                 i = i + 1
 
+            if last_dl_error:
+                raise ValueError("OMPD library could not be loaded: %s" % last_dl_error)
             raise ValueError("OMPD library could not be loaded!")
         except:
             traceback.print_exc()
diff --git a/libompd/gdb-plugin/ompdAPITests.c b/libompd/gdb-plugin/ompdAPITests.c
index 912914c..c830c3f 100644
--- a/libompd/gdb-plugin/ompdAPITests.c
+++ b/libompd/gdb-plugin/ompdAPITests.c
@@ -1,5 +1,6 @@
+#include "ompdDLService.h"
+
 #include <Python.h>
-#include <dlfcn.h>
 #include <errno.h>
 #include <omp-tools.h>
 #include <pthread.h>
@@ -7,8 +8,6 @@
 #include <stdlib.h>
 #include <string.h>
 
-extern void *ompd_library;
-
 struct _ompd_aspace_cont {
   int id;
 };
@@ -810,7 +809,11 @@
 
   printf("Test: With Correct Arguments.\n");
   ompd_rc_t (*my_ompd_init)(ompd_word_t version, ompd_callbacks_t *) =
-      dlsym(ompd_library, "ompd_initialize");
+      ompd_get_symbol("ompd_initialize");
+  if (!my_ompd_init) {
+    printf("Failed to look up ompd_initialize.\n");
+    return Py_None;
+  }
   rc = my_ompd_init(version, &table);
   if (rc != ompd_rc_ok) {
     printf("Failed, with return code = %d\n", rc);
diff --git a/libompd/gdb-plugin/ompdDLService.c b/libompd/gdb-plugin/ompdDLService.c
new file mode 100644
index 0000000..dc7a404
--- /dev/null
+++ b/libompd/gdb-plugin/ompdDLService.c
@@ -0,0 +1,87 @@
+/*
+ * ompdDLService.c -- Load libompd and look up OMPD API symbols.
+ */
+
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "ompdDLService.h"
+
+#include <dlfcn.h>
+#include <string.h>
+
+void *ompd_library = NULL;
+
+static char last_error[256];
+
+static void set_error(const char *msg) {
+  if (!msg || !msg[0]) {
+    last_error[0] = '\0';
+    return;
+  }
+  strncpy(last_error, msg, sizeof(last_error) - 1);
+  last_error[sizeof(last_error) - 1] = '\0';
+}
+
+static void clear_error(void) {
+  last_error[0] = '\0';
+  (void)dlerror();
+}
+
+int ompd_load_library(const char *name) {
+  const char *dlerr;
+
+  clear_error();
+  if (!name || !name[0]) {
+    set_error("OMPD library path is empty");
+    ompd_library = NULL;
+    return -1;
+  }
+
+  ompd_library = dlopen(name, RTLD_LAZY);
+  dlerr = dlerror();
+  if (dlerr) {
+    set_error(dlerr);
+    ompd_library = NULL;
+    return -1;
+  }
+  if (!ompd_library) {
+    set_error("dlopen returned NULL");
+    return -1;
+  }
+  return 0;
+}
+
+void *ompd_get_symbol(const char *name) {
+  const char *dlerr;
+  void *sym;
+
+  clear_error();
+  if (!ompd_library) {
+    set_error("OMPD library is not loaded");
+    return NULL;
+  }
+  if (!name || !name[0]) {
+    set_error("OMPD symbol name is empty");
+    return NULL;
+  }
+
+  sym = dlsym(ompd_library, name);
+  dlerr = dlerror();
+  if (dlerr) {
+    set_error(dlerr);
+    return NULL;
+  }
+  return sym;
+}
+
+const char *ompd_get_dl_error(void) {
+  if (!last_error[0])
+    return NULL;
+  return last_error;
+}
diff --git a/libompd/gdb-plugin/ompdDLService.h b/libompd/gdb-plugin/ompdDLService.h
new file mode 100644
index 0000000..6c95185
--- /dev/null
+++ b/libompd/gdb-plugin/ompdDLService.h
@@ -0,0 +1,40 @@
+/*
+ * ompdDLService.h -- Load libompd and look up OMPD API symbols.
+ */
+
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef OPENMP_LIBOMPD_GDB_PLUGIN_OMPD_DL_SERVICE_H
+#define OPENMP_LIBOMPD_GDB_PLUGIN_OMPD_DL_SERVICE_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Handle of the loaded OMPD library, or NULL if none is loaded. */
+extern void *ompd_library;
+
+/* Load the OMPD library at name.
+ * Returns 0 on success. On failure, returns -1 and ompd_get_dl_error()
+ * describes the problem. */
+int ompd_load_library(const char *name);
+
+/* Look up name in the loaded OMPD library.
+ * Returns the symbol address, or NULL on failure. */
+void *ompd_get_symbol(const char *name);
+
+/* Last load/lookup error, or NULL if the last helper call succeeded.
+ * The string stays valid until the next helper call. */
+const char *ompd_get_dl_error(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/libompd/gdb-plugin/ompdModule.c b/libompd/gdb-plugin/ompdModule.c
index 9078b24..d591824 100644
--- a/libompd/gdb-plugin/ompdModule.c
+++ b/libompd/gdb-plugin/ompdModule.c
@@ -10,18 +10,17 @@
 //
 //===----------------------------------------------------------------------===//
 
+#include "ompdDLService.h"
+
 #include <Python.h>
 #include <omp-tools.h>
 // #include <ompd.h>
-#include <dlfcn.h>
 #include <errno.h>
 #include <pthread.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 
-void *ompd_library;
-
 #define OMPD_WEAK_ATTR __attribute__((weak))
 
 struct _ompd_aspace_cont {
@@ -41,8 +40,8 @@
 OMPD_WEAK_ATTR ompd_rc_t ompd_get_api_version(ompd_word_t *addr) {
   static ompd_rc_t (*my_get_api_version)(ompd_word_t *) = NULL;
   if (!my_get_api_version) {
-    my_get_api_version = dlsym(ompd_library, "ompd_get_api_version");
-    if (dlerror()) {
+    my_get_api_version = ompd_get_symbol("ompd_get_api_version");
+    if (ompd_get_dl_error()) {
       return ompd_rc_error;
     }
   }
@@ -52,8 +51,8 @@
 OMPD_WEAK_ATTR ompd_rc_t ompd_get_version_string(const char **string) {
   static ompd_rc_t (*my_get_version_string)(const char **) = NULL;
   if (!my_get_version_string) {
-    my_get_version_string = dlsym(ompd_library, "ompd_get_version_string");
-    if (dlerror()) {
+    my_get_version_string = ompd_get_symbol("ompd_get_version_string");
+    if (ompd_get_dl_error()) {
       return ompd_rc_error;
     }
   }
@@ -63,8 +62,8 @@
 OMPD_WEAK_ATTR ompd_rc_t ompd_finalize(void) {
   static ompd_rc_t (*my_ompd_finalize)(void) = NULL;
   if (!my_ompd_finalize) {
-    my_ompd_finalize = dlsym(ompd_library, "ompd_finalize");
-    if (dlerror()) {
+    my_ompd_finalize = ompd_get_symbol("ompd_finalize");
+    if (ompd_get_dl_error()) {
       return ompd_rc_error;
     }
   }
@@ -77,8 +76,8 @@
   static ompd_rc_t (*my_ompd_process_initialize)(
       ompd_address_space_context_t *, ompd_address_space_handle_t **) = NULL;
   if (!my_ompd_process_initialize) {
-    my_ompd_process_initialize = dlsym(ompd_library, "ompd_process_initialize");
-    if (dlerror()) {
+    my_ompd_process_initialize = ompd_get_symbol("ompd_process_initialize");
+    if (ompd_get_dl_error()) {
       return ompd_rc_error;
     }
   }
@@ -90,8 +89,8 @@
   static ompd_rc_t (*my_ompd_get_omp_version)(ompd_address_space_handle_t *,
                                               ompd_word_t *) = NULL;
   if (!my_ompd_get_omp_version) {
-    my_ompd_get_omp_version = dlsym(ompd_library, "ompd_get_omp_version");
-    if (dlerror()) {
+    my_ompd_get_omp_version = ompd_get_symbol("ompd_get_omp_version");
+    if (ompd_get_dl_error()) {
       return ompd_rc_error;
     }
   }
@@ -104,8 +103,8 @@
       ompd_address_space_handle_t *, const char **) = NULL;
   if (!my_ompd_get_omp_version_string) {
     my_ompd_get_omp_version_string =
-        dlsym(ompd_library, "ompd_get_omp_version_string");
-    if (dlerror()) {
+        ompd_get_symbol("ompd_get_omp_version_string");
+    if (ompd_get_dl_error()) {
       return ompd_rc_error;
     }
   }
@@ -119,8 +118,8 @@
       ompd_address_space_handle_t *, ompd_thread_id_t, ompd_size_t,
       const void *, ompd_thread_handle_t **) = NULL;
   if (!my_get_thread_handle) {
-    my_get_thread_handle = dlsym(ompd_library, "ompd_get_thread_handle");
-    if (dlerror()) {
+    my_get_thread_handle = ompd_get_symbol("ompd_get_thread_handle");
+    if (ompd_get_dl_error()) {
       return ompd_rc_error;
     }
   }
@@ -133,9 +132,8 @@
   static ompd_rc_t (*my_get_thread_in_parallel)(ompd_parallel_handle_t *, int,
                                                 ompd_thread_handle_t **) = NULL;
   if (!my_get_thread_in_parallel) {
-    my_get_thread_in_parallel =
-        dlsym(ompd_library, "ompd_get_thread_in_parallel");
-    if (dlerror()) {
+    my_get_thread_in_parallel = ompd_get_symbol("ompd_get_thread_in_parallel");
+    if (ompd_get_dl_error()) {
       return ompd_rc_error;
     }
   }
@@ -148,9 +146,8 @@
   static ompd_rc_t (*my_thread_handle_compare)(
       ompd_thread_handle_t *, ompd_thread_handle_t *, int *) = NULL;
   if (!my_thread_handle_compare) {
-    my_thread_handle_compare =
-        dlsym(ompd_library, "ompd_thread_handle_compare");
-    if (dlerror()) {
+    my_thread_handle_compare = ompd_get_symbol("ompd_thread_handle_compare");
+    if (ompd_get_dl_error()) {
       return ompd_rc_error;
     }
   }
@@ -164,8 +161,8 @@
       ompd_thread_handle_t *, ompd_parallel_handle_t **) = NULL;
   if (!my_get_current_parallel_handle) {
     my_get_current_parallel_handle =
-        dlsym(ompd_library, "ompd_get_curr_parallel_handle");
-    if (dlerror()) {
+        ompd_get_symbol("ompd_get_curr_parallel_handle");
+    if (ompd_get_dl_error()) {
       return ompd_rc_error;
     }
   }
@@ -179,8 +176,8 @@
       ompd_parallel_handle_t *, ompd_parallel_handle_t *, int *) = NULL;
   if (!my_parallel_handle_compare) {
     my_parallel_handle_compare =
-        dlsym(ompd_library, "ompd_parallel_handle_compare");
-    if (dlerror()) {
+        ompd_get_symbol("ompd_parallel_handle_compare");
+    if (ompd_get_dl_error()) {
       return ompd_rc_error;
     }
   }
@@ -195,8 +192,8 @@
       ompd_parallel_handle_t *, ompd_parallel_handle_t **) = NULL;
   if (!my_get_enclosing_parallel_handle) {
     my_get_enclosing_parallel_handle =
-        dlsym(ompd_library, "ompd_get_enclosing_parallel_handle");
-    if (dlerror()) {
+        ompd_get_symbol("ompd_get_enclosing_parallel_handle");
+    if (ompd_get_dl_error()) {
       return ompd_rc_error;
     }
   }
@@ -210,8 +207,8 @@
       ompd_task_handle_t *, ompd_parallel_handle_t **) = NULL;
   if (!my_get_task_parallel_handle) {
     my_get_task_parallel_handle =
-        dlsym(ompd_library, "ompd_get_task_parallel_handle");
-    if (dlerror()) {
+        ompd_get_symbol("ompd_get_task_parallel_handle");
+    if (ompd_get_dl_error()) {
       return ompd_rc_error;
     }
   }
@@ -223,9 +220,8 @@
   static ompd_rc_t (*my_get_current_task_handle)(ompd_thread_handle_t *,
                                                  ompd_task_handle_t **) = NULL;
   if (!my_get_current_task_handle) {
-    my_get_current_task_handle =
-        dlsym(ompd_library, "ompd_get_curr_task_handle");
-    if (dlerror()) {
+    my_get_current_task_handle = ompd_get_symbol("ompd_get_curr_task_handle");
+    if (ompd_get_dl_error()) {
       return ompd_rc_error;
     }
   }
@@ -238,8 +234,8 @@
       ompd_task_handle_t *, ompd_task_handle_t **) = NULL;
   if (!my_get_generating_task_handle) {
     my_get_generating_task_handle =
-        dlsym(ompd_library, "ompd_get_generating_task_handle");
-    if (dlerror()) {
+        ompd_get_symbol("ompd_get_generating_task_handle");
+    if (ompd_get_dl_error()) {
       return ompd_rc_error;
     }
   }
@@ -252,8 +248,8 @@
       ompd_task_handle_t *, ompd_task_handle_t **) = NULL;
   if (!my_get_scheduling_task_handle) {
     my_get_scheduling_task_handle =
-        dlsym(ompd_library, "ompd_get_scheduling_task_handle");
-    if (dlerror()) {
+        ompd_get_symbol("ompd_get_scheduling_task_handle");
+    if (ompd_get_dl_error()) {
       return ompd_rc_error;
     }
   }
@@ -266,8 +262,8 @@
   static ompd_rc_t (*my_get_task_in_parallel)(ompd_parallel_handle_t *, int,
                                               ompd_task_handle_t **) = NULL;
   if (!my_get_task_in_parallel) {
-    my_get_task_in_parallel = dlsym(ompd_library, "ompd_get_task_in_parallel");
-    if (dlerror()) {
+    my_get_task_in_parallel = ompd_get_symbol("ompd_get_task_in_parallel");
+    if (ompd_get_dl_error()) {
       return ompd_rc_error;
     }
   }
@@ -280,8 +276,8 @@
   static ompd_rc_t (*my_get_task_frame)(
       ompd_task_handle_t *, ompd_frame_info_t *, ompd_frame_info_t *) = NULL;
   if (!my_get_task_frame) {
-    my_get_task_frame = dlsym(ompd_library, "ompd_get_task_frame");
-    if (dlerror()) {
+    my_get_task_frame = ompd_get_symbol("ompd_get_task_frame");
+    if (ompd_get_dl_error()) {
       return ompd_rc_error;
     }
   }
@@ -295,8 +291,8 @@
   static ompd_rc_t (*my_get_icv_from_scope)(void *, ompd_scope_t, ompd_icv_id_t,
                                             ompd_word_t *) = NULL;
   if (!my_get_icv_from_scope) {
-    my_get_icv_from_scope = dlsym(ompd_library, "ompd_get_icv_from_scope");
-    if (dlerror()) {
+    my_get_icv_from_scope = ompd_get_symbol("ompd_get_icv_from_scope");
+    if (ompd_get_dl_error()) {
       return ompd_rc_error;
     }
   }
@@ -311,8 +307,8 @@
       ompd_address_space_handle_t *, ompd_icv_id_t, ompd_icv_id_t *,
       const char **, ompd_scope_t *, int *) = NULL;
   if (!my_enumerate_icvs) {
-    my_enumerate_icvs = dlsym(ompd_library, "ompd_enumerate_icvs");
-    if (dlerror()) {
+    my_enumerate_icvs = ompd_get_symbol("ompd_enumerate_icvs");
+    if (ompd_get_dl_error()) {
       return ompd_rc_error;
     }
   }
@@ -327,8 +323,8 @@
                                           ompd_word_t, ompd_word_t *,
                                           const char **, ompd_word_t *) = NULL;
   if (!my_enumerate_states) {
-    my_enumerate_states = dlsym(ompd_library, "ompd_enumerate_states");
-    if (dlerror()) {
+    my_enumerate_states = ompd_get_symbol("ompd_enumerate_states");
+    if (ompd_get_dl_error()) {
       return ompd_rc_error;
     }
   }
@@ -342,8 +338,8 @@
   static ompd_rc_t (*my_get_state)(ompd_thread_handle_t *, ompd_word_t *,
                                    ompd_wait_id_t *) = NULL;
   if (!my_get_state) {
-    my_get_state = dlsym(ompd_library, "ompd_get_state");
-    if (dlerror()) {
+    my_get_state = ompd_get_symbol("ompd_get_state");
+    if (ompd_get_dl_error()) {
       return ompd_rc_error;
     }
   }
@@ -355,8 +351,8 @@
   static ompd_rc_t (*my_get_task_function)(ompd_task_handle_t *,
                                            ompd_address_t *) = NULL;
   if (!my_get_task_function) {
-    my_get_task_function = dlsym(ompd_library, "ompd_get_task_function");
-    if (dlerror()) {
+    my_get_task_function = ompd_get_symbol("ompd_get_task_function");
+    if (ompd_get_dl_error()) {
       return ompd_rc_error;
     }
   }
@@ -369,8 +365,8 @@
   static ompd_rc_t (*my_get_thread_id)(ompd_thread_handle_t *, ompd_thread_id_t,
                                        ompd_size_t, void *) = NULL;
   if (!my_get_thread_id) {
-    my_get_thread_id = dlsym(ompd_library, "ompd_get_thread_id");
-    if (dlerror()) {
+    my_get_thread_id = ompd_get_symbol("ompd_get_thread_id");
+    if (ompd_get_dl_error()) {
       return ompd_rc_error;
     }
   }
@@ -383,8 +379,8 @@
   static ompd_rc_t (*my_get_tool_data)(void *, ompd_scope_t, ompd_word_t *,
                                        ompd_address_t *) = NULL;
   if (!my_get_tool_data) {
-    my_get_tool_data = dlsym(ompd_library, "ompd_get_tool_data");
-    if (dlerror()) {
+    my_get_tool_data = ompd_get_symbol("ompd_get_tool_data");
+    if (ompd_get_dl_error()) {
       return ompd_rc_error;
     }
   }
@@ -398,8 +394,8 @@
       void *, ompd_scope_t, ompd_icv_id_t, const char **) = NULL;
   if (!my_get_icv_string_from_scope) {
     my_get_icv_string_from_scope =
-        dlsym(ompd_library, "ompd_get_icv_string_from_scope");
-    if (dlerror()) {
+        ompd_get_symbol("ompd_get_icv_string_from_scope");
+    if (ompd_get_dl_error()) {
       return ompd_rc_error;
     }
   }
@@ -410,8 +406,8 @@
 ompd_rel_thread_handle(ompd_thread_handle_t *threadHandle) {
   static ompd_rc_t (*my_release_thread_handle)(ompd_thread_handle_t *) = NULL;
   if (!my_release_thread_handle) {
-    my_release_thread_handle = dlsym(ompd_library, "ompd_rel_thread_handle");
-    if (dlerror()) {
+    my_release_thread_handle = ompd_get_symbol("ompd_rel_thread_handle");
+    if (ompd_get_dl_error()) {
       return ompd_rc_error;
     }
   }
@@ -423,9 +419,8 @@
   static ompd_rc_t (*my_release_parallel_handle)(ompd_parallel_handle_t *) =
       NULL;
   if (!my_release_parallel_handle) {
-    my_release_parallel_handle =
-        dlsym(ompd_library, "ompd_rel_parallel_handle");
-    if (dlerror()) {
+    my_release_parallel_handle = ompd_get_symbol("ompd_rel_parallel_handle");
+    if (ompd_get_dl_error()) {
       return ompd_rc_error;
     }
   }
@@ -435,8 +430,8 @@
 OMPD_WEAK_ATTR ompd_rc_t ompd_rel_task_handle(ompd_task_handle_t *taskHandle) {
   static ompd_rc_t (*my_release_task_handle)(ompd_task_handle_t *) = NULL;
   if (!my_release_task_handle) {
-    my_release_task_handle = dlsym(ompd_library, "ompd_rel_task_handle");
-    if (dlerror()) {
+    my_release_task_handle = ompd_get_symbol("ompd_rel_task_handle");
+    if (ompd_get_dl_error()) {
       return ompd_rc_error;
     }
   }
@@ -449,8 +444,8 @@
   static ompd_rc_t (*my_task_handle_compare)(
       ompd_task_handle_t *, ompd_task_handle_t *, int *) = NULL;
   if (!my_task_handle_compare) {
-    my_task_handle_compare = dlsym(ompd_library, "ompd_task_handle_compare");
-    if (dlerror()) {
+    my_task_handle_compare = ompd_get_symbol("ompd_task_handle_compare");
+    if (ompd_get_dl_error()) {
       return ompd_rc_error;
     }
   }
@@ -464,8 +459,8 @@
       ompd_address_space_handle_t *, const char *const **) = NULL;
   if (!my_ompd_get_display_control_vars) {
     my_ompd_get_display_control_vars =
-        dlsym(ompd_library, "ompd_get_display_control_vars");
-    if (dlerror()) {
+        ompd_get_symbol("ompd_get_display_control_vars");
+    if (ompd_get_dl_error()) {
       return ompd_rc_error;
     }
   }
@@ -475,22 +470,19 @@
 /**
  * Loads the OMPD library (libompd.so). Returns an integer with the version if
  * the OMPD library could be loaded successfully. Error codes: -1: argument
- * could not be converted to string -2: error when calling dlopen -3: error when
- * fetching version of OMPD API else: see ompd return codes
+ * could not be converted to string -2: error when loading the library
+ * else: see ompd return codes
  */
 static PyObject *ompd_open(PyObject *self, PyObject *args) {
-  const char *name, *dlerr;
-  dlerror();
+  const char *name;
   if (!PyArg_ParseTuple(args, "s", &name)) {
     return Py_BuildValue("i", -1);
   }
-  ompd_library = dlopen(name, RTLD_LAZY);
-  if ((dlerr = dlerror())) {
+  if (ompd_load_library(name) != 0) {
+    /* Keep -2 so ompd.py can try the next ompd_dll_locations entry.
+     * The helper error stays in ompd_get_dl_error() for the caller. */
     return Py_BuildValue("i", -2);
   }
-  if (dlerror()) {
-    return Py_BuildValue("i", -3);
-  }
   ompd_word_t version;
   ompd_rc_t rc = ompd_get_api_version(&version);
   if (rc != ompd_rc_ok)
@@ -501,6 +493,16 @@
 }
 
 /**
+ * Last load/lookup error from the symbol-lookup helper, or None.
+ */
+static PyObject *call_ompd_get_dl_error(PyObject *self, PyObject *noargs) {
+  const char *err = ompd_get_dl_error();
+  if (!err)
+    Py_RETURN_NONE;
+  return Py_BuildValue("s", err);
+}
+
+/**
  * Have the debugger print a string.
  */
 ompd_rc_t _print(const char *str, int category) {
@@ -825,7 +827,12 @@
       NULL,   _read_string, _endianess, _endianess, _thread_context};
 
   ompd_rc_t (*my_ompd_init)(ompd_word_t version, ompd_callbacks_t *) =
-      dlsym(ompd_library, "ompd_initialize");
+      ompd_get_symbol("ompd_initialize");
+  if (!my_ompd_init) {
+    _printf("An error occurred when looking up ompd_initialize: %s",
+            ompd_get_dl_error() ? ompd_get_dl_error() : "unknown");
+    Py_RETURN_NONE;
+  }
   ompd_rc_t returnInit = my_ompd_init(201811, &table);
   if (returnInit != ompd_rc_ok) {
     _printf("An error occurred when calling ompd_initialize! Error code: %d",
@@ -834,7 +841,12 @@
   ompd_address_space_handle_t *addr_space = NULL;
   ompd_rc_t (*my_proc_init)(ompd_address_space_context_t *,
                             ompd_address_space_handle_t **) =
-      dlsym(ompd_library, "ompd_process_initialize");
+      ompd_get_symbol("ompd_process_initialize");
+  if (!my_proc_init) {
+    _printf("An error occurred when looking up ompd_process_initialize: %s",
+            ompd_get_dl_error() ? ompd_get_dl_error() : "unknown");
+    Py_RETURN_NONE;
+  }
   ompd_rc_t retProcInit = my_proc_init(&acontext, &addr_space);
   if (retProcInit != ompd_rc_ok) {
     _printf("An error occurred when calling ompd_process_initialize! Error "
@@ -968,7 +980,8 @@
 
   if (retVal != ompd_rc_ok) {
     _printf("An error occurred when calling ompd_get_task_parallel_handle! "
-            "Error code: %d", retVal);
+            "Error code: %d",
+            retVal);
     return Py_BuildValue("l", retVal);
   }
   return PyCapsule_New(taskParallelHandle, "ParallelHandle",
@@ -1482,7 +1495,9 @@
  */
 static PyMethodDef ompdModule_methods[] = {
     {"ompd_open", ompd_open, METH_VARARGS,
-     "Execute dlopen, return OMPD version."},
+     "Load libompd, return OMPD version."},
+    {"ompd_get_dl_error", call_ompd_get_dl_error, METH_NOARGS,
+     "Return the last OMPD library load/lookup error, or None."},
     {"call_ompd_initialize", call_ompd_initialize, METH_NOARGS,
      "Initializes OMPD environment and callbacks."},
     {"call_ompd_rel_thread_handle", call_ompd_rel_thread_handle, METH_VARARGS,
diff --git a/libompd/test/lit.cfg b/libompd/test/lit.cfg
index df881d0..eed6279 100644
--- a/libompd/test/lit.cfg
+++ b/libompd/test/lit.cfg
@@ -80,4 +80,21 @@
     config.ompt_plugin))
 
 config.substitutions.append(("FileCheck", config.test_filecheck))
+config.substitutions.append(
+    ("%ompd-lib", os.path.join(config.ompd_library_dir, "libompd.so"))
+)
+config.substitutions.append(
+    (
+        "%ompd-dl-src",
+        os.path.normpath(
+            os.path.join(config.ompd_test_src, "..", "gdb-plugin", "ompdDLService.c")
+        ),
+    )
+)
+config.substitutions.append(
+    (
+        "%ompd-dl-inc",
+        os.path.normpath(os.path.join(config.ompd_test_src, "..", "gdb-plugin")),
+    )
+)
 
diff --git a/libompd/test/ompd_dl_service.c b/libompd/test/ompd_dl_service.c
new file mode 100644
index 0000000..787cbdd
--- /dev/null
+++ b/libompd/test/ompd_dl_service.c
@@ -0,0 +1,55 @@
+// RUN: %test_c_compiler %s %ompd-dl-src -I%ompd-dl-inc -ldl -o %t
+// RUN: %t %ompd-lib | FileCheck %s
+// REQUIRES: linux
+
+#include "ompdDLService.h"
+
+#include <stdio.h>
+
+static const char *err_or_none(void) {
+  const char *err = ompd_get_dl_error();
+  return err ? err : "none";
+}
+
+int main(int argc, char **argv) {
+  if (argc != 2)
+    return 1;
+
+  if (ompd_load_library("") == 0)
+    return 2;
+  printf("empty: fail\n");
+  printf("empty-err: %s\n", err_or_none());
+
+  if (ompd_load_library("/no/such/libompd.so") == 0)
+    return 3;
+  printf("missing: fail\n");
+  printf("missing-err: %s\n", err_or_none());
+
+  if (ompd_load_library(argv[1]) != 0)
+    return 4;
+  printf("load: ok\n");
+  printf("load-err: %s\n", err_or_none());
+
+  if (!ompd_get_symbol("ompd_initialize"))
+    return 5;
+  printf("init-sym: ok\n");
+  printf("init-sym-err: %s\n", err_or_none());
+
+  if (ompd_get_symbol("no_such_ompd_symbol"))
+    return 6;
+  printf("missing-sym: fail\n");
+  printf("missing-sym-err: %s\n", err_or_none());
+
+  return 0;
+}
+
+// CHECK: empty: fail
+// CHECK-NEXT: empty-err: OMPD library path is empty
+// CHECK-NEXT: missing: fail
+// CHECK-NEXT: missing-err: {{.+}}
+// CHECK-NEXT: load: ok
+// CHECK-NEXT: load-err: none
+// CHECK-NEXT: init-sym: ok
+// CHECK-NEXT: init-sym-err: none
+// CHECK-NEXT: missing-sym: fail
+// CHECK-NEXT: missing-sym-err: {{.+}}