|  | /* Target used to communicate with the AMD Debugger API. | 
|  |  | 
|  | Copyright (C) 2019-2024 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 "async-event.h" | 
|  | #include "cli/cli-cmds.h" | 
|  | #include "cli/cli-decode.h" | 
|  | #include "cli/cli-style.h" | 
|  | #include "inf-loop.h" | 
|  | #include "inferior.h" | 
|  | #include "objfiles.h" | 
|  | #include "observable.h" | 
|  | #include "registry.h" | 
|  | #include "solib.h" | 
|  | #include "target.h" | 
|  |  | 
|  | /* When true, print debug messages relating to the amd-dbgapi target.  */ | 
|  |  | 
|  | static bool debug_amd_dbgapi = false; | 
|  |  | 
|  | /* Make a copy of S styled in green.  */ | 
|  |  | 
|  | static std::string | 
|  | make_green (const char *s) | 
|  | { | 
|  | cli_style_option style (nullptr, ui_file_style::GREEN); | 
|  | string_file sf (true); | 
|  | gdb_printf (&sf, "%ps", styled_string (style.style(), s)); | 
|  | return sf.release (); | 
|  | } | 
|  |  | 
|  | /* Debug module names.  "amd-dbgapi" is for the target debug messages (this | 
|  | file), whereas "amd-dbgapi-lib" is for logging messages output by the | 
|  | amd-dbgapi library.  */ | 
|  |  | 
|  | static const char *amd_dbgapi_debug_module_unstyled = "amd-dbgapi"; | 
|  | static const char *amd_dbgapi_lib_debug_module_unstyled | 
|  | = "amd-dbgapi-lib"; | 
|  |  | 
|  | /* Styled variants of the above.  */ | 
|  |  | 
|  | static const std::string amd_dbgapi_debug_module_styled | 
|  | = make_green (amd_dbgapi_debug_module_unstyled); | 
|  | static const std::string amd_dbgapi_lib_debug_module_styled | 
|  | = make_green (amd_dbgapi_lib_debug_module_unstyled); | 
|  |  | 
|  | /* Return the styled or unstyled variant of the amd-dbgapi module name, | 
|  | depending on whether gdb_stdlog can emit colors.  */ | 
|  |  | 
|  | static const char * | 
|  | amd_dbgapi_debug_module () | 
|  | { | 
|  | if (gdb_stdlog->can_emit_style_escape ()) | 
|  | return amd_dbgapi_debug_module_styled.c_str (); | 
|  | else | 
|  | return amd_dbgapi_debug_module_unstyled; | 
|  | } | 
|  |  | 
|  | /* Same as the above, but for the amd-dbgapi-lib module name.  */ | 
|  |  | 
|  | static const char * | 
|  | amd_dbgapi_lib_debug_module () | 
|  | { | 
|  | if (gdb_stdlog->can_emit_style_escape ()) | 
|  | return amd_dbgapi_lib_debug_module_styled.c_str (); | 
|  | else | 
|  | return amd_dbgapi_lib_debug_module_unstyled; | 
|  | } | 
|  |  | 
|  | /* Print an amd-dbgapi debug statement.  */ | 
|  |  | 
|  | #define amd_dbgapi_debug_printf(fmt, ...) \ | 
|  | debug_prefixed_printf_cond (debug_amd_dbgapi, \ | 
|  | amd_dbgapi_debug_module (), \ | 
|  | fmt, ##__VA_ARGS__) | 
|  |  | 
|  | /* Print amd-dbgapi start/end debug statements.  */ | 
|  |  | 
|  | #define AMD_DBGAPI_SCOPED_DEBUG_START_END(fmt, ...) \ | 
|  | scoped_debug_start_end (debug_amd_dbgapi, amd_dbgapi_debug_module (), \ | 
|  | fmt, ##__VA_ARGS__) | 
|  |  | 
|  | /* inferior_created observer token.  */ | 
|  |  | 
|  | static gdb::observers::token amd_dbgapi_target_inferior_created_observer_token; | 
|  |  | 
|  | const gdb::observers::token & | 
|  | get_amd_dbgapi_target_inferior_created_observer_token () | 
|  | { | 
|  | return amd_dbgapi_target_inferior_created_observer_token; | 
|  | } | 
|  |  | 
|  | /* A type holding coordinates, etc. info for a given wave.  */ | 
|  |  | 
|  | struct wave_coordinates | 
|  | { | 
|  | /* The wave.  Set by the ctor.  */ | 
|  | amd_dbgapi_wave_id_t wave_id; | 
|  |  | 
|  | /* All these fields are initialized here to a value that is printed | 
|  | as "?".  */ | 
|  | amd_dbgapi_dispatch_id_t dispatch_id = AMD_DBGAPI_DISPATCH_NONE; | 
|  | amd_dbgapi_queue_id_t queue_id = AMD_DBGAPI_QUEUE_NONE; | 
|  | amd_dbgapi_agent_id_t agent_id = AMD_DBGAPI_AGENT_NONE; | 
|  | uint32_t group_ids[3] {UINT32_MAX, UINT32_MAX, UINT32_MAX}; | 
|  | uint32_t wave_in_group = UINT32_MAX; | 
|  |  | 
|  | explicit wave_coordinates (amd_dbgapi_wave_id_t wave_id) | 
|  | : wave_id (wave_id) | 
|  | {} | 
|  |  | 
|  | /* Return the target ID string for the wave this wave_coordinates is | 
|  | for.  */ | 
|  | std::string to_string () const; | 
|  |  | 
|  | /* Pull out coordinates info from the amd-dbgapi library.  */ | 
|  | void fetch (); | 
|  | }; | 
|  |  | 
|  | /* A type holding info about a given wave.  */ | 
|  |  | 
|  | struct wave_info | 
|  | { | 
|  | /* We cache the coordinates info because we need it after a wave | 
|  | exits.  The wave's ID is here.  */ | 
|  | wave_coordinates coords; | 
|  |  | 
|  | /* The last resume_mode passed to amd_dbgapi_wave_resume for this | 
|  | wave.  We track this because we are guaranteed to see a | 
|  | WAVE_COMMAND_TERMINATED event if a stepping wave terminates, and | 
|  | we need to know to not delete such a wave until we process that | 
|  | event.  */ | 
|  | amd_dbgapi_resume_mode_t last_resume_mode = AMD_DBGAPI_RESUME_MODE_NORMAL; | 
|  |  | 
|  | /* Whether we've called amd_dbgapi_wave_stop for this wave and are | 
|  | waiting for its stop event.  Similarly, we track this because | 
|  | we're guaranteed to get a WAVE_COMMAND_TERMINATED event if the | 
|  | wave terminates while being stopped.  */ | 
|  | bool stopping = false; | 
|  |  | 
|  | explicit wave_info (amd_dbgapi_wave_id_t wave_id) | 
|  | : coords (wave_id) | 
|  | { | 
|  | coords.fetch (); | 
|  | } | 
|  | }; | 
|  |  | 
|  | /* Big enough to hold the size of the largest register in bytes.  */ | 
|  | #define AMDGPU_MAX_REGISTER_SIZE 256 | 
|  |  | 
|  | /* amd-dbgapi-specific inferior data.  */ | 
|  |  | 
|  | struct amd_dbgapi_inferior_info | 
|  | { | 
|  | explicit amd_dbgapi_inferior_info (inferior *inf, | 
|  | bool precise_memory_requested = false) | 
|  | : inf (inf) | 
|  | { | 
|  | precise_memory.requested = precise_memory_requested; | 
|  | } | 
|  |  | 
|  | /* Backlink to inferior.  */ | 
|  | inferior *inf; | 
|  |  | 
|  | /* The amd_dbgapi_process_id for this inferior.  */ | 
|  | amd_dbgapi_process_id_t process_id = AMD_DBGAPI_PROCESS_NONE; | 
|  |  | 
|  | /* The amd_dbgapi_notifier_t for this inferior.  */ | 
|  | amd_dbgapi_notifier_t notifier = -1; | 
|  |  | 
|  | /* The status of the inferior's runtime support.  */ | 
|  | amd_dbgapi_runtime_state_t runtime_state = AMD_DBGAPI_RUNTIME_STATE_UNLOADED; | 
|  |  | 
|  | /* This value mirrors the current "forward progress needed" value for this | 
|  | process in amd-dbgapi.  It is used to avoid unnecessary calls to | 
|  | amd_dbgapi_process_set_progress, to reduce the noise in the logs. | 
|  |  | 
|  | Initialized to true, since that's the default in amd-dbgapi too.  */ | 
|  | bool forward_progress_required = true; | 
|  |  | 
|  | struct | 
|  | { | 
|  | /* Whether precise memory reporting is requested.  */ | 
|  | bool requested; | 
|  |  | 
|  | /* Whether precise memory was requested and successfully enabled by | 
|  | dbgapi (it may not be available for the current hardware, for | 
|  | instance).  */ | 
|  | bool enabled = false; | 
|  | } precise_memory; | 
|  |  | 
|  | std::unordered_map<decltype (amd_dbgapi_breakpoint_id_t::handle), | 
|  | struct breakpoint *> | 
|  | breakpoint_map; | 
|  |  | 
|  | /* List of pending events the amd-dbgapi target retrieved from the dbgapi.  */ | 
|  | std::list<std::pair<ptid_t, target_waitstatus>> wave_events; | 
|  |  | 
|  | /* Map of wave ID to wave_info.  We cache wave_info objects because | 
|  | we need to access the info after the wave is gone, in the thread | 
|  | exit nofication.  E.g.: | 
|  | [AMDGPU Wave 1:4:1:1 (0,0,0)/0 exited] | 
|  |  | 
|  | wave_info objects are added when we first see the wave, and | 
|  | removed from a thread_deleted observer.  */ | 
|  | std::unordered_map<decltype (amd_dbgapi_wave_id_t::handle), wave_info> | 
|  | wave_info_map; | 
|  | }; | 
|  |  | 
|  | static amd_dbgapi_event_id_t process_event_queue | 
|  | (amd_dbgapi_process_id_t process_id, | 
|  | amd_dbgapi_event_kind_t until_event_kind = AMD_DBGAPI_EVENT_KIND_NONE); | 
|  |  | 
|  | static const target_info amd_dbgapi_target_info = { | 
|  | "amd-dbgapi", | 
|  | N_("AMD Debugger API"), | 
|  | N_("GPU debugging using the AMD Debugger API") | 
|  | }; | 
|  |  | 
|  | static amd_dbgapi_log_level_t get_debug_amd_dbgapi_lib_log_level (); | 
|  |  | 
|  | struct amd_dbgapi_target final : public target_ops | 
|  | { | 
|  | const target_info & | 
|  | info () const override | 
|  | { | 
|  | return amd_dbgapi_target_info; | 
|  | } | 
|  | strata | 
|  | stratum () const override | 
|  | { | 
|  | return arch_stratum; | 
|  | } | 
|  |  | 
|  | void close () override; | 
|  | void mourn_inferior () override; | 
|  | void detach (inferior *inf, int from_tty) override; | 
|  |  | 
|  | void async (bool enable) override; | 
|  |  | 
|  | bool has_pending_events () override; | 
|  | ptid_t wait (ptid_t, struct target_waitstatus *, target_wait_flags) override; | 
|  | void resume (ptid_t, int, enum gdb_signal) override; | 
|  | void commit_resumed () override; | 
|  | void stop (ptid_t ptid) override; | 
|  |  | 
|  | void fetch_registers (struct regcache *, int) override; | 
|  | void store_registers (struct regcache *, int) override; | 
|  |  | 
|  | void update_thread_list () override; | 
|  |  | 
|  | struct gdbarch *thread_architecture (ptid_t) override; | 
|  |  | 
|  | void thread_events (int enable) override; | 
|  |  | 
|  | std::string pid_to_str (ptid_t ptid) override; | 
|  |  | 
|  | const char *thread_name (thread_info *tp) override; | 
|  |  | 
|  | const char *extra_thread_info (thread_info *tp) override; | 
|  |  | 
|  | bool thread_alive (ptid_t ptid) override; | 
|  |  | 
|  | enum target_xfer_status xfer_partial (enum target_object object, | 
|  | const char *annex, gdb_byte *readbuf, | 
|  | const gdb_byte *writebuf, | 
|  | ULONGEST offset, ULONGEST len, | 
|  | ULONGEST *xfered_len) override; | 
|  |  | 
|  | bool stopped_by_watchpoint () override; | 
|  |  | 
|  | bool stopped_by_sw_breakpoint () override; | 
|  | bool stopped_by_hw_breakpoint () override; | 
|  |  | 
|  | private: | 
|  | /* True if we must report thread events.  */ | 
|  | bool m_report_thread_events = false; | 
|  |  | 
|  | /* Cache for the last value returned by thread_architecture.  */ | 
|  | gdbarch *m_cached_arch = nullptr; | 
|  | ptid_t::tid_type m_cached_arch_tid = 0; | 
|  | }; | 
|  |  | 
|  | static struct amd_dbgapi_target the_amd_dbgapi_target; | 
|  |  | 
|  | /* Per-inferior data key.  */ | 
|  |  | 
|  | static const registry<inferior>::key<amd_dbgapi_inferior_info> | 
|  | amd_dbgapi_inferior_data; | 
|  |  | 
|  | /* Fetch the amd_dbgapi_inferior_info data for the given inferior.  */ | 
|  |  | 
|  | static struct amd_dbgapi_inferior_info * | 
|  | get_amd_dbgapi_inferior_info (struct inferior *inferior) | 
|  | { | 
|  | amd_dbgapi_inferior_info *info = amd_dbgapi_inferior_data.get (inferior); | 
|  |  | 
|  | if (info == nullptr) | 
|  | info = amd_dbgapi_inferior_data.emplace (inferior, inferior); | 
|  |  | 
|  | return info; | 
|  | } | 
|  |  | 
|  | /* The async event handler registered with the event loop, indicating that we | 
|  | might have events to report to the core and that we'd like our wait method | 
|  | to be called. | 
|  |  | 
|  | This is nullptr when async is disabled and non-nullptr when async is | 
|  | enabled. | 
|  |  | 
|  | It is marked when a notifier fd tells us there's an event available.  The | 
|  | callback triggers handle_inferior_event in order to pull the event from | 
|  | amd-dbgapi and handle it.  */ | 
|  |  | 
|  | static async_event_handler *amd_dbgapi_async_event_handler = nullptr; | 
|  |  | 
|  | std::string | 
|  | wave_coordinates::to_string () const | 
|  | { | 
|  | std::string str = "AMDGPU Wave"; | 
|  |  | 
|  | str += (agent_id != AMD_DBGAPI_AGENT_NONE | 
|  | ? string_printf (" %ld", agent_id.handle) | 
|  | : " ?"); | 
|  |  | 
|  | str += (queue_id != AMD_DBGAPI_QUEUE_NONE | 
|  | ? string_printf (":%ld", queue_id.handle) | 
|  | : ":?"); | 
|  |  | 
|  | str += (dispatch_id != AMD_DBGAPI_DISPATCH_NONE | 
|  | ? string_printf (":%ld", dispatch_id.handle) | 
|  | : ":?"); | 
|  |  | 
|  | str += string_printf (":%ld", wave_id.handle); | 
|  |  | 
|  | str += (group_ids[0] != UINT32_MAX | 
|  | ? string_printf (" (%d,%d,%d)", group_ids[0], group_ids[1], | 
|  | group_ids[2]) | 
|  | : " (?,?,?)"); | 
|  |  | 
|  | str += (wave_in_group != UINT32_MAX | 
|  | ? string_printf ("/%d", wave_in_group) | 
|  | : "/?"); | 
|  |  | 
|  | return str; | 
|  | } | 
|  |  | 
|  | /* Read in wave_info for WAVE_ID.  */ | 
|  |  | 
|  | void | 
|  | wave_coordinates::fetch () | 
|  | { | 
|  | /* Any field that fails to be read is left with its in-class | 
|  | initialized value, which is printed as "?".  */ | 
|  |  | 
|  | amd_dbgapi_wave_get_info (wave_id, AMD_DBGAPI_WAVE_INFO_AGENT, | 
|  | sizeof (agent_id), &agent_id); | 
|  | amd_dbgapi_wave_get_info (wave_id, AMD_DBGAPI_WAVE_INFO_QUEUE, | 
|  | sizeof (queue_id), &queue_id); | 
|  | amd_dbgapi_wave_get_info (wave_id, AMD_DBGAPI_WAVE_INFO_DISPATCH, | 
|  | sizeof (dispatch_id), &dispatch_id); | 
|  |  | 
|  | amd_dbgapi_wave_get_info (wave_id, | 
|  | AMD_DBGAPI_WAVE_INFO_WORKGROUP_COORD, | 
|  | sizeof (group_ids), &group_ids); | 
|  |  | 
|  | amd_dbgapi_wave_get_info (wave_id, | 
|  | AMD_DBGAPI_WAVE_INFO_WAVE_NUMBER_IN_WORKGROUP, | 
|  | sizeof (wave_in_group), &wave_in_group); | 
|  | } | 
|  |  | 
|  | /* Get the wave_info object for TP, from the wave_info map.  It is | 
|  | assumed that the wave is in the map.  */ | 
|  |  | 
|  | static wave_info & | 
|  | get_thread_wave_info (thread_info *tp) | 
|  | { | 
|  | amd_dbgapi_inferior_info *info = get_amd_dbgapi_inferior_info (tp->inf); | 
|  | amd_dbgapi_wave_id_t wave_id = get_amd_dbgapi_wave_id (tp->ptid); | 
|  |  | 
|  | auto it = info->wave_info_map.find (wave_id.handle); | 
|  | gdb_assert (it != info->wave_info_map.end ()); | 
|  |  | 
|  | return it->second; | 
|  | } | 
|  |  | 
|  | /* Clear our async event handler.  */ | 
|  |  | 
|  | static void | 
|  | async_event_handler_clear () | 
|  | { | 
|  | gdb_assert (amd_dbgapi_async_event_handler != nullptr); | 
|  | clear_async_event_handler (amd_dbgapi_async_event_handler); | 
|  | } | 
|  |  | 
|  | /* Mark our async event handler.  */ | 
|  |  | 
|  | static void | 
|  | async_event_handler_mark () | 
|  | { | 
|  | gdb_assert (amd_dbgapi_async_event_handler != nullptr); | 
|  | mark_async_event_handler (amd_dbgapi_async_event_handler); | 
|  | } | 
|  |  | 
|  | /* Set forward progress requirement to REQUIRE for all processes of PROC_TARGET | 
|  | matching PTID.  */ | 
|  |  | 
|  | static void | 
|  | require_forward_progress (ptid_t ptid, process_stratum_target *proc_target, | 
|  | bool require) | 
|  | { | 
|  | for (inferior *inf : all_inferiors (proc_target)) | 
|  | { | 
|  | if (ptid != minus_one_ptid && inf->pid != ptid.pid ()) | 
|  | continue; | 
|  |  | 
|  | amd_dbgapi_inferior_info *info = get_amd_dbgapi_inferior_info (inf); | 
|  |  | 
|  | if (info->process_id == AMD_DBGAPI_PROCESS_NONE) | 
|  | continue; | 
|  |  | 
|  | /* Don't do unnecessary calls to amd-dbgapi to avoid polluting the logs.  */ | 
|  | if (info->forward_progress_required == require) | 
|  | continue; | 
|  |  | 
|  | amd_dbgapi_status_t status | 
|  | = amd_dbgapi_process_set_progress | 
|  | (info->process_id, (require | 
|  | ? AMD_DBGAPI_PROGRESS_NORMAL | 
|  | : AMD_DBGAPI_PROGRESS_NO_FORWARD)); | 
|  | gdb_assert (status == AMD_DBGAPI_STATUS_SUCCESS); | 
|  |  | 
|  | info->forward_progress_required = require; | 
|  |  | 
|  | /* If ptid targets a single inferior and we have found it, no need to | 
|  | continue.  */ | 
|  | if (ptid != minus_one_ptid) | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | /* See amd-dbgapi-target.h.  */ | 
|  |  | 
|  | amd_dbgapi_process_id_t | 
|  | get_amd_dbgapi_process_id (inferior *inf) | 
|  | { | 
|  | return get_amd_dbgapi_inferior_info (inf)->process_id; | 
|  | } | 
|  |  | 
|  | /* A breakpoint dbgapi wants us to insert, to handle shared library | 
|  | loading/unloading.  */ | 
|  |  | 
|  | struct amd_dbgapi_target_breakpoint : public code_breakpoint | 
|  | { | 
|  | amd_dbgapi_target_breakpoint (struct gdbarch *gdbarch, CORE_ADDR address) | 
|  | : code_breakpoint (gdbarch, bp_breakpoint) | 
|  | { | 
|  | symtab_and_line sal; | 
|  | sal.pc = address; | 
|  | sal.section = find_pc_overlay (sal.pc); | 
|  | sal.pspace = current_program_space; | 
|  | add_location (sal); | 
|  |  | 
|  | pspace = current_program_space; | 
|  | disposition = disp_donttouch; | 
|  | } | 
|  |  | 
|  | void re_set () override; | 
|  | void check_status (struct bpstat *bs) override; | 
|  | }; | 
|  |  | 
|  | void | 
|  | amd_dbgapi_target_breakpoint::re_set () | 
|  | { | 
|  | /* Nothing.  */ | 
|  | } | 
|  |  | 
|  | void | 
|  | amd_dbgapi_target_breakpoint::check_status (struct bpstat *bs) | 
|  | { | 
|  | struct inferior *inf = current_inferior (); | 
|  | amd_dbgapi_inferior_info *info = get_amd_dbgapi_inferior_info (inf); | 
|  | amd_dbgapi_status_t status; | 
|  |  | 
|  | bs->stop = 0; | 
|  | bs->print_it = print_it_noop; | 
|  |  | 
|  | /* Find the address the breakpoint is set at.  */ | 
|  | auto match_breakpoint | 
|  | = [bs] (const decltype (info->breakpoint_map)::value_type &value) | 
|  | { return value.second == bs->breakpoint_at; }; | 
|  | auto it | 
|  | = std::find_if (info->breakpoint_map.begin (), info->breakpoint_map.end (), | 
|  | match_breakpoint); | 
|  |  | 
|  | if (it == info->breakpoint_map.end ()) | 
|  | error (_("Could not find breakpoint_id for breakpoint at %s"), | 
|  | paddress (inf->arch (), bs->bp_location_at->address)); | 
|  |  | 
|  | amd_dbgapi_breakpoint_id_t breakpoint_id { it->first }; | 
|  | amd_dbgapi_breakpoint_action_t action; | 
|  |  | 
|  | status = amd_dbgapi_report_breakpoint_hit | 
|  | (breakpoint_id, | 
|  | reinterpret_cast<amd_dbgapi_client_thread_id_t> (inferior_thread ()), | 
|  | &action); | 
|  |  | 
|  | if (status != AMD_DBGAPI_STATUS_SUCCESS) | 
|  | error (_("amd_dbgapi_report_breakpoint_hit failed for breakpoint %ld " | 
|  | "at %s (%s)"), | 
|  | breakpoint_id.handle, paddress (inf->arch (), bs->bp_location_at->address), | 
|  | get_status_string (status)); | 
|  |  | 
|  | if (action == AMD_DBGAPI_BREAKPOINT_ACTION_RESUME) | 
|  | return; | 
|  |  | 
|  | /* If the action is AMD_DBGAPI_BREAKPOINT_ACTION_HALT, we need to wait until | 
|  | a breakpoint resume event for this breakpoint_id is seen.  */ | 
|  | amd_dbgapi_event_id_t resume_event_id | 
|  | = process_event_queue (info->process_id, | 
|  | AMD_DBGAPI_EVENT_KIND_BREAKPOINT_RESUME); | 
|  |  | 
|  | /* We should always get a breakpoint_resume event after processing all | 
|  | events generated by reporting the breakpoint hit.  */ | 
|  | gdb_assert (resume_event_id != AMD_DBGAPI_EVENT_NONE); | 
|  |  | 
|  | amd_dbgapi_breakpoint_id_t resume_breakpoint_id; | 
|  | status = amd_dbgapi_event_get_info (resume_event_id, | 
|  | AMD_DBGAPI_EVENT_INFO_BREAKPOINT, | 
|  | sizeof (resume_breakpoint_id), | 
|  | &resume_breakpoint_id); | 
|  |  | 
|  | if (status != AMD_DBGAPI_STATUS_SUCCESS) | 
|  | error (_("amd_dbgapi_event_get_info failed (%s)"), get_status_string (status)); | 
|  |  | 
|  | /* The debugger API guarantees that [breakpoint_hit...resume_breakpoint] | 
|  | sequences cannot interleave, so this breakpoint resume event must be | 
|  | for our breakpoint_id.  */ | 
|  | if (resume_breakpoint_id != breakpoint_id) | 
|  | error (_("breakpoint resume event is not for this breakpoint. " | 
|  | "Expected breakpoint_%ld, got breakpoint_%ld"), | 
|  | breakpoint_id.handle, resume_breakpoint_id.handle); | 
|  |  | 
|  | amd_dbgapi_event_processed (resume_event_id); | 
|  | } | 
|  |  | 
|  | bool | 
|  | amd_dbgapi_target::thread_alive (ptid_t ptid) | 
|  | { | 
|  | if (!ptid_is_gpu (ptid)) | 
|  | return beneath ()->thread_alive (ptid); | 
|  |  | 
|  | /* Check that the wave_id is valid.  */ | 
|  |  | 
|  | amd_dbgapi_wave_state_t state; | 
|  | amd_dbgapi_status_t status | 
|  | = amd_dbgapi_wave_get_info (get_amd_dbgapi_wave_id (ptid), | 
|  | AMD_DBGAPI_WAVE_INFO_STATE, sizeof (state), | 
|  | &state); | 
|  | return status == AMD_DBGAPI_STATUS_SUCCESS; | 
|  | } | 
|  |  | 
|  | const char * | 
|  | amd_dbgapi_target::thread_name (thread_info *tp) | 
|  | { | 
|  | if (!ptid_is_gpu (tp->ptid)) | 
|  | return beneath ()->thread_name (tp); | 
|  |  | 
|  | return nullptr; | 
|  | } | 
|  |  | 
|  | std::string | 
|  | amd_dbgapi_target::pid_to_str (ptid_t ptid) | 
|  | { | 
|  | if (!ptid_is_gpu (ptid)) | 
|  | return beneath ()->pid_to_str (ptid); | 
|  |  | 
|  | process_stratum_target *proc_target = current_inferior ()->process_target (); | 
|  | inferior *inf = find_inferior_pid (proc_target, ptid.pid ()); | 
|  | gdb_assert (inf != nullptr); | 
|  | amd_dbgapi_inferior_info *info = get_amd_dbgapi_inferior_info (inf); | 
|  |  | 
|  | auto wave_id = get_amd_dbgapi_wave_id (ptid); | 
|  |  | 
|  | auto it = info->wave_info_map.find (wave_id.handle); | 
|  | if (it != info->wave_info_map.end ()) | 
|  | return it->second.coords.to_string (); | 
|  |  | 
|  | /* A wave we don't know about.  Shouldn't usually happen, but | 
|  | asserting and bringing down the session is a bit too harsh.  Just | 
|  | print all unknown info as "?"s.  */ | 
|  | return wave_coordinates (wave_id).to_string (); | 
|  | } | 
|  |  | 
|  | const char * | 
|  | amd_dbgapi_target::extra_thread_info (thread_info *tp) | 
|  | { | 
|  | if (!ptid_is_gpu (tp->ptid)) | 
|  | beneath ()->extra_thread_info (tp); | 
|  |  | 
|  | return nullptr; | 
|  | } | 
|  |  | 
|  | target_xfer_status | 
|  | amd_dbgapi_target::xfer_partial (enum target_object object, const char *annex, | 
|  | gdb_byte *readbuf, const gdb_byte *writebuf, | 
|  | ULONGEST offset, ULONGEST requested_len, | 
|  | ULONGEST *xfered_len) | 
|  | { | 
|  | std::optional<scoped_restore_current_thread> maybe_restore_thread; | 
|  |  | 
|  | if (!ptid_is_gpu (inferior_ptid)) | 
|  | return beneath ()->xfer_partial (object, annex, readbuf, writebuf, offset, | 
|  | requested_len, xfered_len); | 
|  |  | 
|  | gdb_assert (requested_len > 0); | 
|  | gdb_assert (xfered_len != nullptr); | 
|  |  | 
|  | if (object != TARGET_OBJECT_MEMORY) | 
|  | return TARGET_XFER_E_IO; | 
|  |  | 
|  | amd_dbgapi_process_id_t process_id | 
|  | = get_amd_dbgapi_process_id (current_inferior ()); | 
|  | amd_dbgapi_wave_id_t wave_id = get_amd_dbgapi_wave_id (inferior_ptid); | 
|  |  | 
|  | size_t len = requested_len; | 
|  | amd_dbgapi_status_t status; | 
|  |  | 
|  | if (readbuf != nullptr) | 
|  | status = amd_dbgapi_read_memory (process_id, wave_id, 0, | 
|  | AMD_DBGAPI_ADDRESS_SPACE_GLOBAL, | 
|  | offset, &len, readbuf); | 
|  | else | 
|  | status = amd_dbgapi_write_memory (process_id, wave_id, 0, | 
|  | AMD_DBGAPI_ADDRESS_SPACE_GLOBAL, | 
|  | offset, &len, writebuf); | 
|  |  | 
|  | if (status != AMD_DBGAPI_STATUS_SUCCESS) | 
|  | return TARGET_XFER_E_IO; | 
|  |  | 
|  | *xfered_len = len; | 
|  | return TARGET_XFER_OK; | 
|  | } | 
|  |  | 
|  | bool | 
|  | amd_dbgapi_target::stopped_by_watchpoint () | 
|  | { | 
|  | if (!ptid_is_gpu (inferior_ptid)) | 
|  | return beneath ()->stopped_by_watchpoint (); | 
|  |  | 
|  | return false; | 
|  | } | 
|  |  | 
|  | void | 
|  | amd_dbgapi_target::resume (ptid_t scope_ptid, int step, enum gdb_signal signo) | 
|  | { | 
|  | amd_dbgapi_debug_printf ("scope_ptid = %s", scope_ptid.to_string ().c_str ()); | 
|  |  | 
|  | /* The amd_dbgapi_exceptions_t matching SIGNO will only be used if the | 
|  | thread which is the target of the signal SIGNO is a GPU thread.  If so, | 
|  | make sure that there is a corresponding amd_dbgapi_exceptions_t for SIGNO | 
|  | before we try to resume any thread.  */ | 
|  | amd_dbgapi_exceptions_t exception = AMD_DBGAPI_EXCEPTION_NONE; | 
|  | if (ptid_is_gpu (inferior_ptid)) | 
|  | { | 
|  | switch (signo) | 
|  | { | 
|  | case GDB_SIGNAL_BUS: | 
|  | exception = AMD_DBGAPI_EXCEPTION_WAVE_APERTURE_VIOLATION; | 
|  | break; | 
|  | case GDB_SIGNAL_SEGV: | 
|  | exception = AMD_DBGAPI_EXCEPTION_WAVE_MEMORY_VIOLATION; | 
|  | break; | 
|  | case GDB_SIGNAL_ILL: | 
|  | exception = AMD_DBGAPI_EXCEPTION_WAVE_ILLEGAL_INSTRUCTION; | 
|  | break; | 
|  | case GDB_SIGNAL_FPE: | 
|  | exception = AMD_DBGAPI_EXCEPTION_WAVE_MATH_ERROR; | 
|  | break; | 
|  | case GDB_SIGNAL_ABRT: | 
|  | exception = AMD_DBGAPI_EXCEPTION_WAVE_ABORT; | 
|  | break; | 
|  | case GDB_SIGNAL_TRAP: | 
|  | exception = AMD_DBGAPI_EXCEPTION_WAVE_TRAP; | 
|  | break; | 
|  | case GDB_SIGNAL_0: | 
|  | exception = AMD_DBGAPI_EXCEPTION_NONE; | 
|  | break; | 
|  | default: | 
|  | error (_("Resuming with signal %s is not supported by this agent."), | 
|  | gdb_signal_to_name (signo)); | 
|  | } | 
|  | } | 
|  |  | 
|  | if (!ptid_is_gpu (inferior_ptid) || scope_ptid != inferior_ptid) | 
|  | { | 
|  | beneath ()->resume (scope_ptid, step, signo); | 
|  |  | 
|  | /* If the request is for a single thread, we are done.  */ | 
|  | if (scope_ptid == inferior_ptid) | 
|  | return; | 
|  | } | 
|  |  | 
|  | process_stratum_target *proc_target = current_inferior ()->process_target (); | 
|  |  | 
|  | /* Disable forward progress requirement.  */ | 
|  | require_forward_progress (scope_ptid, proc_target, false); | 
|  |  | 
|  | for (thread_info *thread : all_non_exited_threads (proc_target, scope_ptid)) | 
|  | { | 
|  | if (!ptid_is_gpu (thread->ptid)) | 
|  | continue; | 
|  |  | 
|  | amd_dbgapi_wave_id_t wave_id = get_amd_dbgapi_wave_id (thread->ptid); | 
|  | amd_dbgapi_status_t status; | 
|  |  | 
|  | wave_info &wi = get_thread_wave_info (thread); | 
|  | amd_dbgapi_resume_mode_t &resume_mode = wi.last_resume_mode; | 
|  | amd_dbgapi_exceptions_t wave_exception; | 
|  | if (thread->ptid == inferior_ptid) | 
|  | { | 
|  | resume_mode = (step | 
|  | ? AMD_DBGAPI_RESUME_MODE_SINGLE_STEP | 
|  | : AMD_DBGAPI_RESUME_MODE_NORMAL); | 
|  | wave_exception = exception; | 
|  | } | 
|  | else | 
|  | { | 
|  | resume_mode = AMD_DBGAPI_RESUME_MODE_NORMAL; | 
|  | wave_exception = AMD_DBGAPI_EXCEPTION_NONE; | 
|  | } | 
|  |  | 
|  | status = amd_dbgapi_wave_resume (wave_id, resume_mode, wave_exception); | 
|  | if (status != AMD_DBGAPI_STATUS_SUCCESS | 
|  | /* Ignore the error that wave is no longer valid as that could | 
|  | indicate that the process has exited.  GDB treats resuming a | 
|  | thread that no longer exists as being successful.  */ | 
|  | && status != AMD_DBGAPI_STATUS_ERROR_INVALID_WAVE_ID) | 
|  | error (_("wave_resume for wave_%ld failed (%s)"), wave_id.handle, | 
|  | get_status_string (status)); | 
|  |  | 
|  | wi.stopping = false; | 
|  | } | 
|  | } | 
|  |  | 
|  | void | 
|  | amd_dbgapi_target::commit_resumed () | 
|  | { | 
|  | amd_dbgapi_debug_printf ("called"); | 
|  |  | 
|  | beneath ()->commit_resumed (); | 
|  |  | 
|  | process_stratum_target *proc_target = current_inferior ()->process_target (); | 
|  | require_forward_progress (minus_one_ptid, proc_target, true); | 
|  | } | 
|  |  | 
|  | /* Return a string version of RESUME_MODE, for debug log purposes.  */ | 
|  |  | 
|  | static const char * | 
|  | resume_mode_to_string (amd_dbgapi_resume_mode_t resume_mode) | 
|  | { | 
|  | switch (resume_mode) | 
|  | { | 
|  | case AMD_DBGAPI_RESUME_MODE_NORMAL: | 
|  | return "normal"; | 
|  | case AMD_DBGAPI_RESUME_MODE_SINGLE_STEP: | 
|  | return "step"; | 
|  | } | 
|  | gdb_assert_not_reached ("invalid amd_dbgapi_resume_mode_t"); | 
|  | } | 
|  |  | 
|  | void | 
|  | amd_dbgapi_target::stop (ptid_t ptid) | 
|  | { | 
|  | amd_dbgapi_debug_printf ("ptid = %s", ptid.to_string ().c_str ()); | 
|  |  | 
|  | bool many_threads = ptid == minus_one_ptid || ptid.is_pid (); | 
|  |  | 
|  | if (!ptid_is_gpu (ptid) || many_threads) | 
|  | { | 
|  | beneath ()->stop (ptid); | 
|  |  | 
|  | /* The request is for a single thread, we are done.  */ | 
|  | if (!many_threads) | 
|  | return; | 
|  | } | 
|  |  | 
|  | auto stop_one_thread = [this] (thread_info *thread) | 
|  | { | 
|  | gdb_assert (thread != nullptr); | 
|  |  | 
|  | amd_dbgapi_wave_id_t wave_id = get_amd_dbgapi_wave_id (thread->ptid); | 
|  | amd_dbgapi_wave_state_t state; | 
|  | amd_dbgapi_status_t status | 
|  | = amd_dbgapi_wave_get_info (wave_id, AMD_DBGAPI_WAVE_INFO_STATE, | 
|  | sizeof (state), &state); | 
|  | if (status == AMD_DBGAPI_STATUS_SUCCESS) | 
|  | { | 
|  | /* If the wave is already known to be stopped then do nothing.  */ | 
|  | if (state == AMD_DBGAPI_WAVE_STATE_STOP) | 
|  | return; | 
|  |  | 
|  | status = amd_dbgapi_wave_stop (wave_id); | 
|  | if (status == AMD_DBGAPI_STATUS_SUCCESS) | 
|  | { | 
|  | wave_info &wi = get_thread_wave_info (thread); | 
|  | wi.stopping = true; | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (status != AMD_DBGAPI_STATUS_ERROR_INVALID_WAVE_ID) | 
|  | error (_("wave_stop for wave_%ld failed (%s)"), wave_id.handle, | 
|  | get_status_string (status)); | 
|  | } | 
|  | else if (status != AMD_DBGAPI_STATUS_ERROR_INVALID_WAVE_ID) | 
|  | error (_("wave_get_info for wave_%ld failed (%s)"), wave_id.handle, | 
|  | get_status_string (status)); | 
|  |  | 
|  | /* The status is AMD_DBGAPI_STATUS_ERROR_INVALID_WAVE_ID.  The wave | 
|  | could have terminated since the last time the wave list was | 
|  | refreshed.  */ | 
|  |  | 
|  | wave_info &wi = get_thread_wave_info (thread); | 
|  | wi.stopping = true; | 
|  |  | 
|  | amd_dbgapi_debug_printf ("got AMD_DBGAPI_STATUS_ERROR_INVALID_WAVE_ID " | 
|  | "for wave_%ld, last_resume_mode=%s, " | 
|  | "report_thread_events=%d", | 
|  | wave_id.handle, | 
|  | resume_mode_to_string (wi.last_resume_mode), | 
|  | m_report_thread_events); | 
|  |  | 
|  | /* If the wave was stepping when it terminated, then it is | 
|  | guaranteed that we will see a WAVE_COMMAND_TERMINATED event | 
|  | for it.  Don't report a thread exit event or delete the | 
|  | thread yet, until we see such event.  */ | 
|  | if (wi.last_resume_mode == AMD_DBGAPI_RESUME_MODE_SINGLE_STEP) | 
|  | return; | 
|  |  | 
|  | if (m_report_thread_events) | 
|  | { | 
|  | get_amd_dbgapi_inferior_info (thread->inf)->wave_events.emplace_back | 
|  | (thread->ptid, target_waitstatus ().set_thread_exited (0)); | 
|  |  | 
|  | if (target_is_async_p ()) | 
|  | async_event_handler_mark (); | 
|  | } | 
|  |  | 
|  | delete_thread_silent (thread); | 
|  | }; | 
|  |  | 
|  | process_stratum_target *proc_target = current_inferior ()->process_target (); | 
|  |  | 
|  | /* Disable forward progress requirement.  */ | 
|  | require_forward_progress (ptid, proc_target, false); | 
|  |  | 
|  | if (!many_threads) | 
|  | { | 
|  | /* No need to iterate all non-exited threads if the request is to stop a | 
|  | specific thread.  */ | 
|  | stop_one_thread (proc_target->find_thread (ptid)); | 
|  | return; | 
|  | } | 
|  |  | 
|  | for (auto *inf : all_inferiors (proc_target)) | 
|  | /* Use the threads_safe iterator since stop_one_thread may delete the | 
|  | thread if it has exited.  */ | 
|  | for (auto *thread : inf->threads_safe ()) | 
|  | if (thread->state != THREAD_EXITED && thread->ptid.matches (ptid) | 
|  | && ptid_is_gpu (thread->ptid)) | 
|  | stop_one_thread (thread); | 
|  | } | 
|  |  | 
|  | /* Callback for our async event handler.  */ | 
|  |  | 
|  | static void | 
|  | handle_target_event (gdb_client_data client_data) | 
|  | { | 
|  | inferior_event_handler (INF_REG_EVENT); | 
|  | } | 
|  |  | 
|  | struct scoped_amd_dbgapi_event_processed | 
|  | { | 
|  | scoped_amd_dbgapi_event_processed (amd_dbgapi_event_id_t event_id) | 
|  | : m_event_id (event_id) | 
|  | { | 
|  | gdb_assert (event_id != AMD_DBGAPI_EVENT_NONE); | 
|  | } | 
|  |  | 
|  | ~scoped_amd_dbgapi_event_processed () | 
|  | { | 
|  | amd_dbgapi_status_t status = amd_dbgapi_event_processed (m_event_id); | 
|  | if (status != AMD_DBGAPI_STATUS_SUCCESS) | 
|  | warning (_("Failed to acknowledge amd-dbgapi event %" PRIu64), | 
|  | m_event_id.handle); | 
|  | } | 
|  |  | 
|  | DISABLE_COPY_AND_ASSIGN (scoped_amd_dbgapi_event_processed); | 
|  |  | 
|  | private: | 
|  | amd_dbgapi_event_id_t m_event_id; | 
|  | }; | 
|  |  | 
|  | /* Called when a dbgapi notifier fd is readable.  CLIENT_DATA is the | 
|  | amd_dbgapi_inferior_info object corresponding to the notifier.  */ | 
|  |  | 
|  | static void | 
|  | dbgapi_notifier_handler (int err, gdb_client_data client_data) | 
|  | { | 
|  | amd_dbgapi_inferior_info *info = (amd_dbgapi_inferior_info *) client_data; | 
|  | int ret; | 
|  |  | 
|  | /* Drain the notifier pipe.  */ | 
|  | do | 
|  | { | 
|  | char buf; | 
|  | ret = read (info->notifier, &buf, 1); | 
|  | } | 
|  | while (ret >= 0 || (ret == -1 && errno == EINTR)); | 
|  |  | 
|  | if (info->inf->target_is_pushed (&the_amd_dbgapi_target)) | 
|  | { | 
|  | /* The amd-dbgapi target is pushed: signal our async handler, the event | 
|  | will be consumed through our wait method.  */ | 
|  |  | 
|  | async_event_handler_mark (); | 
|  | } | 
|  | else | 
|  | { | 
|  | /* The amd-dbgapi target is not pushed: if there's an event, the only | 
|  | expected one is one of the RUNTIME kind.  If the event tells us the | 
|  | inferior as activated the ROCm runtime, push the amd-dbgapi | 
|  | target.  */ | 
|  |  | 
|  | amd_dbgapi_event_id_t event_id; | 
|  | amd_dbgapi_event_kind_t event_kind; | 
|  | amd_dbgapi_status_t status | 
|  | = amd_dbgapi_process_next_pending_event (info->process_id, &event_id, | 
|  | &event_kind); | 
|  | if (status != AMD_DBGAPI_STATUS_SUCCESS) | 
|  | error (_("next_pending_event failed (%s)"), get_status_string (status)); | 
|  |  | 
|  | if (event_id == AMD_DBGAPI_EVENT_NONE) | 
|  | return; | 
|  |  | 
|  | gdb_assert (event_kind == AMD_DBGAPI_EVENT_KIND_RUNTIME); | 
|  |  | 
|  | scoped_amd_dbgapi_event_processed mark_event_processed (event_id); | 
|  |  | 
|  | amd_dbgapi_runtime_state_t runtime_state; | 
|  | status = amd_dbgapi_event_get_info (event_id, | 
|  | AMD_DBGAPI_EVENT_INFO_RUNTIME_STATE, | 
|  | sizeof (runtime_state), | 
|  | &runtime_state); | 
|  | if (status != AMD_DBGAPI_STATUS_SUCCESS) | 
|  | error (_("event_get_info for event_%ld failed (%s)"), | 
|  | event_id.handle, get_status_string (status)); | 
|  |  | 
|  | switch (runtime_state) | 
|  | { | 
|  | case AMD_DBGAPI_RUNTIME_STATE_LOADED_SUCCESS: | 
|  | gdb_assert (info->runtime_state == AMD_DBGAPI_RUNTIME_STATE_UNLOADED); | 
|  | info->runtime_state = runtime_state; | 
|  | amd_dbgapi_debug_printf ("pushing amd-dbgapi target"); | 
|  | info->inf->push_target (&the_amd_dbgapi_target); | 
|  |  | 
|  | /* The underlying target will already be async if we are running, but not if | 
|  | we are attaching.  */ | 
|  | if (info->inf->process_target ()->is_async_p ()) | 
|  | { | 
|  | scoped_restore_current_thread restore_thread; | 
|  | switch_to_inferior_no_thread (info->inf); | 
|  |  | 
|  | /* Make sure our async event handler is created.  */ | 
|  | target_async (true); | 
|  | } | 
|  | break; | 
|  |  | 
|  | case AMD_DBGAPI_RUNTIME_STATE_UNLOADED: | 
|  | gdb_assert (info->runtime_state | 
|  | == AMD_DBGAPI_RUNTIME_STATE_LOADED_ERROR_RESTRICTION); | 
|  | info->runtime_state = runtime_state; | 
|  | break; | 
|  |  | 
|  | case AMD_DBGAPI_RUNTIME_STATE_LOADED_ERROR_RESTRICTION: | 
|  | gdb_assert (info->runtime_state == AMD_DBGAPI_RUNTIME_STATE_UNLOADED); | 
|  | info->runtime_state = runtime_state; | 
|  | warning (_("amd-dbgapi: unable to enable GPU debugging " | 
|  | "due to a restriction error")); | 
|  | break; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | void | 
|  | amd_dbgapi_target::async (bool enable) | 
|  | { | 
|  | beneath ()->async (enable); | 
|  |  | 
|  | if (enable) | 
|  | { | 
|  | if (amd_dbgapi_async_event_handler != nullptr) | 
|  | { | 
|  | /* Already enabled.  */ | 
|  | return; | 
|  | } | 
|  |  | 
|  | /* The library gives us one notifier file descriptor per inferior (even | 
|  | the ones that have not yet loaded their runtime).  Register them | 
|  | all with the event loop.  */ | 
|  | process_stratum_target *proc_target | 
|  | = current_inferior ()->process_target (); | 
|  |  | 
|  | for (inferior *inf : all_non_exited_inferiors (proc_target)) | 
|  | { | 
|  | amd_dbgapi_inferior_info *info = get_amd_dbgapi_inferior_info (inf); | 
|  |  | 
|  | if (info->notifier != -1) | 
|  | add_file_handler (info->notifier, dbgapi_notifier_handler, info, | 
|  | string_printf ("amd-dbgapi notifier for pid %d", | 
|  | inf->pid)); | 
|  | } | 
|  |  | 
|  | amd_dbgapi_async_event_handler | 
|  | = create_async_event_handler (handle_target_event, nullptr, | 
|  | "amd-dbgapi"); | 
|  |  | 
|  | /* There may be pending events to handle.  Tell the event loop to poll | 
|  | them.  */ | 
|  | async_event_handler_mark (); | 
|  | } | 
|  | else | 
|  | { | 
|  | if (amd_dbgapi_async_event_handler == nullptr) | 
|  | return; | 
|  |  | 
|  | for (inferior *inf : all_inferiors ()) | 
|  | { | 
|  | amd_dbgapi_inferior_info *info = get_amd_dbgapi_inferior_info (inf); | 
|  |  | 
|  | if (info->notifier != -1) | 
|  | delete_file_handler (info->notifier); | 
|  | } | 
|  |  | 
|  | delete_async_event_handler (&amd_dbgapi_async_event_handler); | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Make a ptid for a GPU wave.  See comment on ptid_is_gpu for more details.  */ | 
|  |  | 
|  | static ptid_t | 
|  | make_gpu_ptid (ptid_t::pid_type pid, amd_dbgapi_wave_id_t wave_id) | 
|  | { | 
|  | return ptid_t (pid, 1, wave_id.handle); | 
|  | } | 
|  |  | 
|  | /* When a thread is deleted, remove its wave_info from the inferior's | 
|  | wave_info map.  */ | 
|  |  | 
|  | static void | 
|  | amd_dbgapi_thread_deleted (thread_info *tp) | 
|  | { | 
|  | if (tp->inf->target_at (arch_stratum) == &the_amd_dbgapi_target | 
|  | && ptid_is_gpu (tp->ptid)) | 
|  | { | 
|  | amd_dbgapi_inferior_info *info = amd_dbgapi_inferior_data.get (tp->inf); | 
|  | auto wave_id = get_amd_dbgapi_wave_id (tp->ptid); | 
|  | auto it = info->wave_info_map.find (wave_id.handle); | 
|  | gdb_assert (it != info->wave_info_map.end ()); | 
|  | info->wave_info_map.erase (it); | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Register WAVE_PTID as a new thread in INF's thread list, and record | 
|  | its wave_info in the inferior's wave_info map.  */ | 
|  |  | 
|  | static thread_info * | 
|  | add_gpu_thread (inferior *inf, ptid_t wave_ptid) | 
|  | { | 
|  | process_stratum_target *proc_target = inf->process_target (); | 
|  | amd_dbgapi_inferior_info *info = get_amd_dbgapi_inferior_info (inf); | 
|  |  | 
|  | auto wave_id = get_amd_dbgapi_wave_id (wave_ptid); | 
|  |  | 
|  | if (!info->wave_info_map.try_emplace (wave_id.handle, | 
|  | wave_info (wave_id)).second) | 
|  | internal_error ("wave ID %ld already in map", wave_id.handle); | 
|  |  | 
|  | /* Create new GPU threads silently to avoid spamming the terminal | 
|  | with thousands of "[New Thread ...]" messages.  */ | 
|  | thread_info *thread = add_thread_silent (proc_target, wave_ptid); | 
|  | set_running (proc_target, wave_ptid, true); | 
|  | set_executing (proc_target, wave_ptid, true); | 
|  | return thread; | 
|  | } | 
|  |  | 
|  | /* Process an event that was just pulled out of the amd-dbgapi library.  */ | 
|  |  | 
|  | static void | 
|  | process_one_event (amd_dbgapi_event_id_t event_id, | 
|  | amd_dbgapi_event_kind_t event_kind) | 
|  | { | 
|  | /* Automatically mark this event processed when going out of scope.  */ | 
|  | scoped_amd_dbgapi_event_processed mark_event_processed (event_id); | 
|  |  | 
|  | amd_dbgapi_process_id_t process_id; | 
|  | amd_dbgapi_status_t status | 
|  | = amd_dbgapi_event_get_info (event_id, AMD_DBGAPI_EVENT_INFO_PROCESS, | 
|  | sizeof (process_id), &process_id); | 
|  | if (status != AMD_DBGAPI_STATUS_SUCCESS) | 
|  | error (_("event_get_info for event_%ld failed (%s)"), event_id.handle, | 
|  | get_status_string (status)); | 
|  |  | 
|  | amd_dbgapi_os_process_id_t pid; | 
|  | status = amd_dbgapi_process_get_info (process_id, | 
|  | AMD_DBGAPI_PROCESS_INFO_OS_ID, | 
|  | sizeof (pid), &pid); | 
|  | if (status != AMD_DBGAPI_STATUS_SUCCESS) | 
|  | error (_("process_get_info for process_%ld failed (%s)"), | 
|  | process_id.handle, get_status_string (status)); | 
|  |  | 
|  | auto *proc_target = current_inferior ()->process_target (); | 
|  | inferior *inf = find_inferior_pid (proc_target, pid); | 
|  | gdb_assert (inf != nullptr); | 
|  | amd_dbgapi_inferior_info *info = get_amd_dbgapi_inferior_info (inf); | 
|  |  | 
|  | switch (event_kind) | 
|  | { | 
|  | case AMD_DBGAPI_EVENT_KIND_WAVE_COMMAND_TERMINATED: | 
|  | case AMD_DBGAPI_EVENT_KIND_WAVE_STOP: | 
|  | { | 
|  | amd_dbgapi_wave_id_t wave_id; | 
|  | status | 
|  | = amd_dbgapi_event_get_info (event_id, AMD_DBGAPI_EVENT_INFO_WAVE, | 
|  | sizeof (wave_id), &wave_id); | 
|  | if (status != AMD_DBGAPI_STATUS_SUCCESS) | 
|  | error (_("event_get_info for event_%ld failed (%s)"), | 
|  | event_id.handle, get_status_string (status)); | 
|  |  | 
|  | ptid_t event_ptid = make_gpu_ptid (pid, wave_id); | 
|  | target_waitstatus ws; | 
|  |  | 
|  | amd_dbgapi_wave_stop_reasons_t stop_reason; | 
|  | status = amd_dbgapi_wave_get_info (wave_id, | 
|  | AMD_DBGAPI_WAVE_INFO_STOP_REASON, | 
|  | sizeof (stop_reason), &stop_reason); | 
|  | if (status == AMD_DBGAPI_STATUS_ERROR_INVALID_WAVE_ID | 
|  | && event_kind == AMD_DBGAPI_EVENT_KIND_WAVE_COMMAND_TERMINATED) | 
|  | ws.set_thread_exited (0); | 
|  | else if (status == AMD_DBGAPI_STATUS_SUCCESS) | 
|  | { | 
|  | if (stop_reason & AMD_DBGAPI_WAVE_STOP_REASON_APERTURE_VIOLATION) | 
|  | ws.set_stopped (GDB_SIGNAL_BUS); | 
|  | else if (stop_reason | 
|  | & AMD_DBGAPI_WAVE_STOP_REASON_MEMORY_VIOLATION) | 
|  | ws.set_stopped (GDB_SIGNAL_SEGV); | 
|  | else if (stop_reason | 
|  | & AMD_DBGAPI_WAVE_STOP_REASON_ILLEGAL_INSTRUCTION) | 
|  | ws.set_stopped (GDB_SIGNAL_ILL); | 
|  | else if (stop_reason | 
|  | & (AMD_DBGAPI_WAVE_STOP_REASON_FP_INPUT_DENORMAL | 
|  | | AMD_DBGAPI_WAVE_STOP_REASON_FP_DIVIDE_BY_0 | 
|  | | AMD_DBGAPI_WAVE_STOP_REASON_FP_OVERFLOW | 
|  | | AMD_DBGAPI_WAVE_STOP_REASON_FP_UNDERFLOW | 
|  | | AMD_DBGAPI_WAVE_STOP_REASON_FP_INEXACT | 
|  | | AMD_DBGAPI_WAVE_STOP_REASON_FP_INVALID_OPERATION | 
|  | | AMD_DBGAPI_WAVE_STOP_REASON_INT_DIVIDE_BY_0)) | 
|  | ws.set_stopped (GDB_SIGNAL_FPE); | 
|  | else if (stop_reason | 
|  | & (AMD_DBGAPI_WAVE_STOP_REASON_BREAKPOINT | 
|  | | AMD_DBGAPI_WAVE_STOP_REASON_WATCHPOINT | 
|  | | AMD_DBGAPI_WAVE_STOP_REASON_SINGLE_STEP | 
|  | | AMD_DBGAPI_WAVE_STOP_REASON_DEBUG_TRAP | 
|  | | AMD_DBGAPI_WAVE_STOP_REASON_TRAP)) | 
|  | ws.set_stopped (GDB_SIGNAL_TRAP); | 
|  | else if (stop_reason & AMD_DBGAPI_WAVE_STOP_REASON_ASSERT_TRAP) | 
|  | ws.set_stopped (GDB_SIGNAL_ABRT); | 
|  | else | 
|  | ws.set_stopped (GDB_SIGNAL_0); | 
|  |  | 
|  | thread_info *thread = proc_target->find_thread (event_ptid); | 
|  | if (thread == nullptr) | 
|  | thread = add_gpu_thread (inf, event_ptid); | 
|  |  | 
|  | /* If the wave is stopped because of a software breakpoint, the | 
|  | program counter needs to be adjusted so that it points to the | 
|  | breakpoint instruction.  */ | 
|  | if ((stop_reason & AMD_DBGAPI_WAVE_STOP_REASON_BREAKPOINT) != 0) | 
|  | { | 
|  | regcache *regcache = get_thread_regcache (thread); | 
|  | gdbarch *gdbarch = regcache->arch (); | 
|  |  | 
|  | CORE_ADDR pc = regcache_read_pc (regcache); | 
|  | CORE_ADDR adjusted_pc | 
|  | = pc - gdbarch_decr_pc_after_break (gdbarch); | 
|  |  | 
|  | if (adjusted_pc != pc) | 
|  | regcache_write_pc (regcache, adjusted_pc); | 
|  | } | 
|  | } | 
|  | else | 
|  | error (_("wave_get_info for wave_%ld failed (%s)"), | 
|  | wave_id.handle, get_status_string (status)); | 
|  |  | 
|  | info->wave_events.emplace_back (event_ptid, ws); | 
|  | break; | 
|  | } | 
|  |  | 
|  | case AMD_DBGAPI_EVENT_KIND_CODE_OBJECT_LIST_UPDATED: | 
|  | /* We get here when the following sequence of events happens: | 
|  |  | 
|  | - the inferior hits the amd-dbgapi "r_brk" internal breakpoint | 
|  | - amd_dbgapi_target_breakpoint::check_status calls | 
|  | amd_dbgapi_report_breakpoint_hit, which queues an event of this | 
|  | kind in dbgapi | 
|  | - amd_dbgapi_target_breakpoint::check_status calls | 
|  | process_event_queue, which pulls the event out of dbgapi, and | 
|  | gets us here | 
|  |  | 
|  | When amd_dbgapi_target_breakpoint::check_status is called, the current | 
|  | inferior is the inferior that hit the breakpoint, which should still be | 
|  | the case now.  */ | 
|  | gdb_assert (inf == current_inferior ()); | 
|  | handle_solib_event (); | 
|  | break; | 
|  |  | 
|  | case AMD_DBGAPI_EVENT_KIND_BREAKPOINT_RESUME: | 
|  | /* Breakpoint resume events should be handled by the breakpoint | 
|  | action, and this code should not reach this.  */ | 
|  | gdb_assert_not_reached ("unhandled event kind"); | 
|  | break; | 
|  |  | 
|  | case AMD_DBGAPI_EVENT_KIND_RUNTIME: | 
|  | { | 
|  | amd_dbgapi_runtime_state_t runtime_state; | 
|  |  | 
|  | status = amd_dbgapi_event_get_info (event_id, | 
|  | AMD_DBGAPI_EVENT_INFO_RUNTIME_STATE, | 
|  | sizeof (runtime_state), | 
|  | &runtime_state); | 
|  | if (status != AMD_DBGAPI_STATUS_SUCCESS) | 
|  | error (_("event_get_info for event_%ld failed (%s)"), | 
|  | event_id.handle, get_status_string (status)); | 
|  |  | 
|  | gdb_assert (runtime_state == AMD_DBGAPI_RUNTIME_STATE_UNLOADED); | 
|  | gdb_assert | 
|  | (info->runtime_state == AMD_DBGAPI_RUNTIME_STATE_LOADED_SUCCESS); | 
|  |  | 
|  | info->runtime_state = runtime_state; | 
|  |  | 
|  | gdb_assert (inf->target_is_pushed (&the_amd_dbgapi_target)); | 
|  | inf->unpush_target (&the_amd_dbgapi_target); | 
|  | } | 
|  | break; | 
|  |  | 
|  | default: | 
|  | error (_("event kind (%d) not supported"), event_kind); | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Return a textual version of KIND.  */ | 
|  |  | 
|  | static const char * | 
|  | event_kind_str (amd_dbgapi_event_kind_t kind) | 
|  | { | 
|  | switch (kind) | 
|  | { | 
|  | case AMD_DBGAPI_EVENT_KIND_NONE: | 
|  | return "NONE"; | 
|  |  | 
|  | case AMD_DBGAPI_EVENT_KIND_WAVE_STOP: | 
|  | return "WAVE_STOP"; | 
|  |  | 
|  | case AMD_DBGAPI_EVENT_KIND_WAVE_COMMAND_TERMINATED: | 
|  | return "WAVE_COMMAND_TERMINATED"; | 
|  |  | 
|  | case AMD_DBGAPI_EVENT_KIND_CODE_OBJECT_LIST_UPDATED: | 
|  | return "CODE_OBJECT_LIST_UPDATED"; | 
|  |  | 
|  | case AMD_DBGAPI_EVENT_KIND_BREAKPOINT_RESUME: | 
|  | return "BREAKPOINT_RESUME"; | 
|  |  | 
|  | case AMD_DBGAPI_EVENT_KIND_RUNTIME: | 
|  | return "RUNTIME"; | 
|  |  | 
|  | case AMD_DBGAPI_EVENT_KIND_QUEUE_ERROR: | 
|  | return "QUEUE_ERROR"; | 
|  | } | 
|  |  | 
|  | gdb_assert_not_reached ("unhandled amd_dbgapi_event_kind_t value"); | 
|  | } | 
|  |  | 
|  | /* Drain the dbgapi event queue of a given process_id, or of all processes if | 
|  | process_id is AMD_DBGAPI_PROCESS_NONE.  Stop processing the events if an | 
|  | event of a given kind is requested and `process_id` is not | 
|  | AMD_DBGAPI_PROCESS_NONE.  Wave stop events that are not returned are queued | 
|  | into their inferior's amd_dbgapi_inferior_info pending wave events. */ | 
|  |  | 
|  | static amd_dbgapi_event_id_t | 
|  | process_event_queue (amd_dbgapi_process_id_t process_id, | 
|  | amd_dbgapi_event_kind_t until_event_kind) | 
|  | { | 
|  | /* An event of a given type can only be requested from a single | 
|  | process_id.  */ | 
|  | gdb_assert (until_event_kind == AMD_DBGAPI_EVENT_KIND_NONE | 
|  | || process_id != AMD_DBGAPI_PROCESS_NONE); | 
|  |  | 
|  | while (true) | 
|  | { | 
|  | amd_dbgapi_event_id_t event_id; | 
|  | amd_dbgapi_event_kind_t event_kind; | 
|  |  | 
|  | amd_dbgapi_status_t status | 
|  | = amd_dbgapi_process_next_pending_event (process_id, &event_id, | 
|  | &event_kind); | 
|  |  | 
|  | if (status != AMD_DBGAPI_STATUS_SUCCESS) | 
|  | error (_("next_pending_event failed (%s)"), get_status_string (status)); | 
|  |  | 
|  | if (event_kind != AMD_DBGAPI_EVENT_KIND_NONE) | 
|  | amd_dbgapi_debug_printf ("Pulled event from dbgapi: " | 
|  | "event_id.handle = %" PRIu64 ", " | 
|  | "event_kind = %s", | 
|  | event_id.handle, | 
|  | event_kind_str (event_kind)); | 
|  |  | 
|  | if (event_id == AMD_DBGAPI_EVENT_NONE || event_kind == until_event_kind) | 
|  | return event_id; | 
|  |  | 
|  | process_one_event (event_id, event_kind); | 
|  | } | 
|  | } | 
|  |  | 
|  | bool | 
|  | amd_dbgapi_target::has_pending_events () | 
|  | { | 
|  | if (amd_dbgapi_async_event_handler != nullptr | 
|  | && async_event_handler_marked (amd_dbgapi_async_event_handler)) | 
|  | return true; | 
|  |  | 
|  | return beneath ()->has_pending_events (); | 
|  | } | 
|  |  | 
|  | /* Pop one pending event from the per-inferior structures. | 
|  |  | 
|  | If PID is not -1, restrict the search to the inferior with that pid.  */ | 
|  |  | 
|  | static std::pair<ptid_t, target_waitstatus> | 
|  | consume_one_event (int pid) | 
|  | { | 
|  | auto *target = current_inferior ()->process_target (); | 
|  | struct amd_dbgapi_inferior_info *info = nullptr; | 
|  |  | 
|  | if (pid == -1) | 
|  | { | 
|  | for (inferior *inf : all_inferiors (target)) | 
|  | { | 
|  | info = get_amd_dbgapi_inferior_info (inf); | 
|  | if (!info->wave_events.empty ()) | 
|  | break; | 
|  | } | 
|  |  | 
|  | gdb_assert (info != nullptr); | 
|  | } | 
|  | else | 
|  | { | 
|  | inferior *inf = find_inferior_pid (target, pid); | 
|  |  | 
|  | gdb_assert (inf != nullptr); | 
|  | info = get_amd_dbgapi_inferior_info (inf); | 
|  | } | 
|  |  | 
|  | if (info->wave_events.empty ()) | 
|  | return { minus_one_ptid, {} }; | 
|  |  | 
|  | auto event = info->wave_events.front (); | 
|  | info->wave_events.pop_front (); | 
|  |  | 
|  | return event; | 
|  | } | 
|  |  | 
|  | ptid_t | 
|  | amd_dbgapi_target::wait (ptid_t ptid, struct target_waitstatus *ws, | 
|  | target_wait_flags target_options) | 
|  | { | 
|  | gdb_assert (!current_inferior ()->process_target ()->commit_resumed_state); | 
|  | gdb_assert (ptid == minus_one_ptid || ptid.is_pid ()); | 
|  |  | 
|  | amd_dbgapi_debug_printf ("ptid = %s", ptid.to_string ().c_str ()); | 
|  |  | 
|  | ptid_t event_ptid = beneath ()->wait (ptid, ws, target_options); | 
|  | if (event_ptid != minus_one_ptid) | 
|  | { | 
|  | if (ws->kind () == TARGET_WAITKIND_EXITED | 
|  | || ws->kind () == TARGET_WAITKIND_SIGNALLED) | 
|  | { | 
|  | /* This inferior has exited so drain its dbgapi event queue.  */ | 
|  | while (consume_one_event (event_ptid.pid ()).first | 
|  | != minus_one_ptid) | 
|  | ; | 
|  | } | 
|  | return event_ptid; | 
|  | } | 
|  |  | 
|  | gdb_assert (ws->kind () == TARGET_WAITKIND_NO_RESUMED | 
|  | || ws->kind () == TARGET_WAITKIND_IGNORE); | 
|  |  | 
|  | /* Flush the async handler first.  */ | 
|  | if (target_is_async_p ()) | 
|  | async_event_handler_clear (); | 
|  |  | 
|  | /* There may be more events to process (either already in `wave_events` or | 
|  | that we need to fetch from dbgapi.  Mark the async event handler so that | 
|  | amd_dbgapi_target::wait gets called again and again, until it eventually | 
|  | returns minus_one_ptid.  */ | 
|  | auto more_events = make_scope_exit ([] () | 
|  | { | 
|  | if (target_is_async_p ()) | 
|  | async_event_handler_mark (); | 
|  | }); | 
|  |  | 
|  | auto *proc_target = current_inferior ()->process_target (); | 
|  |  | 
|  | /* Disable forward progress for the specified pid in ptid if it isn't | 
|  | minus_on_ptid, or all attached processes if ptid is minus_one_ptid.  */ | 
|  | require_forward_progress (ptid, proc_target, false); | 
|  |  | 
|  | target_waitstatus gpu_waitstatus; | 
|  | std::tie (event_ptid, gpu_waitstatus) = consume_one_event (ptid.pid ()); | 
|  | if (event_ptid == minus_one_ptid) | 
|  | { | 
|  | /* Drain the events for the current inferior from the amd_dbgapi and | 
|  | preserve the ordering.  */ | 
|  | auto info = get_amd_dbgapi_inferior_info (current_inferior ()); | 
|  | process_event_queue (info->process_id, AMD_DBGAPI_EVENT_KIND_NONE); | 
|  |  | 
|  | std::tie (event_ptid, gpu_waitstatus) = consume_one_event (ptid.pid ()); | 
|  | if (event_ptid == minus_one_ptid) | 
|  | { | 
|  | /* If we requested a specific ptid, and nothing came out, assume | 
|  | another ptid may have more events, otherwise, keep the | 
|  | async_event_handler flushed.  */ | 
|  | if (ptid == minus_one_ptid) | 
|  | more_events.release (); | 
|  |  | 
|  | if (ws->kind () == TARGET_WAITKIND_NO_RESUMED) | 
|  | { | 
|  | /* We can't easily check that all GPU waves are stopped, and no | 
|  | new waves can be created (the GPU has fixed function hardware | 
|  | to create new threads), so even if the target beneath returns | 
|  | waitkind_no_resumed, we have to report waitkind_ignore if GPU | 
|  | debugging is enabled for at least one resumed inferior handled | 
|  | by the amd-dbgapi target.  */ | 
|  |  | 
|  | for (inferior *inf : all_inferiors ()) | 
|  | if (inf->target_at (arch_stratum) == &the_amd_dbgapi_target | 
|  | && get_amd_dbgapi_inferior_info (inf)->runtime_state | 
|  | == AMD_DBGAPI_RUNTIME_STATE_LOADED_SUCCESS) | 
|  | { | 
|  | ws->set_ignore (); | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | /* There are no events to report, return the target beneath's | 
|  | waitstatus (either IGNORE or NO_RESUMED).  */ | 
|  | return minus_one_ptid; | 
|  | } | 
|  | } | 
|  |  | 
|  | *ws = gpu_waitstatus; | 
|  | return event_ptid; | 
|  | } | 
|  |  | 
|  | bool | 
|  | amd_dbgapi_target::stopped_by_sw_breakpoint () | 
|  | { | 
|  | if (!ptid_is_gpu (inferior_ptid)) | 
|  | return beneath ()->stopped_by_sw_breakpoint (); | 
|  |  | 
|  | amd_dbgapi_wave_id_t wave_id = get_amd_dbgapi_wave_id (inferior_ptid); | 
|  |  | 
|  | amd_dbgapi_wave_stop_reasons_t stop_reason; | 
|  | amd_dbgapi_status_t status | 
|  | = amd_dbgapi_wave_get_info (wave_id, AMD_DBGAPI_WAVE_INFO_STOP_REASON, | 
|  | sizeof (stop_reason), &stop_reason); | 
|  |  | 
|  | if (status != AMD_DBGAPI_STATUS_SUCCESS) | 
|  | return false; | 
|  |  | 
|  | return (stop_reason & AMD_DBGAPI_WAVE_STOP_REASON_BREAKPOINT) != 0; | 
|  | } | 
|  |  | 
|  | bool | 
|  | amd_dbgapi_target::stopped_by_hw_breakpoint () | 
|  | { | 
|  | if (!ptid_is_gpu (inferior_ptid)) | 
|  | return beneath ()->stopped_by_hw_breakpoint (); | 
|  |  | 
|  | return false; | 
|  | } | 
|  |  | 
|  | /* Set the process' memory access reporting precision mode. | 
|  |  | 
|  | Warn if the requested mode is not supported on at least one agent in the | 
|  | process. | 
|  |  | 
|  | Error out if setting the requested mode failed for some other reason.  */ | 
|  |  | 
|  | static void | 
|  | set_process_memory_precision (amd_dbgapi_inferior_info &info) | 
|  | { | 
|  | auto mode = (info.precise_memory.requested | 
|  | ? AMD_DBGAPI_MEMORY_PRECISION_PRECISE | 
|  | : AMD_DBGAPI_MEMORY_PRECISION_NONE); | 
|  | amd_dbgapi_status_t status | 
|  | = amd_dbgapi_set_memory_precision (info.process_id, mode); | 
|  |  | 
|  | if (status == AMD_DBGAPI_STATUS_SUCCESS) | 
|  | info.precise_memory.enabled = info.precise_memory.requested; | 
|  | else if (status == AMD_DBGAPI_STATUS_ERROR_NOT_SUPPORTED) | 
|  | warning (_("AMDGPU precise memory access reporting could not be enabled.")); | 
|  | else if (status != AMD_DBGAPI_STATUS_SUCCESS) | 
|  | error (_("amd_dbgapi_set_memory_precision failed (%s)"), | 
|  | get_status_string (status)); | 
|  | } | 
|  |  | 
|  | /* Make the amd-dbgapi library attach to the process behind INF. | 
|  |  | 
|  | Note that this is unrelated to the "attach" GDB concept / command. | 
|  |  | 
|  | By attaching to the process, we get a notifier fd that tells us when it | 
|  | activates the ROCm runtime and when there are subsequent debug events.  */ | 
|  |  | 
|  | static void | 
|  | attach_amd_dbgapi (inferior *inf) | 
|  | { | 
|  | AMD_DBGAPI_SCOPED_DEBUG_START_END ("inf num = %d", inf->num); | 
|  |  | 
|  | if (!target_can_async_p ()) | 
|  | { | 
|  | warning (_("The amd-dbgapi target requires the target beneath to be " | 
|  | "asynchronous, GPU debugging is disabled")); | 
|  | return; | 
|  | } | 
|  |  | 
|  | /* dbgapi can't attach to a vfork child (a process born from a vfork that | 
|  | hasn't exec'ed yet) while we are still attached to the parent.  It would | 
|  | not be useful for us to attach to vfork children anyway, because vfork | 
|  | children are very restricted in what they can do (see vfork(2)) and aren't | 
|  | going to launch some GPU programs that we need to debug.  To avoid this | 
|  | problem, we don't push the amd-dbgapi target / attach dbgapi in vfork | 
|  | children.  If a vfork child execs, we'll try enabling the amd-dbgapi target | 
|  | through the inferior_execd observer.  */ | 
|  | if (inf->vfork_parent != nullptr) | 
|  | return; | 
|  |  | 
|  | auto *info = get_amd_dbgapi_inferior_info (inf); | 
|  |  | 
|  | /* Are we already attached?  */ | 
|  | if (info->process_id != AMD_DBGAPI_PROCESS_NONE) | 
|  | { | 
|  | amd_dbgapi_debug_printf | 
|  | ("already attached: process_id = %" PRIu64, info->process_id.handle); | 
|  | return; | 
|  | } | 
|  |  | 
|  | amd_dbgapi_status_t status | 
|  | = amd_dbgapi_process_attach | 
|  | (reinterpret_cast<amd_dbgapi_client_process_id_t> (inf), | 
|  | &info->process_id); | 
|  | if (status == AMD_DBGAPI_STATUS_ERROR_RESTRICTION) | 
|  | { | 
|  | warning (_("amd-dbgapi: unable to enable GPU debugging due to a " | 
|  | "restriction error")); | 
|  | return; | 
|  | } | 
|  | else if (status != AMD_DBGAPI_STATUS_SUCCESS) | 
|  | { | 
|  | warning (_("amd-dbgapi: could not attach to process %d (%s), GPU " | 
|  | "debugging will not be available."), inf->pid, | 
|  | get_status_string (status)); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (amd_dbgapi_process_get_info (info->process_id, | 
|  | AMD_DBGAPI_PROCESS_INFO_NOTIFIER, | 
|  | sizeof (info->notifier), &info->notifier) | 
|  | != AMD_DBGAPI_STATUS_SUCCESS) | 
|  | { | 
|  | amd_dbgapi_process_detach (info->process_id); | 
|  | info->process_id = AMD_DBGAPI_PROCESS_NONE; | 
|  | warning (_("amd-dbgapi: could not retrieve process %d's notifier, GPU " | 
|  | "debugging will not be available."), inf->pid); | 
|  | return; | 
|  | } | 
|  |  | 
|  | amd_dbgapi_debug_printf ("process_id = %" PRIu64 ", notifier fd = %d", | 
|  | info->process_id.handle, info->notifier); | 
|  |  | 
|  | set_process_memory_precision (*info); | 
|  |  | 
|  | /* If GDB is attaching to a process that has the runtime loaded, there will | 
|  | already be a "runtime loaded" event available.  Consume it and push the | 
|  | target.  */ | 
|  | dbgapi_notifier_handler (0, info); | 
|  |  | 
|  | add_file_handler (info->notifier, dbgapi_notifier_handler, info, | 
|  | "amd-dbgapi notifier"); | 
|  | } | 
|  |  | 
|  | static void maybe_reset_amd_dbgapi (); | 
|  |  | 
|  | /* Make the amd-dbgapi library detach from INF. | 
|  |  | 
|  | Note that this us unrelated to the "detach" GDB concept / command. | 
|  |  | 
|  | This undoes what attach_amd_dbgapi does.  */ | 
|  |  | 
|  | static void | 
|  | detach_amd_dbgapi (inferior *inf) | 
|  | { | 
|  | AMD_DBGAPI_SCOPED_DEBUG_START_END ("inf num = %d", inf->num); | 
|  |  | 
|  | auto *info = get_amd_dbgapi_inferior_info (inf); | 
|  |  | 
|  | if (info->process_id == AMD_DBGAPI_PROCESS_NONE) | 
|  | return; | 
|  |  | 
|  | info->runtime_state = AMD_DBGAPI_RUNTIME_STATE_UNLOADED; | 
|  |  | 
|  | amd_dbgapi_status_t status = amd_dbgapi_process_detach (info->process_id); | 
|  | if (status != AMD_DBGAPI_STATUS_SUCCESS) | 
|  | warning (_("amd-dbgapi: could not detach from process %d (%s)"), | 
|  | inf->pid, get_status_string (status)); | 
|  |  | 
|  | gdb_assert (info->notifier != -1); | 
|  | delete_file_handler (info->notifier); | 
|  |  | 
|  | /* This is a noop if the target is not pushed.  */ | 
|  | inf->unpush_target (&the_amd_dbgapi_target); | 
|  |  | 
|  | /* Delete the breakpoints that are still active.  */ | 
|  | for (auto &&value : info->breakpoint_map) | 
|  | delete_breakpoint (value.second); | 
|  |  | 
|  | /* Reset the amd_dbgapi_inferior_info, except for precise_memory_mode.  */ | 
|  | *info = amd_dbgapi_inferior_info (inf, info->precise_memory.requested); | 
|  |  | 
|  | maybe_reset_amd_dbgapi (); | 
|  | } | 
|  |  | 
|  | void | 
|  | amd_dbgapi_target::mourn_inferior () | 
|  | { | 
|  | detach_amd_dbgapi (current_inferior ()); | 
|  | beneath ()->mourn_inferior (); | 
|  | } | 
|  |  | 
|  | void | 
|  | amd_dbgapi_target::detach (inferior *inf, int from_tty) | 
|  | { | 
|  | /* We're about to resume the waves by detaching the dbgapi library from the | 
|  | inferior, so we need to remove all breakpoints that are still inserted. | 
|  |  | 
|  | Breakpoints may still be inserted because the inferior may be running in | 
|  | non-stop mode, or because GDB changed the default setting to leave all | 
|  | breakpoints inserted in all-stop mode when all threads are stopped.  */ | 
|  | remove_breakpoints_inf (inf); | 
|  |  | 
|  | detach_amd_dbgapi (inf); | 
|  | beneath ()->detach (inf, from_tty); | 
|  | } | 
|  |  | 
|  | void | 
|  | amd_dbgapi_target::fetch_registers (struct regcache *regcache, int regno) | 
|  | { | 
|  | if (!ptid_is_gpu (regcache->ptid ())) | 
|  | { | 
|  | beneath ()->fetch_registers (regcache, regno); | 
|  | return; | 
|  | } | 
|  |  | 
|  | struct gdbarch *gdbarch = regcache->arch (); | 
|  | gdb_assert (is_amdgpu_arch (gdbarch)); | 
|  |  | 
|  | amdgpu_gdbarch_tdep *tdep = get_amdgpu_gdbarch_tdep (gdbarch); | 
|  | amd_dbgapi_wave_id_t wave_id = get_amd_dbgapi_wave_id (regcache->ptid ()); | 
|  | gdb_byte raw[AMDGPU_MAX_REGISTER_SIZE]; | 
|  | amd_dbgapi_status_t status | 
|  | = amd_dbgapi_read_register (wave_id, tdep->register_ids[regno], 0, | 
|  | register_type (gdbarch, regno)->length (), | 
|  | raw); | 
|  |  | 
|  | if (status == AMD_DBGAPI_STATUS_SUCCESS) | 
|  | regcache->raw_supply (regno, raw); | 
|  | else if (status != AMD_DBGAPI_STATUS_ERROR_REGISTER_NOT_AVAILABLE) | 
|  | warning (_("Couldn't read register %s (#%d) (%s)."), | 
|  | gdbarch_register_name (gdbarch, regno), regno, | 
|  | get_status_string (status)); | 
|  | } | 
|  |  | 
|  | void | 
|  | amd_dbgapi_target::store_registers (struct regcache *regcache, int regno) | 
|  | { | 
|  | if (!ptid_is_gpu (regcache->ptid ())) | 
|  | { | 
|  | beneath ()->store_registers (regcache, regno); | 
|  | return; | 
|  | } | 
|  |  | 
|  | struct gdbarch *gdbarch = regcache->arch (); | 
|  | gdb_assert (is_amdgpu_arch (gdbarch)); | 
|  |  | 
|  | gdb_byte raw[AMDGPU_MAX_REGISTER_SIZE]; | 
|  | regcache->raw_collect (regno, &raw); | 
|  |  | 
|  | amdgpu_gdbarch_tdep *tdep = get_amdgpu_gdbarch_tdep (gdbarch); | 
|  |  | 
|  | /* If the register has read-only bits, invalidate the value in the regcache | 
|  | as the value actually written may differ.  */ | 
|  | if (tdep->register_properties[regno] | 
|  | & AMD_DBGAPI_REGISTER_PROPERTY_READONLY_BITS) | 
|  | regcache->invalidate (regno); | 
|  |  | 
|  | /* Invalidate all volatile registers if this register has the invalidate | 
|  | volatile property.  For example, writing to VCC may change the content | 
|  | of STATUS.VCCZ.  */ | 
|  | if (tdep->register_properties[regno] | 
|  | & AMD_DBGAPI_REGISTER_PROPERTY_INVALIDATE_VOLATILE) | 
|  | { | 
|  | for (size_t r = 0; r < tdep->register_properties.size (); ++r) | 
|  | if (tdep->register_properties[r] & AMD_DBGAPI_REGISTER_PROPERTY_VOLATILE) | 
|  | regcache->invalidate (r); | 
|  | } | 
|  |  | 
|  | amd_dbgapi_wave_id_t wave_id = get_amd_dbgapi_wave_id (regcache->ptid ()); | 
|  | amd_dbgapi_status_t status | 
|  | = amd_dbgapi_write_register (wave_id, tdep->register_ids[regno], 0, | 
|  | register_type (gdbarch, regno)->length (), | 
|  | raw); | 
|  |  | 
|  | if (status != AMD_DBGAPI_STATUS_SUCCESS) | 
|  | warning (_("Couldn't write register %s (#%d)."), | 
|  | gdbarch_register_name (gdbarch, regno), regno); | 
|  | } | 
|  |  | 
|  | struct gdbarch * | 
|  | amd_dbgapi_target::thread_architecture (ptid_t ptid) | 
|  | { | 
|  | if (!ptid_is_gpu (ptid)) | 
|  | return beneath ()->thread_architecture (ptid); | 
|  |  | 
|  | /* We can cache the gdbarch for a given wave_id (ptid::tid) because | 
|  | wave IDs are unique, and aren't reused.  */ | 
|  | if (ptid.tid () == m_cached_arch_tid) | 
|  | return m_cached_arch; | 
|  |  | 
|  | amd_dbgapi_wave_id_t wave_id = get_amd_dbgapi_wave_id (ptid); | 
|  | amd_dbgapi_architecture_id_t architecture_id; | 
|  | amd_dbgapi_status_t status; | 
|  |  | 
|  | status = amd_dbgapi_wave_get_info (wave_id, AMD_DBGAPI_WAVE_INFO_ARCHITECTURE, | 
|  | sizeof (architecture_id), | 
|  | &architecture_id); | 
|  | if (status != AMD_DBGAPI_STATUS_SUCCESS) | 
|  | error (_("Couldn't get architecture for wave_%ld"), ptid.tid ()); | 
|  |  | 
|  | uint32_t elf_amdgpu_machine; | 
|  | status = amd_dbgapi_architecture_get_info | 
|  | (architecture_id, AMD_DBGAPI_ARCHITECTURE_INFO_ELF_AMDGPU_MACHINE, | 
|  | sizeof (elf_amdgpu_machine), &elf_amdgpu_machine); | 
|  | if (status != AMD_DBGAPI_STATUS_SUCCESS) | 
|  | error (_("Couldn't get elf_amdgpu_machine for architecture_%ld"), | 
|  | architecture_id.handle); | 
|  |  | 
|  | struct gdbarch_info info; | 
|  | info.bfd_arch_info = bfd_lookup_arch (bfd_arch_amdgcn, elf_amdgpu_machine); | 
|  | info.byte_order = BFD_ENDIAN_LITTLE; | 
|  |  | 
|  | m_cached_arch_tid = ptid.tid (); | 
|  | m_cached_arch = gdbarch_find_by_info (info); | 
|  | if (m_cached_arch == nullptr) | 
|  | error (_("Couldn't get elf_amdgpu_machine (%#x)"), elf_amdgpu_machine); | 
|  |  | 
|  | return m_cached_arch; | 
|  | } | 
|  |  | 
|  | void | 
|  | amd_dbgapi_target::thread_events (int enable) | 
|  | { | 
|  | m_report_thread_events = enable; | 
|  | beneath ()->thread_events (enable); | 
|  | } | 
|  |  | 
|  | void | 
|  | amd_dbgapi_target::update_thread_list () | 
|  | { | 
|  | for (inferior *inf : all_inferiors ()) | 
|  | { | 
|  | amd_dbgapi_process_id_t process_id | 
|  | = get_amd_dbgapi_process_id (inf); | 
|  | if (process_id == AMD_DBGAPI_PROCESS_NONE) | 
|  | { | 
|  | /* The inferior may not be attached yet.  */ | 
|  | continue; | 
|  | } | 
|  |  | 
|  | size_t count; | 
|  | amd_dbgapi_wave_id_t *wave_list; | 
|  | amd_dbgapi_changed_t changed; | 
|  | amd_dbgapi_status_t status | 
|  | = amd_dbgapi_process_wave_list (process_id, &count, &wave_list, | 
|  | &changed); | 
|  | if (status != AMD_DBGAPI_STATUS_SUCCESS) | 
|  | error (_("amd_dbgapi_wave_list failed (%s)"), | 
|  | get_status_string (status)); | 
|  |  | 
|  | if (changed == AMD_DBGAPI_CHANGED_NO) | 
|  | continue; | 
|  |  | 
|  | /* Create a set and free the wave list.  */ | 
|  | std::set<ptid_t::tid_type> threads; | 
|  | for (size_t i = 0; i < count; ++i) | 
|  | threads.emplace (wave_list[i].handle); | 
|  |  | 
|  | xfree (wave_list); | 
|  |  | 
|  | /* Prune the wave_ids that already have a thread_info.  Any thread_info | 
|  | which does not have a corresponding wave_id represents a wave which | 
|  | is gone at this point and should be deleted.  */ | 
|  | for (thread_info *tp : inf->threads_safe ()) | 
|  | if (ptid_is_gpu (tp->ptid) && tp->state != THREAD_EXITED) | 
|  | { | 
|  | auto it = threads.find (tp->ptid.tid ()); | 
|  |  | 
|  | if (it == threads.end ()) | 
|  | { | 
|  | auto wave_id = get_amd_dbgapi_wave_id (tp->ptid); | 
|  | wave_info &wi = get_thread_wave_info (tp); | 
|  |  | 
|  | /* Waves that were stepping or in progress of being | 
|  | stopped are guaranteed to report a | 
|  | WAVE_COMMAND_TERMINATED event if they terminate. | 
|  | Don't delete such threads until we see the | 
|  | event.  */ | 
|  | if (wi.last_resume_mode == AMD_DBGAPI_RESUME_MODE_SINGLE_STEP | 
|  | || wi.stopping) | 
|  | { | 
|  | amd_dbgapi_debug_printf | 
|  | ("wave_%ld disappeared, keeping it" | 
|  | " (last_resume_mode=%s, stopping=%d)", | 
|  | wave_id.handle, | 
|  | resume_mode_to_string (wi.last_resume_mode), | 
|  | wi.stopping); | 
|  | } | 
|  | else | 
|  | { | 
|  | amd_dbgapi_debug_printf ("wave_%ld disappeared, deleting it", | 
|  | wave_id.handle); | 
|  | delete_thread_silent (tp); | 
|  | } | 
|  | } | 
|  | else | 
|  | threads.erase (it); | 
|  | } | 
|  |  | 
|  | /* The wave_ids that are left require a new thread_info.  */ | 
|  | for (ptid_t::tid_type tid : threads) | 
|  | { | 
|  | ptid_t wave_ptid | 
|  | = make_gpu_ptid (inf->pid, amd_dbgapi_wave_id_t {tid}); | 
|  | add_gpu_thread (inf, wave_ptid); | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Give the beneath target a chance to do extra processing.  */ | 
|  | this->beneath ()->update_thread_list (); | 
|  | } | 
|  |  | 
|  | /* inferior_created observer.  */ | 
|  |  | 
|  | static void | 
|  | amd_dbgapi_target_inferior_created (inferior *inf) | 
|  | { | 
|  | /* If the inferior is not running on the native target (e.g. it is running | 
|  | on a remote target), we don't want to deal with it.  */ | 
|  | if (inf->process_target () != get_native_target ()) | 
|  | return; | 
|  |  | 
|  | attach_amd_dbgapi (inf); | 
|  | } | 
|  |  | 
|  | /* Callback called when an inferior is cloned.  */ | 
|  |  | 
|  | static void | 
|  | amd_dbgapi_target_inferior_cloned (inferior *original_inferior, | 
|  | inferior *new_inferior) | 
|  | { | 
|  | auto *orig_info = get_amd_dbgapi_inferior_info (original_inferior); | 
|  | auto *new_info = get_amd_dbgapi_inferior_info (new_inferior); | 
|  |  | 
|  | /* At this point, the process is not started.  Therefore it is sufficient to | 
|  | copy the precise memory request, it will be applied when the process | 
|  | starts.  */ | 
|  | gdb_assert (new_info->process_id == AMD_DBGAPI_PROCESS_NONE); | 
|  | new_info->precise_memory.requested = orig_info->precise_memory.requested; | 
|  | } | 
|  |  | 
|  | /* inferior_execd observer.  */ | 
|  |  | 
|  | static void | 
|  | amd_dbgapi_inferior_execd (inferior *exec_inf, inferior *follow_inf) | 
|  | { | 
|  | /* The inferior has EXEC'd and the process image has changed.  The dbgapi is | 
|  | attached to the old process image, so we need to detach and re-attach to | 
|  | the new process image.  */ | 
|  | detach_amd_dbgapi (exec_inf); | 
|  |  | 
|  | /* If using "follow-exec-mode new", carry over the precise-memory setting | 
|  | to the new inferior (otherwise, FOLLOW_INF and ORIG_INF point to the same | 
|  | inferior, so this is a no-op).  */ | 
|  | get_amd_dbgapi_inferior_info (follow_inf)->precise_memory.requested | 
|  | = get_amd_dbgapi_inferior_info (exec_inf)->precise_memory.requested; | 
|  |  | 
|  | attach_amd_dbgapi (follow_inf); | 
|  | } | 
|  |  | 
|  | /* inferior_forked observer.  */ | 
|  |  | 
|  | static void | 
|  | amd_dbgapi_inferior_forked (inferior *parent_inf, inferior *child_inf, | 
|  | target_waitkind fork_kind) | 
|  | { | 
|  | if (child_inf != nullptr) | 
|  | { | 
|  | /* Copy precise-memory requested value from parent to child.  */ | 
|  | amd_dbgapi_inferior_info *parent_info | 
|  | = get_amd_dbgapi_inferior_info (parent_inf); | 
|  | amd_dbgapi_inferior_info *child_info | 
|  | = get_amd_dbgapi_inferior_info (child_inf); | 
|  | child_info->precise_memory.requested | 
|  | = parent_info->precise_memory.requested; | 
|  |  | 
|  | if (fork_kind != TARGET_WAITKIND_VFORKED) | 
|  | { | 
|  | scoped_restore_current_thread restore_thread; | 
|  | switch_to_thread (*child_inf->threads ().begin ()); | 
|  | attach_amd_dbgapi (child_inf); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | /* inferior_exit observer. | 
|  |  | 
|  | This covers normal exits, but also detached inferiors (including detached | 
|  | fork parents).  */ | 
|  |  | 
|  | static void | 
|  | amd_dbgapi_inferior_exited (inferior *inf) | 
|  | { | 
|  | detach_amd_dbgapi (inf); | 
|  | } | 
|  |  | 
|  | /* inferior_pre_detach observer.  */ | 
|  |  | 
|  | static void | 
|  | amd_dbgapi_inferior_pre_detach (inferior *inf) | 
|  | { | 
|  | /* We need to amd-dbgapi-detach before we ptrace-detach.  If the amd-dbgapi | 
|  | target isn't pushed, do that now.  If the amd-dbgapi target is pushed, | 
|  | we'll do it in amd_dbgapi_target::detach.  */ | 
|  | if (!inf->target_is_pushed (&the_amd_dbgapi_target)) | 
|  | detach_amd_dbgapi (inf); | 
|  | } | 
|  |  | 
|  | /* get_os_pid callback.  */ | 
|  |  | 
|  | static amd_dbgapi_status_t | 
|  | amd_dbgapi_get_os_pid_callback | 
|  | (amd_dbgapi_client_process_id_t client_process_id, pid_t *pid) | 
|  | { | 
|  | inferior *inf = reinterpret_cast<inferior *> (client_process_id); | 
|  |  | 
|  | if (inf->pid == 0) | 
|  | return AMD_DBGAPI_STATUS_ERROR_PROCESS_EXITED; | 
|  |  | 
|  | *pid = inf->pid; | 
|  | return AMD_DBGAPI_STATUS_SUCCESS; | 
|  | } | 
|  |  | 
|  | /* insert_breakpoint callback.  */ | 
|  |  | 
|  | static amd_dbgapi_status_t | 
|  | amd_dbgapi_insert_breakpoint_callback | 
|  | (amd_dbgapi_client_process_id_t client_process_id, | 
|  | amd_dbgapi_global_address_t address, | 
|  | amd_dbgapi_breakpoint_id_t breakpoint_id) | 
|  | { | 
|  | inferior *inf = reinterpret_cast<inferior *> (client_process_id); | 
|  | struct amd_dbgapi_inferior_info *info = get_amd_dbgapi_inferior_info (inf); | 
|  |  | 
|  | auto it = info->breakpoint_map.find (breakpoint_id.handle); | 
|  | if (it != info->breakpoint_map.end ()) | 
|  | return AMD_DBGAPI_STATUS_ERROR_INVALID_BREAKPOINT_ID; | 
|  |  | 
|  | /* We need to find the address in the given inferior's program space.  */ | 
|  | scoped_restore_current_thread restore_thread; | 
|  | switch_to_inferior_no_thread (inf); | 
|  |  | 
|  | /* Create a new breakpoint.  */ | 
|  | struct obj_section *section = find_pc_section (address); | 
|  | if (section == nullptr || section->objfile == nullptr) | 
|  | return AMD_DBGAPI_STATUS_ERROR; | 
|  |  | 
|  | std::unique_ptr<breakpoint> bp_up | 
|  | (new amd_dbgapi_target_breakpoint (section->objfile->arch (), address)); | 
|  |  | 
|  | breakpoint *bp = install_breakpoint (true, std::move (bp_up), 1); | 
|  |  | 
|  | info->breakpoint_map.emplace (breakpoint_id.handle, bp); | 
|  | return AMD_DBGAPI_STATUS_SUCCESS; | 
|  | } | 
|  |  | 
|  | /* remove_breakpoint callback.  */ | 
|  |  | 
|  | static amd_dbgapi_status_t | 
|  | amd_dbgapi_remove_breakpoint_callback | 
|  | (amd_dbgapi_client_process_id_t client_process_id, | 
|  | amd_dbgapi_breakpoint_id_t breakpoint_id) | 
|  | { | 
|  | inferior *inf = reinterpret_cast<inferior *> (client_process_id); | 
|  | struct amd_dbgapi_inferior_info *info = get_amd_dbgapi_inferior_info (inf); | 
|  |  | 
|  | auto it = info->breakpoint_map.find (breakpoint_id.handle); | 
|  | if (it == info->breakpoint_map.end ()) | 
|  | return AMD_DBGAPI_STATUS_ERROR_INVALID_BREAKPOINT_ID; | 
|  |  | 
|  | delete_breakpoint (it->second); | 
|  | info->breakpoint_map.erase (it); | 
|  |  | 
|  | return AMD_DBGAPI_STATUS_SUCCESS; | 
|  | } | 
|  |  | 
|  | /* signal_received observer.  */ | 
|  |  | 
|  | static void | 
|  | amd_dbgapi_target_signal_received (gdb_signal sig) | 
|  | { | 
|  | amd_dbgapi_inferior_info *info | 
|  | = get_amd_dbgapi_inferior_info (current_inferior ()); | 
|  |  | 
|  | if (info->process_id == AMD_DBGAPI_PROCESS_NONE) | 
|  | return; | 
|  |  | 
|  | if (!ptid_is_gpu (inferior_thread ()->ptid)) | 
|  | return; | 
|  |  | 
|  | if (sig != GDB_SIGNAL_SEGV && sig != GDB_SIGNAL_BUS) | 
|  | return; | 
|  |  | 
|  | if (!info->precise_memory.enabled) | 
|  | gdb_printf (_("\ | 
|  | Warning: precise memory violation signal reporting is not enabled, reported\n\ | 
|  | location may not be accurate.  See \"show amdgpu precise-memory\".\n")); | 
|  | } | 
|  |  | 
|  | /* Style for some kinds of messages.  */ | 
|  |  | 
|  | static cli_style_option fatal_error_style | 
|  | ("amd_dbgapi_fatal_error", ui_file_style::RED); | 
|  | static cli_style_option warning_style | 
|  | ("amd_dbgapi_warning", ui_file_style::YELLOW); | 
|  |  | 
|  | /* BLACK + BOLD means dark gray.  */ | 
|  | static cli_style_option trace_style | 
|  | ("amd_dbgapi_trace", ui_file_style::BLACK, ui_file_style::BOLD); | 
|  |  | 
|  | /* log_message callback.  */ | 
|  |  | 
|  | static void | 
|  | amd_dbgapi_log_message_callback (amd_dbgapi_log_level_t level, | 
|  | const char *message) | 
|  | { | 
|  | std::optional<target_terminal::scoped_restore_terminal_state> tstate; | 
|  |  | 
|  | if (target_supports_terminal_ours ()) | 
|  | { | 
|  | tstate.emplace (); | 
|  | target_terminal::ours_for_output (); | 
|  | } | 
|  |  | 
|  | /* Error and warning messages are meant to be printed to the user.  */ | 
|  | if (level == AMD_DBGAPI_LOG_LEVEL_FATAL_ERROR | 
|  | || level == AMD_DBGAPI_LOG_LEVEL_WARNING) | 
|  | { | 
|  | begin_line (); | 
|  | ui_file_style style = (level == AMD_DBGAPI_LOG_LEVEL_FATAL_ERROR | 
|  | ? fatal_error_style : warning_style).style (); | 
|  | gdb_printf (gdb_stderr, "%ps\n", styled_string (style, message)); | 
|  | return; | 
|  | } | 
|  |  | 
|  | /* Print other messages as debug logs.  TRACE and VERBOSE messages are | 
|  | very verbose, print them dark grey so it's easier to spot other messages | 
|  | through the flood.  */ | 
|  | if (level >= AMD_DBGAPI_LOG_LEVEL_TRACE) | 
|  | { | 
|  | debug_prefixed_printf (amd_dbgapi_lib_debug_module (), nullptr, "%ps", | 
|  | styled_string (trace_style.style (), message)); | 
|  | return; | 
|  | } | 
|  |  | 
|  | debug_prefixed_printf (amd_dbgapi_lib_debug_module (), nullptr, "%s", | 
|  | message); | 
|  | } | 
|  |  | 
|  | /* Callbacks passed to amd_dbgapi_initialize.  */ | 
|  |  | 
|  | static amd_dbgapi_callbacks_t dbgapi_callbacks = { | 
|  | .allocate_memory = malloc, | 
|  | .deallocate_memory = free, | 
|  | .get_os_pid = amd_dbgapi_get_os_pid_callback, | 
|  | .insert_breakpoint = amd_dbgapi_insert_breakpoint_callback, | 
|  | .remove_breakpoint = amd_dbgapi_remove_breakpoint_callback, | 
|  | .log_message = amd_dbgapi_log_message_callback, | 
|  | }; | 
|  |  | 
|  | void | 
|  | amd_dbgapi_target::close () | 
|  | { | 
|  | if (amd_dbgapi_async_event_handler != nullptr) | 
|  | delete_async_event_handler (&amd_dbgapi_async_event_handler); | 
|  | } | 
|  |  | 
|  | /* Callback for "show amdgpu precise-memory".  */ | 
|  |  | 
|  | static void | 
|  | show_precise_memory_mode (struct ui_file *file, int from_tty, | 
|  | struct cmd_list_element *c, const char *value) | 
|  | { | 
|  | amd_dbgapi_inferior_info *info | 
|  | = get_amd_dbgapi_inferior_info (current_inferior ()); | 
|  |  | 
|  | gdb_printf (file, | 
|  | _("AMDGPU precise memory access reporting is %s " | 
|  | "(currently %s).\n"), | 
|  | info->precise_memory.requested ? "on" : "off", | 
|  | info->precise_memory.enabled ? "enabled" : "disabled"); | 
|  | } | 
|  |  | 
|  | /* Callback for "set amdgpu precise-memory".  */ | 
|  |  | 
|  | static void | 
|  | set_precise_memory_mode (bool value) | 
|  | { | 
|  | amd_dbgapi_inferior_info *info | 
|  | = get_amd_dbgapi_inferior_info (current_inferior ()); | 
|  |  | 
|  | info->precise_memory.requested = value; | 
|  |  | 
|  | if (info->process_id != AMD_DBGAPI_PROCESS_NONE) | 
|  | set_process_memory_precision (*info); | 
|  | } | 
|  |  | 
|  | /* Return whether precise-memory is requested for the current inferior.  */ | 
|  |  | 
|  | static bool | 
|  | get_precise_memory_mode () | 
|  | { | 
|  | amd_dbgapi_inferior_info *info | 
|  | = get_amd_dbgapi_inferior_info (current_inferior ()); | 
|  |  | 
|  | return info->precise_memory.requested; | 
|  | } | 
|  |  | 
|  | /* List of set/show amdgpu commands.  */ | 
|  | struct cmd_list_element *set_amdgpu_list; | 
|  | struct cmd_list_element *show_amdgpu_list; | 
|  |  | 
|  | /* List of set/show debug amd-dbgapi-lib commands.  */ | 
|  | struct cmd_list_element *set_debug_amd_dbgapi_lib_list; | 
|  | struct cmd_list_element *show_debug_amd_dbgapi_lib_list; | 
|  |  | 
|  | /* Mapping from amd-dbgapi log level enum values to text.  */ | 
|  |  | 
|  | static constexpr const char *debug_amd_dbgapi_lib_log_level_enums[] = | 
|  | { | 
|  | /* [AMD_DBGAPI_LOG_LEVEL_NONE] = */ "off", | 
|  | /* [AMD_DBGAPI_LOG_LEVEL_FATAL_ERROR] = */ "error", | 
|  | /* [AMD_DBGAPI_LOG_LEVEL_WARNING] = */ "warning", | 
|  | /* [AMD_DBGAPI_LOG_LEVEL_INFO] = */ "info", | 
|  | /* [AMD_DBGAPI_LOG_LEVEL_TRACE] = */ "trace", | 
|  | /* [AMD_DBGAPI_LOG_LEVEL_VERBOSE] = */ "verbose", | 
|  | nullptr | 
|  | }; | 
|  |  | 
|  | /* Storage for "set debug amd-dbgapi-lib log-level".  */ | 
|  |  | 
|  | static const char *debug_amd_dbgapi_lib_log_level | 
|  | = debug_amd_dbgapi_lib_log_level_enums[AMD_DBGAPI_LOG_LEVEL_WARNING]; | 
|  |  | 
|  | /* Get the amd-dbgapi library log level requested by the user.  */ | 
|  |  | 
|  | static amd_dbgapi_log_level_t | 
|  | get_debug_amd_dbgapi_lib_log_level () | 
|  | { | 
|  | for (size_t pos = 0; | 
|  | debug_amd_dbgapi_lib_log_level_enums[pos] != nullptr; | 
|  | ++pos) | 
|  | if (debug_amd_dbgapi_lib_log_level | 
|  | == debug_amd_dbgapi_lib_log_level_enums[pos]) | 
|  | return static_cast<amd_dbgapi_log_level_t> (pos); | 
|  |  | 
|  | gdb_assert_not_reached ("invalid log level"); | 
|  | } | 
|  |  | 
|  | /* Callback for "set debug amd-dbgapi log-level", apply the selected log level | 
|  | to the library.  */ | 
|  |  | 
|  | static void | 
|  | set_debug_amd_dbgapi_lib_log_level (const char *args, int from_tty, | 
|  | struct cmd_list_element *c) | 
|  | { | 
|  | amd_dbgapi_set_log_level (get_debug_amd_dbgapi_lib_log_level ()); | 
|  | } | 
|  |  | 
|  | /* Callback for "show debug amd-dbgapi log-level".  */ | 
|  |  | 
|  | static void | 
|  | show_debug_amd_dbgapi_lib_log_level (struct ui_file *file, int from_tty, | 
|  | struct cmd_list_element *c, | 
|  | const char *value) | 
|  | { | 
|  | gdb_printf (file, _("The amd-dbgapi library log level is %s.\n"), value); | 
|  | } | 
|  |  | 
|  | /* If the amd-dbgapi library is not attached to any process, finalize and | 
|  | re-initialize it so that the handle ID numbers will all start from the | 
|  | beginning again.  This is only for convenience, not essential.  */ | 
|  |  | 
|  | static void | 
|  | maybe_reset_amd_dbgapi () | 
|  | { | 
|  | for (inferior *inf : all_non_exited_inferiors ()) | 
|  | { | 
|  | amd_dbgapi_inferior_info *info = get_amd_dbgapi_inferior_info (inf); | 
|  |  | 
|  | if (info->process_id != AMD_DBGAPI_PROCESS_NONE) | 
|  | return; | 
|  | } | 
|  |  | 
|  | amd_dbgapi_status_t status = amd_dbgapi_finalize (); | 
|  | if (status != AMD_DBGAPI_STATUS_SUCCESS) | 
|  | error (_("amd-dbgapi failed to finalize (%s)"), | 
|  | get_status_string (status)); | 
|  |  | 
|  | status = amd_dbgapi_initialize (&dbgapi_callbacks); | 
|  | if (status != AMD_DBGAPI_STATUS_SUCCESS) | 
|  | error (_("amd-dbgapi failed to initialize (%s)"), | 
|  | get_status_string (status)); | 
|  | } | 
|  |  | 
|  | extern initialize_file_ftype _initialize_amd_dbgapi_target; | 
|  |  | 
|  | void | 
|  | _initialize_amd_dbgapi_target () | 
|  | { | 
|  | /* Make sure the loaded debugger library version is greater than or equal to | 
|  | the one used to build GDB.  */ | 
|  | uint32_t major, minor, patch; | 
|  | amd_dbgapi_get_version (&major, &minor, &patch); | 
|  | if (major != AMD_DBGAPI_VERSION_MAJOR || minor < AMD_DBGAPI_VERSION_MINOR) | 
|  | error (_("amd-dbgapi library version mismatch, got %d.%d.%d, need %d.%d+"), | 
|  | major, minor, patch, AMD_DBGAPI_VERSION_MAJOR, | 
|  | AMD_DBGAPI_VERSION_MINOR); | 
|  |  | 
|  | /* Initialize the AMD Debugger API.  */ | 
|  | amd_dbgapi_status_t status = amd_dbgapi_initialize (&dbgapi_callbacks); | 
|  | if (status != AMD_DBGAPI_STATUS_SUCCESS) | 
|  | error (_("amd-dbgapi failed to initialize (%s)"), | 
|  | get_status_string (status)); | 
|  |  | 
|  | /* Set the initial log level.  */ | 
|  | amd_dbgapi_set_log_level (get_debug_amd_dbgapi_lib_log_level ()); | 
|  |  | 
|  | /* Install observers.  */ | 
|  | gdb::observers::inferior_cloned.attach (amd_dbgapi_target_inferior_cloned, | 
|  | "amd-dbgapi"); | 
|  | gdb::observers::signal_received.attach (amd_dbgapi_target_signal_received, | 
|  | "amd-dbgapi"); | 
|  | gdb::observers::inferior_created.attach | 
|  | (amd_dbgapi_target_inferior_created, | 
|  | amd_dbgapi_target_inferior_created_observer_token, "amd-dbgapi"); | 
|  | gdb::observers::inferior_execd.attach (amd_dbgapi_inferior_execd, "amd-dbgapi"); | 
|  | gdb::observers::inferior_forked.attach (amd_dbgapi_inferior_forked, "amd-dbgapi"); | 
|  | gdb::observers::inferior_exit.attach (amd_dbgapi_inferior_exited, "amd-dbgapi"); | 
|  | gdb::observers::inferior_pre_detach.attach (amd_dbgapi_inferior_pre_detach, "amd-dbgapi"); | 
|  | gdb::observers::thread_deleted.attach (amd_dbgapi_thread_deleted, "amd-dbgapi"); | 
|  |  | 
|  | add_basic_prefix_cmd ("amdgpu", no_class, | 
|  | _("Generic command for setting amdgpu flags."), | 
|  | &set_amdgpu_list, 0, &setlist); | 
|  |  | 
|  | add_show_prefix_cmd ("amdgpu", no_class, | 
|  | _("Generic command for showing amdgpu flags."), | 
|  | &show_amdgpu_list, 0, &showlist); | 
|  |  | 
|  | add_setshow_boolean_cmd ("precise-memory", no_class, | 
|  | _("Set precise-memory mode."), | 
|  | _("Show precise-memory mode."), _("\ | 
|  | If on, precise memory reporting is enabled if/when the inferior is running.\n\ | 
|  | If off (default), precise memory reporting is disabled."), | 
|  | set_precise_memory_mode, | 
|  | get_precise_memory_mode, | 
|  | show_precise_memory_mode, | 
|  | &set_amdgpu_list, &show_amdgpu_list); | 
|  |  | 
|  | add_basic_prefix_cmd ("amd-dbgapi-lib", no_class, | 
|  | _("Generic command for setting amd-dbgapi library " | 
|  | "debugging flags."), | 
|  | &set_debug_amd_dbgapi_lib_list, 0, &setdebuglist); | 
|  |  | 
|  | add_show_prefix_cmd ("amd-dbgapi-lib", no_class, | 
|  | _("Generic command for showing amd-dbgapi library " | 
|  | "debugging flags."), | 
|  | &show_debug_amd_dbgapi_lib_list, 0, &showdebuglist); | 
|  |  | 
|  | add_setshow_enum_cmd ("log-level", class_maintenance, | 
|  | debug_amd_dbgapi_lib_log_level_enums, | 
|  | &debug_amd_dbgapi_lib_log_level, | 
|  | _("Set the amd-dbgapi library log level."), | 
|  | _("Show the amd-dbgapi library log level."), | 
|  | _("off     == no logging is enabled\n" | 
|  | "error   == fatal errors are reported\n" | 
|  | "warning == fatal errors and warnings are reported\n" | 
|  | "info    == fatal errors, warnings, and info " | 
|  | "messages are reported\n" | 
|  | "trace   == fatal errors, warnings, info, and " | 
|  | "API tracing messages are reported\n" | 
|  | "verbose == all messages are reported"), | 
|  | set_debug_amd_dbgapi_lib_log_level, | 
|  | show_debug_amd_dbgapi_lib_log_level, | 
|  | &set_debug_amd_dbgapi_lib_list, | 
|  | &show_debug_amd_dbgapi_lib_list); | 
|  |  | 
|  | add_setshow_boolean_cmd ("amd-dbgapi", class_maintenance, | 
|  | &debug_amd_dbgapi, | 
|  | _("Set debugging of amd-dbgapi target."), | 
|  | _("Show debugging of amd-dbgapi target."), | 
|  | _("\ | 
|  | When on, print debug messages relating to the amd-dbgapi target."), | 
|  | nullptr, nullptr, | 
|  | &setdebuglist, &showdebuglist); | 
|  | } |