| /* Ada Ravenscar thread support. |
| |
| Copyright (C) 2004-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 "extract-store-integer.h" |
| #include "gdbcore.h" |
| #include "gdbthread.h" |
| #include "ada-lang.h" |
| #include "target.h" |
| #include "inferior.h" |
| #include "command.h" |
| #include "ravenscar-thread.h" |
| #include "observable.h" |
| #include "cli/cli-cmds.h" |
| #include "top.h" |
| #include "regcache.h" |
| #include "objfiles.h" |
| #include <unordered_map> |
| |
| /* This module provides support for "Ravenscar" tasks (Ada) when |
| debugging on bare-metal targets. |
| |
| The typical situation is when debugging a bare-metal target over |
| the remote protocol. In that situation, the system does not know |
| about high-level concepts such as threads, only about some code |
| running on one or more CPUs. And since the remote protocol does not |
| provide any handling for CPUs, the de facto standard for handling |
| them is to have one thread per CPU, where the thread's ptid has |
| its lwp field set to the CPU number (eg: 1 for the first CPU, |
| 2 for the second one, etc). This module will make that assumption. |
| |
| This module then creates and maintains the list of threads based |
| on the list of Ada tasks, with one thread per Ada task. The convention |
| is that threads corresponding to the CPUs (see assumption above) |
| have a ptid_t of the form (PID, LWP, 0), while threads corresponding |
| to our Ada tasks have a ptid_t of the form (PID, 0, TID) where TID |
| is the Ada task's ID as extracted from Ada runtime information. |
| |
| Switching to a given Ada task (or its underlying thread) is performed |
| by fetching the registers of that task from the memory area where |
| the registers were saved. For any of the other operations, the |
| operation is performed by first finding the CPU on which the task |
| is running, switching to its corresponding ptid, and then performing |
| the operation on that ptid using the target beneath us. */ |
| |
| /* If true, ravenscar task support is enabled. */ |
| static bool ravenscar_task_support = true; |
| |
| static const char running_thread_name[] = "__gnat_running_thread_table"; |
| |
| static const char known_tasks_name[] = "system__tasking__debug__known_tasks"; |
| static const char first_task_name[] = "system__tasking__debug__first_task"; |
| |
| static const char ravenscar_runtime_initializer[] |
| = "system__bb__threads__initialize"; |
| |
| static const target_info ravenscar_target_info = { |
| "ravenscar", |
| N_("Ravenscar tasks."), |
| N_("Ravenscar tasks support.") |
| }; |
| |
| struct ravenscar_thread_target final : public target_ops |
| { |
| ravenscar_thread_target () |
| : m_base_ptid (inferior_ptid) |
| { |
| } |
| |
| const target_info &info () const override |
| { return ravenscar_target_info; } |
| |
| strata stratum () const override { return thread_stratum; } |
| |
| ptid_t wait (ptid_t, struct target_waitstatus *, target_wait_flags) override; |
| void resume (ptid_t, int, enum gdb_signal) override; |
| |
| void fetch_registers (struct regcache *, int) override; |
| void store_registers (struct regcache *, int) override; |
| |
| void prepare_to_store (struct regcache *) override; |
| |
| bool stopped_by_sw_breakpoint () override; |
| |
| bool stopped_by_hw_breakpoint () override; |
| |
| bool stopped_by_watchpoint () override; |
| |
| bool stopped_data_address (CORE_ADDR *) 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 thread_alive (ptid_t ptid) override; |
| |
| int core_of_thread (ptid_t ptid) override; |
| |
| void update_thread_list () override; |
| |
| std::string pid_to_str (ptid_t) override; |
| |
| ptid_t get_ada_task_ptid (long lwp, ULONGEST thread) override; |
| |
| struct btrace_target_info *enable_btrace (thread_info *tp, |
| const struct btrace_config *conf) |
| override |
| { |
| process_stratum_target *proc_target |
| = as_process_stratum_target (this->beneath ()); |
| ptid_t underlying = get_base_thread_from_ravenscar_task (tp->ptid); |
| tp = proc_target->find_thread (underlying); |
| |
| return beneath ()->enable_btrace (tp, conf); |
| } |
| |
| void mourn_inferior () override; |
| |
| void close () override |
| { |
| delete this; |
| } |
| |
| thread_info *add_active_thread (); |
| |
| private: |
| |
| /* PTID of the last thread that received an event. |
| This can be useful to determine the associated task that received |
| the event, to make it the current task. */ |
| ptid_t m_base_ptid; |
| |
| ptid_t active_task (int cpu); |
| bool task_is_currently_active (ptid_t ptid); |
| bool runtime_initialized (); |
| int get_thread_base_cpu (ptid_t ptid); |
| ptid_t get_base_thread_from_ravenscar_task (ptid_t ptid); |
| void add_thread (struct ada_task_info *task); |
| |
| /* Like switch_to_thread, but uses the base ptid for the thread. */ |
| void set_base_thread_from_ravenscar_task (ptid_t ptid) |
| { |
| process_stratum_target *proc_target |
| = as_process_stratum_target (this->beneath ()); |
| ptid_t underlying = get_base_thread_from_ravenscar_task (ptid); |
| switch_to_thread (proc_target->find_thread (underlying)); |
| } |
| |
| /* Some targets use lazy FPU initialization. On these, the FP |
| registers for a given task might be uninitialized, or stored in |
| the per-task context, or simply be the live registers on the CPU. |
| This enum is used to encode this information. */ |
| enum fpu_state |
| { |
| /* This target doesn't do anything special for FP registers -- if |
| any exist, they are treated just identical to non-FP |
| registers. */ |
| NOTHING_SPECIAL, |
| /* This target uses the lazy FP scheme, and the FP registers are |
| taken from the CPU. This can happen for any task, because if a |
| task switch occurs, the registers aren't immediately written to |
| the per-task context -- this is deferred until the current task |
| causes an FPU trap. */ |
| LIVE_FP_REGISTERS, |
| /* This target uses the lazy FP scheme, and the FP registers are |
| not available. Maybe this task never initialized the FPU, or |
| maybe GDB couldn't find the required symbol. */ |
| NO_FP_REGISTERS |
| }; |
| |
| /* Return the FPU state. */ |
| fpu_state get_fpu_state (struct regcache *regcache, |
| const ravenscar_arch_ops *arch_ops); |
| |
| /* This maps a TID to the CPU on which it was running. This is |
| needed because sometimes the runtime will report an active task |
| that hasn't yet been put on the list of tasks that is read by |
| ada-tasks.c. */ |
| std::unordered_map<ULONGEST, int> m_cpu_map; |
| }; |
| |
| /* Return true iff PTID corresponds to a ravenscar task. */ |
| |
| static bool |
| is_ravenscar_task (ptid_t ptid) |
| { |
| /* By construction, ravenscar tasks have their LWP set to zero. |
| Also make sure that the TID is nonzero, as some remotes, when |
| asked for the list of threads, will return the first thread |
| as having its TID set to zero. For instance, TSIM version |
| 2.0.48 for LEON3 sends 'm0' as a reply to the 'qfThreadInfo' |
| query, which the remote protocol layer then treats as a thread |
| whose TID is 0. This is obviously not a ravenscar task. */ |
| return ptid.lwp () == 0 && ptid.tid () != 0; |
| } |
| |
| /* Given PTID, which can be either a ravenscar task or a CPU thread, |
| return which CPU that ptid is running on. |
| |
| This assume that PTID is a valid ptid_t. Otherwise, a gdb_assert |
| will be triggered. */ |
| |
| int |
| ravenscar_thread_target::get_thread_base_cpu (ptid_t ptid) |
| { |
| int base_cpu; |
| |
| if (is_ravenscar_task (ptid)) |
| { |
| /* Prefer to not read inferior memory if possible, to avoid |
| reentrancy problems with xfer_partial. */ |
| auto iter = m_cpu_map.find (ptid.tid ()); |
| |
| if (iter != m_cpu_map.end ()) |
| base_cpu = iter->second; |
| else |
| { |
| struct ada_task_info *task_info = ada_get_task_info_from_ptid (ptid); |
| |
| gdb_assert (task_info != NULL); |
| base_cpu = task_info->base_cpu; |
| } |
| } |
| else |
| { |
| /* We assume that the LWP of the PTID is equal to the CPU number. */ |
| base_cpu = ptid.lwp (); |
| } |
| |
| return base_cpu; |
| } |
| |
| /* Given a ravenscar task (identified by its ptid_t PTID), return true |
| if this task is the currently active task on the cpu that task is |
| running on. |
| |
| In other words, this function determine which CPU this task is |
| currently running on, and then return nonzero if the CPU in question |
| is executing the code for that task. If that's the case, then |
| that task's registers are in the CPU bank. Otherwise, the task |
| is currently suspended, and its registers have been saved in memory. */ |
| |
| bool |
| ravenscar_thread_target::task_is_currently_active (ptid_t ptid) |
| { |
| ptid_t active_task_ptid = active_task (get_thread_base_cpu (ptid)); |
| |
| return ptid == active_task_ptid; |
| } |
| |
| /* Return the CPU thread (as a ptid_t) on which the given ravenscar |
| task is running. |
| |
| This is the thread that corresponds to the CPU on which the task |
| is running. */ |
| |
| ptid_t |
| ravenscar_thread_target::get_base_thread_from_ravenscar_task (ptid_t ptid) |
| { |
| int base_cpu; |
| |
| if (!is_ravenscar_task (ptid)) |
| return ptid; |
| |
| base_cpu = get_thread_base_cpu (ptid); |
| return ptid_t (ptid.pid (), base_cpu); |
| } |
| |
| /* Fetch the ravenscar running thread from target memory, make sure |
| there's a corresponding thread in the thread list, and return it. |
| If the runtime is not initialized, return NULL. */ |
| |
| thread_info * |
| ravenscar_thread_target::add_active_thread () |
| { |
| process_stratum_target *proc_target |
| = as_process_stratum_target (this->beneath ()); |
| |
| int base_cpu; |
| |
| gdb_assert (!is_ravenscar_task (m_base_ptid)); |
| base_cpu = get_thread_base_cpu (m_base_ptid); |
| |
| if (!runtime_initialized ()) |
| return nullptr; |
| |
| /* It's possible for runtime_initialized to return true but for it |
| not to be fully initialized. For example, this can happen for a |
| breakpoint placed at the task's beginning. */ |
| ptid_t active_ptid = active_task (base_cpu); |
| if (active_ptid == null_ptid) |
| return nullptr; |
| |
| /* The running thread may not have been added to |
| system.tasking.debug's list yet; so ravenscar_update_thread_list |
| may not always add it to the thread list. Add it here. */ |
| thread_info *active_thr = proc_target->find_thread (active_ptid); |
| if (active_thr == nullptr) |
| { |
| active_thr = ::add_thread (proc_target, active_ptid); |
| m_cpu_map[active_ptid.tid ()] = base_cpu; |
| } |
| return active_thr; |
| } |
| |
| /* The Ravenscar Runtime exports a symbol which contains the ID of |
| the thread that is currently running. Try to locate that symbol |
| and return its associated minimal symbol. |
| Return NULL if not found. */ |
| |
| static bound_minimal_symbol |
| get_running_thread_msymbol () |
| { |
| bound_minimal_symbol msym |
| = lookup_minimal_symbol (current_program_space, running_thread_name); |
| if (!msym.minsym) |
| /* Older versions of the GNAT runtime were using a different |
| (less ideal) name for the symbol where the active thread ID |
| is stored. If we couldn't find the symbol using the latest |
| name, then try the old one. */ |
| msym = lookup_minimal_symbol (current_program_space, "running_thread"); |
| |
| return msym; |
| } |
| |
| /* Return True if the Ada Ravenscar run-time can be found in the |
| application. */ |
| |
| static bool |
| has_ravenscar_runtime () |
| { |
| bound_minimal_symbol msym_ravenscar_runtime_initializer |
| = lookup_minimal_symbol (current_program_space, |
| ravenscar_runtime_initializer); |
| bound_minimal_symbol msym_known_tasks |
| = lookup_minimal_symbol (current_program_space, known_tasks_name); |
| bound_minimal_symbol msym_first_task |
| = lookup_minimal_symbol (current_program_space, first_task_name); |
| bound_minimal_symbol msym_running_thread = get_running_thread_msymbol (); |
| |
| return (msym_ravenscar_runtime_initializer.minsym |
| && (msym_known_tasks.minsym || msym_first_task.minsym) |
| && msym_running_thread.minsym); |
| } |
| |
| /* Return True if the Ada Ravenscar run-time can be found in the |
| application, and if it has been initialized on target. */ |
| |
| bool |
| ravenscar_thread_target::runtime_initialized () |
| { |
| return active_task (1) != null_ptid; |
| } |
| |
| /* Return the ID of the thread that is currently running. |
| Return 0 if the ID could not be determined. */ |
| |
| static CORE_ADDR |
| get_running_thread_id (int cpu) |
| { |
| bound_minimal_symbol object_msym = get_running_thread_msymbol (); |
| int object_size; |
| int buf_size; |
| gdb_byte *buf; |
| CORE_ADDR object_addr; |
| struct type *builtin_type_void_data_ptr |
| = builtin_type (current_inferior ()->arch ())->builtin_data_ptr; |
| |
| if (!object_msym.minsym) |
| return 0; |
| |
| object_size = builtin_type_void_data_ptr->length (); |
| object_addr = (object_msym.value_address () |
| + (cpu - 1) * object_size); |
| buf_size = object_size; |
| buf = (gdb_byte *) alloca (buf_size); |
| read_memory (object_addr, buf, buf_size); |
| return extract_typed_address (buf, builtin_type_void_data_ptr); |
| } |
| |
| void |
| ravenscar_thread_target::resume (ptid_t ptid, int step, |
| enum gdb_signal siggnal) |
| { |
| /* If we see a wildcard resume, we simply pass that on. Otherwise, |
| arrange to resume the base ptid. */ |
| inferior_ptid = m_base_ptid; |
| if (ptid.is_pid ()) |
| { |
| /* We only have one process, so resume all threads of it. */ |
| ptid = minus_one_ptid; |
| } |
| else if (ptid != minus_one_ptid) |
| ptid = m_base_ptid; |
| beneath ()->resume (ptid, step, siggnal); |
| } |
| |
| ptid_t |
| ravenscar_thread_target::wait (ptid_t ptid, |
| struct target_waitstatus *status, |
| target_wait_flags options) |
| { |
| process_stratum_target *beneath |
| = as_process_stratum_target (this->beneath ()); |
| ptid_t event_ptid; |
| |
| if (ptid != minus_one_ptid) |
| ptid = m_base_ptid; |
| event_ptid = beneath->wait (ptid, status, 0); |
| /* Find any new threads that might have been created, and return the |
| active thread. |
| |
| Only do it if the program is still alive, though. Otherwise, |
| this causes problems when debugging through the remote protocol, |
| because we might try switching threads (and thus sending packets) |
| after the remote has disconnected. */ |
| if (status->kind () != TARGET_WAITKIND_EXITED |
| && status->kind () != TARGET_WAITKIND_SIGNALLED |
| && runtime_initialized ()) |
| { |
| m_base_ptid = event_ptid; |
| this->update_thread_list (); |
| thread_info *thr = this->add_active_thread (); |
| if (thr != nullptr) |
| return thr->ptid; |
| } |
| return event_ptid; |
| } |
| |
| /* Add the thread associated to the given TASK to the thread list |
| (if the thread has already been added, this is a no-op). */ |
| |
| void |
| ravenscar_thread_target::add_thread (struct ada_task_info *task) |
| { |
| if (current_inferior ()->find_thread (task->ptid) == NULL) |
| { |
| ::add_thread (current_inferior ()->process_target (), task->ptid); |
| m_cpu_map[task->ptid.tid ()] = task->base_cpu; |
| } |
| } |
| |
| void |
| ravenscar_thread_target::update_thread_list () |
| { |
| /* iterate_over_live_ada_tasks requires that inferior_ptid be set, |
| but this isn't always the case in target methods. So, we ensure |
| it here. */ |
| scoped_restore save_ptid = make_scoped_restore (&inferior_ptid, |
| m_base_ptid); |
| |
| /* Do not clear the thread list before adding the Ada task, to keep |
| the thread that the process stratum has included into it |
| (m_base_ptid) and the running thread, that may not have been included |
| to system.tasking.debug's list yet. */ |
| |
| iterate_over_live_ada_tasks ([this] (struct ada_task_info *task) |
| { |
| this->add_thread (task); |
| }); |
| } |
| |
| ptid_t |
| ravenscar_thread_target::active_task (int cpu) |
| { |
| CORE_ADDR tid = get_running_thread_id (cpu); |
| |
| if (tid == 0) |
| return null_ptid; |
| else |
| return ptid_t (m_base_ptid.pid (), 0, tid); |
| } |
| |
| bool |
| ravenscar_thread_target::thread_alive (ptid_t ptid) |
| { |
| /* Ravenscar tasks are non-terminating. */ |
| return true; |
| } |
| |
| std::string |
| ravenscar_thread_target::pid_to_str (ptid_t ptid) |
| { |
| if (!is_ravenscar_task (ptid)) |
| return beneath ()->pid_to_str (ptid); |
| |
| return string_printf ("Ravenscar Thread 0x%s", |
| phex_nz (ptid.tid (), sizeof (ULONGEST))); |
| } |
| |
| CORE_ADDR |
| ravenscar_arch_ops::get_stack_base (struct regcache *regcache) const |
| { |
| struct gdbarch *gdbarch = regcache->arch (); |
| const int sp_regnum = gdbarch_sp_regnum (gdbarch); |
| ULONGEST stack_address; |
| regcache_cooked_read_unsigned (regcache, sp_regnum, &stack_address); |
| return (CORE_ADDR) stack_address; |
| } |
| |
| void |
| ravenscar_arch_ops::supply_one_register (struct regcache *regcache, |
| int regnum, |
| CORE_ADDR descriptor, |
| CORE_ADDR stack_base) const |
| { |
| CORE_ADDR addr; |
| if (regnum >= first_stack_register && regnum <= last_stack_register) |
| addr = stack_base; |
| else |
| addr = descriptor; |
| addr += offsets[regnum]; |
| |
| struct gdbarch *gdbarch = regcache->arch (); |
| int size = register_size (gdbarch, regnum); |
| gdb_byte *buf = (gdb_byte *) alloca (size); |
| read_memory (addr, buf, size); |
| regcache->raw_supply (regnum, buf); |
| } |
| |
| void |
| ravenscar_arch_ops::fetch_register (struct regcache *regcache, |
| int regnum) const |
| { |
| gdb_assert (regnum != -1); |
| |
| struct gdbarch *gdbarch = regcache->arch (); |
| /* The tid is the thread_id field, which is a pointer to the thread. */ |
| CORE_ADDR thread_descriptor_address |
| = (CORE_ADDR) regcache->ptid ().tid (); |
| |
| int sp_regno = -1; |
| CORE_ADDR stack_address = 0; |
| if (regnum >= first_stack_register && regnum <= last_stack_register) |
| { |
| /* We must supply SP for get_stack_base, so recurse. */ |
| sp_regno = gdbarch_sp_regnum (gdbarch); |
| gdb_assert (!(sp_regno >= first_stack_register |
| && sp_regno <= last_stack_register)); |
| fetch_register (regcache, sp_regno); |
| stack_address = get_stack_base (regcache); |
| } |
| |
| if (regnum < offsets.size () && offsets[regnum] != -1) |
| supply_one_register (regcache, regnum, thread_descriptor_address, |
| stack_address); |
| } |
| |
| void |
| ravenscar_arch_ops::store_one_register (struct regcache *regcache, int regnum, |
| CORE_ADDR descriptor, |
| CORE_ADDR stack_base) const |
| { |
| CORE_ADDR addr; |
| if (regnum >= first_stack_register && regnum <= last_stack_register) |
| addr = stack_base; |
| else |
| addr = descriptor; |
| addr += offsets[regnum]; |
| |
| struct gdbarch *gdbarch = regcache->arch (); |
| int size = register_size (gdbarch, regnum); |
| gdb_byte *buf = (gdb_byte *) alloca (size); |
| regcache->raw_collect (regnum, buf); |
| write_memory (addr, buf, size); |
| } |
| |
| void |
| ravenscar_arch_ops::store_register (struct regcache *regcache, |
| int regnum) const |
| { |
| gdb_assert (regnum != -1); |
| |
| /* The tid is the thread_id field, which is a pointer to the thread. */ |
| CORE_ADDR thread_descriptor_address |
| = (CORE_ADDR) regcache->ptid ().tid (); |
| |
| CORE_ADDR stack_address = 0; |
| if (regnum >= first_stack_register && regnum <= last_stack_register) |
| stack_address = get_stack_base (regcache); |
| |
| if (regnum < offsets.size () && offsets[regnum] != -1) |
| store_one_register (regcache, regnum, thread_descriptor_address, |
| stack_address); |
| } |
| |
| /* Temporarily set the ptid of a regcache to some other value. When |
| this object is destroyed, the regcache's original ptid is |
| restored. */ |
| |
| class temporarily_change_regcache_ptid |
| { |
| public: |
| |
| temporarily_change_regcache_ptid (struct regcache *regcache, ptid_t new_ptid) |
| : m_regcache (regcache), |
| m_save_ptid (regcache->ptid ()) |
| { |
| m_regcache->set_ptid (new_ptid); |
| } |
| |
| ~temporarily_change_regcache_ptid () |
| { |
| m_regcache->set_ptid (m_save_ptid); |
| } |
| |
| private: |
| |
| /* The regcache. */ |
| struct regcache *m_regcache; |
| /* The saved ptid. */ |
| ptid_t m_save_ptid; |
| }; |
| |
| ravenscar_thread_target::fpu_state |
| ravenscar_thread_target::get_fpu_state (struct regcache *regcache, |
| const ravenscar_arch_ops *arch_ops) |
| { |
| /* We want to return true if the special FP register handling is |
| needed. If this target doesn't have lazy FP, then no special |
| treatment is ever needed. */ |
| if (!arch_ops->on_demand_fp ()) |
| return NOTHING_SPECIAL; |
| |
| bound_minimal_symbol fpu_context |
| = lookup_minimal_symbol (current_program_space, |
| "system__bb__cpu_primitives__current_fpu_context", |
| nullptr, nullptr); |
| /* If the symbol can't be found, just fall back. */ |
| if (fpu_context.minsym == nullptr) |
| return NO_FP_REGISTERS; |
| |
| type *ptr_type |
| = builtin_type (current_inferior ()->arch ())->builtin_data_ptr; |
| ptr_type = lookup_pointer_type (ptr_type); |
| value *val = value_from_pointer (ptr_type, fpu_context.value_address ()); |
| |
| int cpu = get_thread_base_cpu (regcache->ptid ()); |
| /* The array index type has a lower bound of 1 -- it is Ada code -- |
| so subtract 1 here. */ |
| val = value_ptradd (val, cpu - 1); |
| |
| val = value_ind (val); |
| CORE_ADDR fpu_task = value_as_long (val); |
| |
| /* The tid is the thread_id field, which is a pointer to the thread. */ |
| CORE_ADDR thread_descriptor_address |
| = (CORE_ADDR) regcache->ptid ().tid (); |
| if (fpu_task == (thread_descriptor_address |
| + arch_ops->get_fpu_context_offset ())) |
| return LIVE_FP_REGISTERS; |
| |
| int v_init_offset = arch_ops->get_v_init_offset (); |
| gdb_byte init = 0; |
| read_memory (thread_descriptor_address + v_init_offset, &init, 1); |
| return init ? NOTHING_SPECIAL : NO_FP_REGISTERS; |
| } |
| |
| void |
| ravenscar_thread_target::fetch_registers (struct regcache *regcache, |
| int regnum) |
| { |
| ptid_t ptid = regcache->ptid (); |
| |
| if (runtime_initialized () && is_ravenscar_task (ptid)) |
| { |
| struct gdbarch *gdbarch = regcache->arch (); |
| bool is_active = task_is_currently_active (ptid); |
| struct ravenscar_arch_ops *arch_ops = gdbarch_ravenscar_ops (gdbarch); |
| std::optional<fpu_state> fp_state; |
| |
| int low_reg = regnum == -1 ? 0 : regnum; |
| int high_reg = regnum == -1 ? gdbarch_num_regs (gdbarch) : regnum + 1; |
| |
| ptid_t base = get_base_thread_from_ravenscar_task (ptid); |
| for (int i = low_reg; i < high_reg; ++i) |
| { |
| bool use_beneath = false; |
| if (arch_ops->is_fp_register (i)) |
| { |
| if (!fp_state.has_value ()) |
| fp_state = get_fpu_state (regcache, arch_ops); |
| if (*fp_state == NO_FP_REGISTERS) |
| continue; |
| if (*fp_state == LIVE_FP_REGISTERS |
| || (is_active && *fp_state == NOTHING_SPECIAL)) |
| use_beneath = true; |
| } |
| else |
| use_beneath = is_active; |
| |
| if (use_beneath) |
| { |
| temporarily_change_regcache_ptid changer (regcache, base); |
| beneath ()->fetch_registers (regcache, i); |
| } |
| else |
| arch_ops->fetch_register (regcache, i); |
| } |
| } |
| else |
| beneath ()->fetch_registers (regcache, regnum); |
| } |
| |
| void |
| ravenscar_thread_target::store_registers (struct regcache *regcache, |
| int regnum) |
| { |
| ptid_t ptid = regcache->ptid (); |
| |
| if (runtime_initialized () && is_ravenscar_task (ptid)) |
| { |
| struct gdbarch *gdbarch = regcache->arch (); |
| bool is_active = task_is_currently_active (ptid); |
| struct ravenscar_arch_ops *arch_ops = gdbarch_ravenscar_ops (gdbarch); |
| std::optional<fpu_state> fp_state; |
| |
| int low_reg = regnum == -1 ? 0 : regnum; |
| int high_reg = regnum == -1 ? gdbarch_num_regs (gdbarch) : regnum + 1; |
| |
| ptid_t base = get_base_thread_from_ravenscar_task (ptid); |
| for (int i = low_reg; i < high_reg; ++i) |
| { |
| bool use_beneath = false; |
| if (arch_ops->is_fp_register (i)) |
| { |
| if (!fp_state.has_value ()) |
| fp_state = get_fpu_state (regcache, arch_ops); |
| if (*fp_state == NO_FP_REGISTERS) |
| continue; |
| if (*fp_state == LIVE_FP_REGISTERS |
| || (is_active && *fp_state == NOTHING_SPECIAL)) |
| use_beneath = true; |
| } |
| else |
| use_beneath = is_active; |
| |
| if (use_beneath) |
| { |
| temporarily_change_regcache_ptid changer (regcache, base); |
| beneath ()->store_registers (regcache, i); |
| } |
| else |
| arch_ops->store_register (regcache, i); |
| } |
| } |
| else |
| beneath ()->store_registers (regcache, regnum); |
| } |
| |
| void |
| ravenscar_thread_target::prepare_to_store (struct regcache *regcache) |
| { |
| ptid_t ptid = regcache->ptid (); |
| |
| if (runtime_initialized () && is_ravenscar_task (ptid)) |
| { |
| if (task_is_currently_active (ptid)) |
| { |
| ptid_t base = get_base_thread_from_ravenscar_task (ptid); |
| temporarily_change_regcache_ptid changer (regcache, base); |
| beneath ()->prepare_to_store (regcache); |
| } |
| else |
| { |
| /* Nothing. */ |
| } |
| } |
| else |
| beneath ()->prepare_to_store (regcache); |
| } |
| |
| /* Implement the to_stopped_by_sw_breakpoint target_ops "method". */ |
| |
| bool |
| ravenscar_thread_target::stopped_by_sw_breakpoint () |
| { |
| scoped_restore_current_thread saver; |
| set_base_thread_from_ravenscar_task (inferior_ptid); |
| return beneath ()->stopped_by_sw_breakpoint (); |
| } |
| |
| /* Implement the to_stopped_by_hw_breakpoint target_ops "method". */ |
| |
| bool |
| ravenscar_thread_target::stopped_by_hw_breakpoint () |
| { |
| scoped_restore_current_thread saver; |
| set_base_thread_from_ravenscar_task (inferior_ptid); |
| return beneath ()->stopped_by_hw_breakpoint (); |
| } |
| |
| /* Implement the to_stopped_by_watchpoint target_ops "method". */ |
| |
| bool |
| ravenscar_thread_target::stopped_by_watchpoint () |
| { |
| scoped_restore_current_thread saver; |
| set_base_thread_from_ravenscar_task (inferior_ptid); |
| return beneath ()->stopped_by_watchpoint (); |
| } |
| |
| /* Implement the to_stopped_data_address target_ops "method". */ |
| |
| bool |
| ravenscar_thread_target::stopped_data_address (CORE_ADDR *addr_p) |
| { |
| scoped_restore_current_thread saver; |
| set_base_thread_from_ravenscar_task (inferior_ptid); |
| return beneath ()->stopped_data_address (addr_p); |
| } |
| |
| void |
| ravenscar_thread_target::mourn_inferior () |
| { |
| m_base_ptid = null_ptid; |
| target_ops *beneath = this->beneath (); |
| current_inferior ()->unpush_target (this); |
| beneath->mourn_inferior (); |
| } |
| |
| /* Implement the to_core_of_thread target_ops "method". */ |
| |
| int |
| ravenscar_thread_target::core_of_thread (ptid_t ptid) |
| { |
| scoped_restore_current_thread saver; |
| set_base_thread_from_ravenscar_task (inferior_ptid); |
| return beneath ()->core_of_thread (inferior_ptid); |
| } |
| |
| /* Implement the target xfer_partial method. */ |
| |
| enum target_xfer_status |
| ravenscar_thread_target::xfer_partial (enum target_object object, |
| const char *annex, |
| gdb_byte *readbuf, |
| const gdb_byte *writebuf, |
| ULONGEST offset, ULONGEST len, |
| ULONGEST *xfered_len) |
| { |
| scoped_restore save_ptid = make_scoped_restore (&inferior_ptid); |
| /* Calling get_base_thread_from_ravenscar_task can read memory from |
| the inferior. However, that function is written to prefer our |
| internal map, so it should not result in recursive calls in |
| practice. */ |
| inferior_ptid = get_base_thread_from_ravenscar_task (inferior_ptid); |
| return beneath ()->xfer_partial (object, annex, readbuf, writebuf, |
| offset, len, xfered_len); |
| } |
| |
| /* Observer on inferior_created: push ravenscar thread stratum if needed. */ |
| |
| static void |
| ravenscar_inferior_created (inferior *inf) |
| { |
| const char *err_msg; |
| |
| if (!ravenscar_task_support |
| || gdbarch_ravenscar_ops (current_inferior ()->arch ()) == NULL |
| || !has_ravenscar_runtime ()) |
| return; |
| |
| err_msg = ada_get_tcb_types_info (); |
| if (err_msg != NULL) |
| { |
| warning (_("%s. Task/thread support disabled."), err_msg); |
| return; |
| } |
| |
| ravenscar_thread_target *rtarget = new ravenscar_thread_target (); |
| inf->push_target (target_ops_up (rtarget)); |
| thread_info *thr = rtarget->add_active_thread (); |
| if (thr != nullptr) |
| switch_to_thread (thr); |
| } |
| |
| ptid_t |
| ravenscar_thread_target::get_ada_task_ptid (long lwp, ULONGEST thread) |
| { |
| return ptid_t (m_base_ptid.pid (), 0, thread); |
| } |
| |
| /* Command-list for the "set/show ravenscar" prefix command. */ |
| static struct cmd_list_element *set_ravenscar_list; |
| static struct cmd_list_element *show_ravenscar_list; |
| |
| /* Implement the "show ravenscar task-switching" command. */ |
| |
| static void |
| show_ravenscar_task_switching_command (struct ui_file *file, int from_tty, |
| struct cmd_list_element *c, |
| const char *value) |
| { |
| if (ravenscar_task_support) |
| gdb_printf (file, _("\ |
| Support for Ravenscar task/thread switching is enabled\n")); |
| else |
| gdb_printf (file, _("\ |
| Support for Ravenscar task/thread switching is disabled\n")); |
| } |
| |
| /* Module startup initialization function, automagically called by |
| init.c. */ |
| |
| void _initialize_ravenscar (); |
| void |
| _initialize_ravenscar () |
| { |
| /* Notice when the inferior is created in order to push the |
| ravenscar ops if needed. */ |
| gdb::observers::inferior_created.attach (ravenscar_inferior_created, |
| "ravenscar-thread"); |
| |
| add_setshow_prefix_cmd |
| ("ravenscar", no_class, |
| _("Prefix command for changing Ravenscar-specific settings."), |
| _("Prefix command for showing Ravenscar-specific settings."), |
| &set_ravenscar_list, &show_ravenscar_list, |
| &setlist, &showlist); |
| |
| add_setshow_boolean_cmd ("task-switching", class_obscure, |
| &ravenscar_task_support, _("\ |
| Enable or disable support for GNAT Ravenscar tasks."), _("\ |
| Show whether support for GNAT Ravenscar tasks is enabled."), |
| _("\ |
| Enable or disable support for task/thread switching with the GNAT\n\ |
| Ravenscar run-time library for bareboard configuration."), |
| NULL, show_ravenscar_task_switching_command, |
| &set_ravenscar_list, &show_ravenscar_list); |
| } |