| /* 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 "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 (bool 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_ADDRESS_ERROR; |
| 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_ADDRESS_ERROR) |
| 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 (bool 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); |
| } |
| |
| /* client_process_get_info callback. */ |
| |
| static amd_dbgapi_status_t |
| amd_dbgapi_client_process_get_info_callback |
| (amd_dbgapi_client_process_id_t client_process_id, |
| amd_dbgapi_client_process_info_t query, size_t value_size, void *value) |
| { |
| inferior *inf = reinterpret_cast<inferior *> (client_process_id); |
| |
| if (inf->pid == 0) |
| return AMD_DBGAPI_STATUS_ERROR_PROCESS_EXITED; |
| |
| if (value == nullptr) |
| return AMD_DBGAPI_STATUS_ERROR_INVALID_ARGUMENT; |
| |
| switch (query) |
| { |
| case AMD_DBGAPI_CLIENT_PROCESS_INFO_OS_PID: |
| if (value_size != sizeof (amd_dbgapi_os_process_id_t)) |
| return AMD_DBGAPI_STATUS_ERROR_INVALID_ARGUMENT_COMPATIBILITY; |
| |
| *static_cast<amd_dbgapi_os_process_id_t *> (value) = inf->pid; |
| return AMD_DBGAPI_STATUS_SUCCESS; |
| |
| case AMD_DBGAPI_CLIENT_PROCESS_INFO_CORE_STATE: |
| return AMD_DBGAPI_STATUS_ERROR_NOT_AVAILABLE; |
| } |
| |
| return AMD_DBGAPI_STATUS_ERROR_INVALID_ARGUMENT; |
| } |
| |
| /* 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; |
| } |
| |
| /* xfer_global_memory callback. */ |
| |
| static amd_dbgapi_status_t |
| amd_dbgapi_xfer_global_memory_callback |
| (amd_dbgapi_client_process_id_t client_process_id, |
| amd_dbgapi_global_address_t global_address, |
| amd_dbgapi_size_t *value_size, void *read_buffer, |
| const void *write_buffer) |
| { |
| if ((read_buffer != nullptr) == (write_buffer != nullptr)) |
| return AMD_DBGAPI_STATUS_ERROR_INVALID_ARGUMENT_COMPATIBILITY; |
| |
| inferior *inf = reinterpret_cast<inferior *> (client_process_id); |
| |
| /* We need to set inferior_ptid / current_inferior as those are |
| used by the target which will process the xfer_partial request. |
| |
| Note that we end up here when amd-dbgapi tries to access device memory or |
| register content which are at this point mapped/saved in the host process |
| memory. As a consequence, unwinding GPU frames will most likely call into |
| here. If we used switch_to_thread to select a host thread, this would |
| implicitly call reinit_frame_cache. We do not want to clear the frame |
| cache while trying to build it. */ |
| scoped_restore save_inferior_ptid = make_scoped_restore (&inferior_ptid); |
| scoped_restore_current_inferior restore_current_inferior; |
| scoped_restore_current_program_space restore_program_space; |
| inferior_ptid = ptid_t (inf->pid); |
| set_current_inferior (inf); |
| set_current_program_space (inf->pspace); |
| |
| target_xfer_status status |
| = target_xfer_partial (inf->top_target (), TARGET_OBJECT_RAW_MEMORY, |
| nullptr, static_cast<gdb_byte *> (read_buffer), |
| static_cast<const gdb_byte *> (write_buffer), |
| global_address, *value_size, value_size); |
| |
| if (status == TARGET_XFER_EOF) |
| return AMD_DBGAPI_STATUS_ERROR_PROCESS_EXITED; |
| else if (status != TARGET_XFER_OK) |
| return AMD_DBGAPI_STATUS_ERROR_MEMORY_ACCESS; |
| |
| 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, |
| .client_process_get_info = amd_dbgapi_client_process_get_info_callback, |
| .insert_breakpoint = amd_dbgapi_insert_breakpoint_callback, |
| .remove_breakpoint = amd_dbgapi_remove_breakpoint_callback, |
| .xfer_global_memory = amd_dbgapi_xfer_global_memory_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); |
| } |