|  | /* Handle ROCm Code Objects for GDB, the GNU Debugger. | 
|  |  | 
|  | Copyright (C) 2019-2023 Free Software Foundation, Inc. | 
|  |  | 
|  | This file is part of GDB. | 
|  |  | 
|  | This program is free software; you can redistribute it and/or modify | 
|  | it under the terms of the GNU General Public License as published by | 
|  | the Free Software Foundation; either version 3 of the License, or | 
|  | (at your option) any later version. | 
|  |  | 
|  | This program is distributed in the hope that it will be useful, | 
|  | but WITHOUT ANY WARRANTY; without even the implied warranty of | 
|  | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
|  | GNU General Public License for more details. | 
|  |  | 
|  | You should have received a copy of the GNU General Public License | 
|  | along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ | 
|  |  | 
|  | #include "defs.h" | 
|  |  | 
|  | #include "amd-dbgapi-target.h" | 
|  | #include "amdgpu-tdep.h" | 
|  | #include "arch-utils.h" | 
|  | #include "elf-bfd.h" | 
|  | #include "elf/amdgpu.h" | 
|  | #include "gdbsupport/fileio.h" | 
|  | #include "inferior.h" | 
|  | #include "observable.h" | 
|  | #include "solib.h" | 
|  | #include "solib-svr4.h" | 
|  | #include "solist.h" | 
|  | #include "symfile.h" | 
|  |  | 
|  | /* ROCm-specific inferior data.  */ | 
|  |  | 
|  | struct solib_info | 
|  | { | 
|  | /* List of code objects loaded into the inferior.  */ | 
|  | so_list *solib_list; | 
|  | }; | 
|  |  | 
|  | /* Per-inferior data key.  */ | 
|  | static const registry<inferior>::key<solib_info> rocm_solib_data; | 
|  |  | 
|  | static target_so_ops rocm_solib_ops; | 
|  |  | 
|  | /* Free the solib linked list.  */ | 
|  |  | 
|  | static void | 
|  | rocm_free_solib_list (struct solib_info *info) | 
|  | { | 
|  | while (info->solib_list != nullptr) | 
|  | { | 
|  | struct so_list *next = info->solib_list->next; | 
|  |  | 
|  | free_so (info->solib_list); | 
|  | info->solib_list = next; | 
|  | } | 
|  |  | 
|  | info->solib_list = nullptr; | 
|  | } | 
|  |  | 
|  |  | 
|  | /* Fetch the solib_info data for INF.  */ | 
|  |  | 
|  | static struct solib_info * | 
|  | get_solib_info (inferior *inf) | 
|  | { | 
|  | solib_info *info = rocm_solib_data.get (inf); | 
|  |  | 
|  | if (info == nullptr) | 
|  | info = rocm_solib_data.emplace (inf); | 
|  |  | 
|  | return info; | 
|  | } | 
|  |  | 
|  | /* Relocate section addresses.  */ | 
|  |  | 
|  | static void | 
|  | rocm_solib_relocate_section_addresses (struct so_list *so, | 
|  | struct target_section *sec) | 
|  | { | 
|  | if (!is_amdgpu_arch (gdbarch_from_bfd (so->abfd))) | 
|  | { | 
|  | svr4_so_ops.relocate_section_addresses (so, sec); | 
|  | return; | 
|  | } | 
|  |  | 
|  | lm_info_svr4 *li = (lm_info_svr4 *) so->lm_info; | 
|  | sec->addr = sec->addr + li->l_addr; | 
|  | sec->endaddr = sec->endaddr + li->l_addr; | 
|  | } | 
|  |  | 
|  | static void rocm_update_solib_list (); | 
|  |  | 
|  | static void | 
|  | rocm_solib_handle_event () | 
|  | { | 
|  | /* Since we sit on top of svr4_so_ops, we might get called following an event | 
|  | concerning host libraries.  We must therefore forward the call.  If the | 
|  | event was for a ROCm code object, it will be a no-op.  On the other hand, | 
|  | if the event was for host libraries, rocm_update_solib_list will be | 
|  | essentially be a no-op (it will reload the same code object list as was | 
|  | previously loaded).  */ | 
|  | svr4_so_ops.handle_event (); | 
|  |  | 
|  | rocm_update_solib_list (); | 
|  | } | 
|  |  | 
|  | /* Make a deep copy of the solib linked list.  */ | 
|  |  | 
|  | static so_list * | 
|  | rocm_solib_copy_list (const so_list *src) | 
|  | { | 
|  | struct so_list *dst = nullptr; | 
|  | struct so_list **link = &dst; | 
|  |  | 
|  | while (src != nullptr) | 
|  | { | 
|  | struct so_list *newobj; | 
|  |  | 
|  | newobj = XNEW (struct so_list); | 
|  | memcpy (newobj, src, sizeof (struct so_list)); | 
|  |  | 
|  | lm_info_svr4 *src_li = (lm_info_svr4 *) src->lm_info; | 
|  | newobj->lm_info = new lm_info_svr4 (*src_li); | 
|  |  | 
|  | newobj->next = nullptr; | 
|  | *link = newobj; | 
|  | link = &newobj->next; | 
|  |  | 
|  | src = src->next; | 
|  | } | 
|  |  | 
|  | return dst; | 
|  | } | 
|  |  | 
|  | /* Build a list of `struct so_list' objects describing the shared | 
|  | objects currently loaded in the inferior.  */ | 
|  |  | 
|  | static struct so_list * | 
|  | rocm_solib_current_sos () | 
|  | { | 
|  | /* First, retrieve the host-side shared library list.  */ | 
|  | so_list *head = svr4_so_ops.current_sos (); | 
|  |  | 
|  | /* Then, the device-side shared library list.  */ | 
|  | so_list *list = get_solib_info (current_inferior ())->solib_list; | 
|  |  | 
|  | if (list == nullptr) | 
|  | return head; | 
|  |  | 
|  | list = rocm_solib_copy_list (list); | 
|  |  | 
|  | if (head == nullptr) | 
|  | return list; | 
|  |  | 
|  | /* Append our libraries to the end of the list.  */ | 
|  | so_list *tail; | 
|  | for (tail = head; tail->next; tail = tail->next) | 
|  | /* Nothing.  */; | 
|  | tail->next = list; | 
|  |  | 
|  | return head; | 
|  | } | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | /* Interface to interact with a ROCm code object stream.  */ | 
|  |  | 
|  | struct rocm_code_object_stream | 
|  | { | 
|  | DISABLE_COPY_AND_ASSIGN (rocm_code_object_stream); | 
|  |  | 
|  | /* Copy SIZE bytes from the underlying objfile storage starting at OFFSET | 
|  | into the user provided buffer BUF. | 
|  |  | 
|  | Return the number of bytes actually copied (might be inferior to SIZE if | 
|  | the end of the stream is reached).  */ | 
|  | virtual file_ptr read (void *buf, file_ptr size, file_ptr offset) = 0; | 
|  |  | 
|  | /* Retrieve file information in SB. | 
|  |  | 
|  | Return 0 on success.  On failure, set the appropriate bfd error number | 
|  | (using bfd_set_error) and return -1.  */ | 
|  | int stat (struct stat *sb); | 
|  |  | 
|  | virtual ~rocm_code_object_stream () = default; | 
|  |  | 
|  | protected: | 
|  | rocm_code_object_stream () = default; | 
|  |  | 
|  | /* Return the size of the object file, or -1 if the size cannot be | 
|  | determined. | 
|  |  | 
|  | This is a helper function for stat.  */ | 
|  | virtual LONGEST size () = 0; | 
|  | }; | 
|  |  | 
|  | int | 
|  | rocm_code_object_stream::stat (struct stat *sb) | 
|  | { | 
|  | const LONGEST size = this->size (); | 
|  | if (size == -1) | 
|  | return -1; | 
|  |  | 
|  | memset (sb, '\0', sizeof (struct stat)); | 
|  | sb->st_size = size; | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /* Interface to a ROCm object stream which is embedded in an ELF file | 
|  | accessible to the debugger.  */ | 
|  |  | 
|  | struct rocm_code_object_stream_file final : rocm_code_object_stream | 
|  | { | 
|  | DISABLE_COPY_AND_ASSIGN (rocm_code_object_stream_file); | 
|  |  | 
|  | rocm_code_object_stream_file (int fd, ULONGEST offset, ULONGEST size); | 
|  |  | 
|  | file_ptr read (void *buf, file_ptr size, file_ptr offset) override; | 
|  |  | 
|  | LONGEST size () override; | 
|  |  | 
|  | ~rocm_code_object_stream_file () override; | 
|  |  | 
|  | protected: | 
|  |  | 
|  | /* The target file descriptor for this stream.  */ | 
|  | int m_fd; | 
|  |  | 
|  | /* The offset of the ELF file image in the target file.  */ | 
|  | ULONGEST m_offset; | 
|  |  | 
|  | /* The size of the ELF file image.  The value 0 means that it was | 
|  | unspecified in the URI descriptor.  */ | 
|  | ULONGEST m_size; | 
|  | }; | 
|  |  | 
|  | rocm_code_object_stream_file::rocm_code_object_stream_file | 
|  | (int fd, ULONGEST offset, ULONGEST size) | 
|  | : m_fd (fd), m_offset (offset), m_size (size) | 
|  | { | 
|  | } | 
|  |  | 
|  | file_ptr | 
|  | rocm_code_object_stream_file::read (void *buf, file_ptr size, | 
|  | file_ptr offset) | 
|  | { | 
|  | fileio_error target_errno; | 
|  | file_ptr nbytes = 0; | 
|  | while (size > 0) | 
|  | { | 
|  | QUIT; | 
|  |  | 
|  | file_ptr bytes_read | 
|  | = target_fileio_pread (m_fd, static_cast<gdb_byte *> (buf) + nbytes, | 
|  | size, m_offset + offset + nbytes, | 
|  | &target_errno); | 
|  |  | 
|  | if (bytes_read == 0) | 
|  | break; | 
|  |  | 
|  | if (bytes_read < 0) | 
|  | { | 
|  | errno = fileio_error_to_host (target_errno); | 
|  | bfd_set_error (bfd_error_system_call); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | nbytes += bytes_read; | 
|  | size -= bytes_read; | 
|  | } | 
|  |  | 
|  | return nbytes; | 
|  | } | 
|  |  | 
|  | LONGEST | 
|  | rocm_code_object_stream_file::size () | 
|  | { | 
|  | if (m_size == 0) | 
|  | { | 
|  | fileio_error target_errno; | 
|  | struct stat stat; | 
|  | if (target_fileio_fstat (m_fd, &stat, &target_errno) < 0) | 
|  | { | 
|  | errno = fileio_error_to_host (target_errno); | 
|  | bfd_set_error (bfd_error_system_call); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | /* Check that the offset is valid.  */ | 
|  | if (m_offset >= stat.st_size) | 
|  | { | 
|  | bfd_set_error (bfd_error_bad_value); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | m_size = stat.st_size - m_offset; | 
|  | } | 
|  |  | 
|  | return m_size; | 
|  | } | 
|  |  | 
|  | rocm_code_object_stream_file::~rocm_code_object_stream_file () | 
|  | { | 
|  | fileio_error target_errno; | 
|  | target_fileio_close (m_fd, &target_errno); | 
|  | } | 
|  |  | 
|  | /* Interface to a code object which lives in the inferior's memory.  */ | 
|  |  | 
|  | struct rocm_code_object_stream_memory final : public rocm_code_object_stream | 
|  | { | 
|  | DISABLE_COPY_AND_ASSIGN (rocm_code_object_stream_memory); | 
|  |  | 
|  | rocm_code_object_stream_memory (gdb::byte_vector buffer); | 
|  |  | 
|  | file_ptr read (void *buf, file_ptr size, file_ptr offset) override; | 
|  |  | 
|  | protected: | 
|  |  | 
|  | /* Snapshot of the original ELF image taken during load.  This is done to | 
|  | support the situation where an inferior uses an in-memory image, and | 
|  | releases or re-uses this memory before GDB is done using it.  */ | 
|  | gdb::byte_vector m_objfile_image; | 
|  |  | 
|  | LONGEST size () override | 
|  | { | 
|  | return m_objfile_image.size (); | 
|  | } | 
|  | }; | 
|  |  | 
|  | rocm_code_object_stream_memory::rocm_code_object_stream_memory | 
|  | (gdb::byte_vector buffer) | 
|  | : m_objfile_image (std::move (buffer)) | 
|  | { | 
|  | } | 
|  |  | 
|  | file_ptr | 
|  | rocm_code_object_stream_memory::read (void *buf, file_ptr size, | 
|  | file_ptr offset) | 
|  | { | 
|  | if (size > m_objfile_image.size () - offset) | 
|  | size = m_objfile_image.size () - offset; | 
|  |  | 
|  | memcpy (buf, m_objfile_image.data () + offset, size); | 
|  | return size; | 
|  | } | 
|  |  | 
|  | } /* anonymous namespace */ | 
|  |  | 
|  | static void * | 
|  | rocm_bfd_iovec_open (bfd *abfd, void *inferior_void) | 
|  | { | 
|  | gdb::string_view uri (bfd_get_filename (abfd)); | 
|  | gdb::string_view protocol_delim = "://"; | 
|  | size_t protocol_end = uri.find (protocol_delim); | 
|  | std::string protocol = gdb::to_string (uri.substr (0, protocol_end)); | 
|  | protocol_end += protocol_delim.length (); | 
|  |  | 
|  | std::transform (protocol.begin (), protocol.end (), protocol.begin (), | 
|  | [] (unsigned char c) { return std::tolower (c); }); | 
|  |  | 
|  | gdb::string_view path; | 
|  | size_t path_end = uri.find_first_of ("#?", protocol_end); | 
|  | if (path_end != std::string::npos) | 
|  | path = uri.substr (protocol_end, path_end++ - protocol_end); | 
|  | else | 
|  | path = uri.substr (protocol_end); | 
|  |  | 
|  | /* %-decode the string.  */ | 
|  | std::string decoded_path; | 
|  | decoded_path.reserve (path.length ()); | 
|  | for (size_t i = 0; i < path.length (); ++i) | 
|  | if (path[i] == '%' | 
|  | && i < path.length () - 2 | 
|  | && std::isxdigit (path[i + 1]) | 
|  | && std::isxdigit (path[i + 2])) | 
|  | { | 
|  | gdb::string_view hex_digits = path.substr (i + 1, 2); | 
|  | decoded_path += std::stoi (gdb::to_string (hex_digits), 0, 16); | 
|  | i += 2; | 
|  | } | 
|  | else | 
|  | decoded_path += path[i]; | 
|  |  | 
|  | /* Tokenize the query/fragment.  */ | 
|  | std::vector<gdb::string_view> tokens; | 
|  | size_t pos, last = path_end; | 
|  | while ((pos = uri.find ('&', last)) != std::string::npos) | 
|  | { | 
|  | tokens.emplace_back (uri.substr (last, pos - last)); | 
|  | last = pos + 1; | 
|  | } | 
|  |  | 
|  | if (last != std::string::npos) | 
|  | tokens.emplace_back (uri.substr (last)); | 
|  |  | 
|  | /* Create a tag-value map from the tokenized query/fragment.  */ | 
|  | std::unordered_map<gdb::string_view, gdb::string_view, | 
|  | gdb::string_view_hash> params; | 
|  | for (gdb::string_view token : tokens) | 
|  | { | 
|  | size_t delim = token.find ('='); | 
|  | if (delim != std::string::npos) | 
|  | { | 
|  | gdb::string_view tag = token.substr (0, delim); | 
|  | gdb::string_view val = token.substr (delim + 1); | 
|  | params.emplace (tag, val); | 
|  | } | 
|  | } | 
|  |  | 
|  | try | 
|  | { | 
|  | ULONGEST offset = 0; | 
|  | ULONGEST size = 0; | 
|  | inferior *inferior = static_cast<struct inferior *> (inferior_void); | 
|  |  | 
|  | auto try_strtoulst = [] (gdb::string_view v) | 
|  | { | 
|  | errno = 0; | 
|  | ULONGEST value = strtoulst (v.data (), nullptr, 0); | 
|  | if (errno != 0) | 
|  | { | 
|  | /* The actual message doesn't matter, the exception is caught | 
|  | below, transformed in a BFD error, and the message is lost.  */ | 
|  | error (_("Failed to parse integer.")); | 
|  | } | 
|  |  | 
|  | return value; | 
|  | }; | 
|  |  | 
|  | auto offset_it = params.find ("offset"); | 
|  | if (offset_it != params.end ()) | 
|  | offset = try_strtoulst (offset_it->second); | 
|  |  | 
|  | auto size_it = params.find ("size"); | 
|  | if (size_it != params.end ()) | 
|  | { | 
|  | size = try_strtoulst (size_it->second); | 
|  | if (size == 0) | 
|  | error (_("Invalid size value")); | 
|  | } | 
|  |  | 
|  | if (protocol == "file") | 
|  | { | 
|  | fileio_error target_errno; | 
|  | int fd | 
|  | = target_fileio_open (static_cast<struct inferior *> (inferior), | 
|  | decoded_path.c_str (), FILEIO_O_RDONLY, | 
|  | false, 0, &target_errno); | 
|  |  | 
|  | if (fd == -1) | 
|  | { | 
|  | errno = fileio_error_to_host (target_errno); | 
|  | bfd_set_error (bfd_error_system_call); | 
|  | return nullptr; | 
|  | } | 
|  |  | 
|  | return new rocm_code_object_stream_file (fd, offset, size); | 
|  | } | 
|  |  | 
|  | if (protocol == "memory") | 
|  | { | 
|  | ULONGEST pid = try_strtoulst (path); | 
|  | if (pid != inferior->pid) | 
|  | { | 
|  | warning (_("`%s': code object is from another inferior"), | 
|  | gdb::to_string (uri).c_str ()); | 
|  | bfd_set_error (bfd_error_bad_value); | 
|  | return nullptr; | 
|  | } | 
|  |  | 
|  | gdb::byte_vector buffer (size); | 
|  | if (target_read_memory (offset, buffer.data (), size) != 0) | 
|  | { | 
|  | warning (_("Failed to copy the code object from the inferior")); | 
|  | bfd_set_error (bfd_error_bad_value); | 
|  | return nullptr; | 
|  | } | 
|  |  | 
|  | return new rocm_code_object_stream_memory (std::move (buffer)); | 
|  | } | 
|  |  | 
|  | warning (_("`%s': protocol not supported: %s"), | 
|  | gdb::to_string (uri).c_str (), protocol.c_str ()); | 
|  | bfd_set_error (bfd_error_bad_value); | 
|  | return nullptr; | 
|  | } | 
|  | catch (const gdb_exception_quit &ex) | 
|  | { | 
|  | set_quit_flag (); | 
|  | bfd_set_error (bfd_error_bad_value); | 
|  | return nullptr; | 
|  | } | 
|  | catch (const gdb_exception &ex) | 
|  | { | 
|  | bfd_set_error (bfd_error_bad_value); | 
|  | return nullptr; | 
|  | } | 
|  | } | 
|  |  | 
|  | static int | 
|  | rocm_bfd_iovec_close (bfd *nbfd, void *data) | 
|  | { | 
|  | delete static_cast<rocm_code_object_stream *> (data); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static file_ptr | 
|  | rocm_bfd_iovec_pread (bfd *abfd, void *data, void *buf, file_ptr size, | 
|  | file_ptr offset) | 
|  | { | 
|  | return static_cast<rocm_code_object_stream *> (data)->read (buf, size, | 
|  | offset); | 
|  | } | 
|  |  | 
|  | static int | 
|  | rocm_bfd_iovec_stat (bfd *abfd, void *data, struct stat *sb) | 
|  | { | 
|  | return static_cast<rocm_code_object_stream *> (data)->stat (sb); | 
|  | } | 
|  |  | 
|  | static gdb_bfd_ref_ptr | 
|  | rocm_solib_bfd_open (const char *pathname) | 
|  | { | 
|  | /* Handle regular files with SVR4 open.  */ | 
|  | if (strstr (pathname, "://") == nullptr) | 
|  | return svr4_so_ops.bfd_open (pathname); | 
|  |  | 
|  | gdb_bfd_ref_ptr abfd | 
|  | = gdb_bfd_openr_iovec (pathname, "elf64-amdgcn", rocm_bfd_iovec_open, | 
|  | current_inferior (), rocm_bfd_iovec_pread, | 
|  | rocm_bfd_iovec_close, rocm_bfd_iovec_stat); | 
|  |  | 
|  | if (abfd == nullptr) | 
|  | error (_("Could not open `%s' as an executable file: %s"), pathname, | 
|  | bfd_errmsg (bfd_get_error ())); | 
|  |  | 
|  | /* Check bfd format.  */ | 
|  | if (!bfd_check_format (abfd.get (), bfd_object)) | 
|  | error (_("`%s': not in executable format: %s"), | 
|  | bfd_get_filename (abfd.get ()), bfd_errmsg (bfd_get_error ())); | 
|  |  | 
|  | unsigned char osabi = elf_elfheader (abfd)->e_ident[EI_OSABI]; | 
|  | unsigned char osabiversion = elf_elfheader (abfd)->e_ident[EI_ABIVERSION]; | 
|  |  | 
|  | /* Check that the code object is using the HSA OS ABI.  */ | 
|  | if (osabi != ELFOSABI_AMDGPU_HSA) | 
|  | error (_("`%s': ELF file OS ABI is not supported (%d)."), | 
|  | bfd_get_filename (abfd.get ()), osabi); | 
|  |  | 
|  | /* We support HSA code objects V3 and greater.  */ | 
|  | if (osabiversion < ELFABIVERSION_AMDGPU_HSA_V3) | 
|  | error (_("`%s': ELF file HSA OS ABI version is not supported (%d)."), | 
|  | bfd_get_filename (abfd.get ()), osabiversion); | 
|  |  | 
|  | return abfd; | 
|  | } | 
|  |  | 
|  | static void | 
|  | rocm_solib_create_inferior_hook (int from_tty) | 
|  | { | 
|  | rocm_free_solib_list (get_solib_info (current_inferior ())); | 
|  |  | 
|  | svr4_so_ops.solib_create_inferior_hook (from_tty); | 
|  | } | 
|  |  | 
|  | static void | 
|  | rocm_update_solib_list () | 
|  | { | 
|  | inferior *inf = current_inferior (); | 
|  |  | 
|  | amd_dbgapi_process_id_t process_id = get_amd_dbgapi_process_id (inf); | 
|  | if (process_id.handle == AMD_DBGAPI_PROCESS_NONE.handle) | 
|  | return; | 
|  |  | 
|  | solib_info *info = get_solib_info (inf); | 
|  |  | 
|  | rocm_free_solib_list (info); | 
|  | struct so_list **link = &info->solib_list; | 
|  |  | 
|  | amd_dbgapi_code_object_id_t *code_object_list; | 
|  | size_t count; | 
|  |  | 
|  | amd_dbgapi_status_t status | 
|  | = amd_dbgapi_process_code_object_list (process_id, &count, | 
|  | &code_object_list, nullptr); | 
|  | if (status != AMD_DBGAPI_STATUS_SUCCESS) | 
|  | { | 
|  | warning (_("amd_dbgapi_process_code_object_list failed (%s)"), | 
|  | get_status_string (status)); | 
|  | return; | 
|  | } | 
|  |  | 
|  | for (size_t i = 0; i < count; ++i) | 
|  | { | 
|  | CORE_ADDR l_addr; | 
|  | char *uri_bytes; | 
|  |  | 
|  | status = amd_dbgapi_code_object_get_info | 
|  | (code_object_list[i], AMD_DBGAPI_CODE_OBJECT_INFO_LOAD_ADDRESS, | 
|  | sizeof (l_addr), &l_addr); | 
|  | if (status != AMD_DBGAPI_STATUS_SUCCESS) | 
|  | continue; | 
|  |  | 
|  | status = amd_dbgapi_code_object_get_info | 
|  | (code_object_list[i], AMD_DBGAPI_CODE_OBJECT_INFO_URI_NAME, | 
|  | sizeof (uri_bytes), &uri_bytes); | 
|  | if (status != AMD_DBGAPI_STATUS_SUCCESS) | 
|  | continue; | 
|  |  | 
|  | struct so_list *so = XCNEW (struct so_list); | 
|  | lm_info_svr4 *li = new lm_info_svr4; | 
|  | li->l_addr = l_addr; | 
|  | so->lm_info = li; | 
|  |  | 
|  | strncpy (so->so_name, uri_bytes, sizeof (so->so_name)); | 
|  | so->so_name[sizeof (so->so_name) - 1] = '\0'; | 
|  | xfree (uri_bytes); | 
|  |  | 
|  | /* Make so_original_name unique so that code objects with the same URI | 
|  | but different load addresses are seen by gdb core as different shared | 
|  | objects.  */ | 
|  | xsnprintf (so->so_original_name, sizeof (so->so_original_name), | 
|  | "code_object_%ld", code_object_list[i].handle); | 
|  |  | 
|  | so->next = nullptr; | 
|  | *link = so; | 
|  | link = &so->next; | 
|  | } | 
|  |  | 
|  | xfree (code_object_list); | 
|  |  | 
|  | if (rocm_solib_ops.current_sos == NULL) | 
|  | { | 
|  | /* Override what we need to.  */ | 
|  | rocm_solib_ops = svr4_so_ops; | 
|  | rocm_solib_ops.current_sos = rocm_solib_current_sos; | 
|  | rocm_solib_ops.solib_create_inferior_hook | 
|  | = rocm_solib_create_inferior_hook; | 
|  | rocm_solib_ops.bfd_open = rocm_solib_bfd_open; | 
|  | rocm_solib_ops.relocate_section_addresses | 
|  | = rocm_solib_relocate_section_addresses; | 
|  | rocm_solib_ops.handle_event = rocm_solib_handle_event; | 
|  |  | 
|  | /* Engage the ROCm so_ops.  */ | 
|  | set_gdbarch_so_ops (current_inferior ()->gdbarch, &rocm_solib_ops); | 
|  | } | 
|  | } | 
|  |  | 
|  | static void | 
|  | rocm_solib_target_inferior_created (inferior *inf) | 
|  | { | 
|  | rocm_free_solib_list (get_solib_info (inf)); | 
|  | rocm_update_solib_list (); | 
|  |  | 
|  | /* Force GDB to reload the solibs.  */ | 
|  | current_inferior ()->pspace->clear_solib_cache (); | 
|  | solib_add (nullptr, 0, auto_solib_add); | 
|  | } | 
|  |  | 
|  | /* -Wmissing-prototypes */ | 
|  | extern initialize_file_ftype _initialize_rocm_solib; | 
|  |  | 
|  | void | 
|  | _initialize_rocm_solib () | 
|  | { | 
|  | /* The dependency on the amd-dbgapi exists because solib-rocm's | 
|  | inferior_created observer needs amd-dbgapi to have attached the process, | 
|  | which happens in amd_dbgapi_target's inferior_created observer.  */ | 
|  | gdb::observers::inferior_created.attach | 
|  | (rocm_solib_target_inferior_created, | 
|  | "solib-rocm", | 
|  | { &get_amd_dbgapi_target_inferior_created_observer_token () }); | 
|  | } |