| /* Target used to communicate with the AMD Debugger API. | |
| Copyright (C) 2019-2023 Free Software Foundation, Inc. | |
| This file is part of GDB. | |
| This program is free software; you can redistribute it and/or modify | |
| it under the terms of the GNU General Public License as published by | |
| the Free Software Foundation; either version 3 of the License, or | |
| (at your option) any later version. | |
| This program is distributed in the hope that it will be useful, | |
| but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| GNU General Public License for more details. | |
| You should have received a copy of the GNU General Public License | |
| along with this program. If not, see <http://www.gnu.org/licenses/>. */ | |
| #include "defs.h" | |
| #include "amd-dbgapi-target.h" | |
| #include "amdgpu-tdep.h" | |
| #include "async-event.h" | |
| #include "cli/cli-cmds.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_infrun, 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; | |
| } | |
| /* 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) | |
| : inf (inf) | |
| {} | |
| /* 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; | |
| 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; | |
| }; | |
| static amd_dbgapi_event_id_t process_event_queue | |
| (amd_dbgapi_process_id_t process_id = AMD_DBGAPI_PROCESS_NONE, | |
| 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; | |
| /* 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; | |
| /* Return the target id string for a given wave. */ | |
| static std::string | |
| wave_target_id_string (amd_dbgapi_wave_id_t wave_id) | |
| { | |
| amd_dbgapi_dispatch_id_t dispatch_id; | |
| amd_dbgapi_queue_id_t queue_id; | |
| amd_dbgapi_agent_id_t agent_id; | |
| uint32_t group_ids[3], wave_in_group; | |
| std::string str = "AMDGPU Wave"; | |
| amd_dbgapi_status_t status | |
| = amd_dbgapi_wave_get_info (wave_id, AMD_DBGAPI_WAVE_INFO_AGENT, | |
| sizeof (agent_id), &agent_id); | |
| str += (status == AMD_DBGAPI_STATUS_SUCCESS | |
| ? string_printf (" %ld", agent_id.handle) | |
| : " ?"); | |
| status = amd_dbgapi_wave_get_info (wave_id, AMD_DBGAPI_WAVE_INFO_QUEUE, | |
| sizeof (queue_id), &queue_id); | |
| str += (status == AMD_DBGAPI_STATUS_SUCCESS | |
| ? string_printf (":%ld", queue_id.handle) | |
| : ":?"); | |
| status = amd_dbgapi_wave_get_info (wave_id, AMD_DBGAPI_WAVE_INFO_DISPATCH, | |
| sizeof (dispatch_id), &dispatch_id); | |
| str += (status == AMD_DBGAPI_STATUS_SUCCESS | |
| ? string_printf (":%ld", dispatch_id.handle) | |
| : ":?"); | |
| str += string_printf (":%ld", wave_id.handle); | |
| status = amd_dbgapi_wave_get_info (wave_id, | |
| AMD_DBGAPI_WAVE_INFO_WORKGROUP_COORD, | |
| sizeof (group_ids), &group_ids); | |
| str += (status == AMD_DBGAPI_STATUS_SUCCESS | |
| ? string_printf (" (%d,%d,%d)", group_ids[0], group_ids[1], | |
| group_ids[2]) | |
| : " (?,?,?)"); | |
| status = amd_dbgapi_wave_get_info | |
| (wave_id, AMD_DBGAPI_WAVE_INFO_WAVE_NUMBER_IN_WORKGROUP, | |
| sizeof (wave_in_group), &wave_in_group); | |
| str += (status == AMD_DBGAPI_STATUS_SUCCESS | |
| ? string_printf ("/%d", wave_in_group) | |
| : "/?"); | |
| return str; | |
| } | |
| /* 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); | |
| } | |
| /* 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; | |
| } | |
| /* 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) | |
| { | |
| 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->gdbarch, 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->gdbarch, 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); | |
| return wave_target_id_string (get_amd_dbgapi_wave_id (ptid)); | |
| } | |
| 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) | |
| { | |
| gdb::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; | |
| if (thread->ptid == inferior_ptid) | |
| status = amd_dbgapi_wave_resume (wave_id, | |
| (step | |
| ? AMD_DBGAPI_RESUME_MODE_SINGLE_STEP | |
| : AMD_DBGAPI_RESUME_MODE_NORMAL), | |
| exception); | |
| else | |
| status = amd_dbgapi_wave_resume (wave_id, AMD_DBGAPI_RESUME_MODE_NORMAL, | |
| AMD_DBGAPI_EXCEPTION_NONE); | |
| 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)); | |
| } | |
| } | |
| 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); | |
| } | |
| 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) | |
| 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. */ | |
| 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); | |
| } | |
| /* 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) | |
| { | |
| /* Silently create new GPU threads to avoid spamming the | |
| terminal with thousands of "[New Thread ...]" messages. */ | |
| thread = add_thread_silent (proc_target, event_ptid); | |
| set_running (proc_target, event_ptid, true); | |
| set_executing (proc_target, event_ptid, true); | |
| } | |
| /* 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 from the amd_dbgapi and preserve the ordering. */ | |
| process_event_queue (); | |
| 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; | |
| } | |
| /* 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); | |
| /* 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. */ | |
| *info = amd_dbgapi_inferior_info (inf); | |
| 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 ()) | |
| delete_thread (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_thread_silent (inf->process_target (), wave_ptid); | |
| set_running (inf->process_target (), wave_ptid, true); | |
| set_executing (inf->process_target (), wave_ptid, true); | |
| } | |
| } | |
| /* 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); | |
| } | |
| /* 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); | |
| 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 && 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; | |
| } | |
| /* 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) | |
| { | |
| gdb::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); | |
| } | |
| /* 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_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"); | |
| 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); | |
| } |