mirror of
https://sourceware.org/git/binutils-gdb.git
synced 2026-08-27 00:26:02 -04:00
Handle nested Ada functions with gnat-llvm
In Ada, a nested function can refer to variables in lexically enclosing outer scopes. Ordinarily this is implemented in DWARF using DW_AT_static_link, so that the correct outer function invocation can be found from the nested function. However, LLVM does not implement the DWARF DW_AT_static_link feature, so this approach isn't possible. gnat-llvm, though, implements "unnesting" manually, passing an activation record parameter to nested functions. This activation record can be used to find the correct outer frame. This patch adds a new language method to enable this. A new test case is included; this test will fail if the static link or some similar feature is not implemented (i.e., a naive unwind looking for the next instance of the outer function will fail). Reviewed-By: Tom de Vries <tdevries@suse.de>
This commit is contained in:
parent
2341db79d2
commit
26d17a9b24
6 changed files with 195 additions and 1 deletions
|
|
@ -13954,6 +13954,11 @@ public:
|
|||
const struct lang_varobj_ops *varobj_ops () const override
|
||||
{ return &ada_varobj_ops; }
|
||||
|
||||
/* See language.h. */
|
||||
|
||||
frame_info_ptr follow_static_link (const frame_info_ptr &frame) const
|
||||
override;
|
||||
|
||||
protected:
|
||||
/* See language.h. */
|
||||
|
||||
|
|
@ -13964,6 +13969,84 @@ protected:
|
|||
}
|
||||
};
|
||||
|
||||
frame_info_ptr
|
||||
ada_language::follow_static_link (const frame_info_ptr &frame) const
|
||||
{
|
||||
const block *frame_block = get_frame_block (frame, nullptr);
|
||||
if (frame_block == nullptr)
|
||||
return {};
|
||||
frame_block = frame_block->function_block ();
|
||||
|
||||
/* LLVM doesn't implement DW_AT_static_link, but for Ada we can
|
||||
search for the pointer to the activation record. Then, we can go
|
||||
up the stack and find the frame where this activation record is
|
||||
defined. Note that we don't use the activation record directly,
|
||||
because that is type-erased and just holds pointers. */
|
||||
symbol *arec = nullptr;
|
||||
for (symbol *iter : block_iterator_range (frame_block))
|
||||
{
|
||||
/* The activation record argument is an artificial argument
|
||||
whose name starts with "AREC". */
|
||||
if (iter->is_argument () && iter->is_artificial ()
|
||||
&& startswith (iter->linkage_name (), "AREC"))
|
||||
{
|
||||
arec = iter;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (arec == nullptr)
|
||||
return {};
|
||||
|
||||
/* We aren't interested in ordinary (non-quit) exceptions that might
|
||||
occur here -- we just want to return an empty frame if something
|
||||
goes wrong. */
|
||||
try
|
||||
{
|
||||
value *val = read_var_value (arec, frame_block, frame);
|
||||
CORE_ADDR arec_address = value_as_address (val);
|
||||
|
||||
for (frame_info_ptr frame_iter = get_prev_frame (frame);
|
||||
frame_iter != nullptr;
|
||||
frame_iter = get_prev_frame (frame_iter))
|
||||
{
|
||||
/* Stacks can be quite deep: give the user a chance to stop
|
||||
this. */
|
||||
QUIT;
|
||||
|
||||
frame_block = get_frame_block (frame_iter, nullptr);
|
||||
if (frame_block == nullptr)
|
||||
continue;
|
||||
frame_block = frame_block->function_block ();
|
||||
|
||||
for (symbol *iter : block_iterator_range (frame_block))
|
||||
{
|
||||
/* The activation record itself is an artificial
|
||||
non-argument of record type, whose name starts with
|
||||
"AREC", and that has the same address as the argument
|
||||
passed down to the callee. */
|
||||
if (!iter->is_argument () && iter->is_artificial ()
|
||||
&& startswith (iter->linkage_name (), "AREC")
|
||||
&& iter->type ()->code () == TYPE_CODE_STRUCT)
|
||||
{
|
||||
value *outer = read_var_value (iter, frame_block,
|
||||
frame_iter);
|
||||
CORE_ADDR outer_address = outer->address ();
|
||||
if (outer_address == arec_address)
|
||||
return frame_iter;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (const gdb_exception_error &ex)
|
||||
{
|
||||
/* Ignore. */
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
|
||||
/* Single instance of the Ada language class. */
|
||||
|
||||
static ada_language ada_language_defn;
|
||||
|
|
|
|||
|
|
@ -3226,7 +3226,11 @@ frame_follow_static_link (const frame_info_ptr &initial_frame)
|
|||
|
||||
const struct dynamic_prop *static_link = frame_block->static_link ();
|
||||
if (static_link == nullptr)
|
||||
return {};
|
||||
{
|
||||
const language_defn *lang
|
||||
= language_def (get_frame_language (initial_frame));
|
||||
return lang->follow_static_link (initial_frame);
|
||||
}
|
||||
|
||||
CORE_ADDR upper_frame_base;
|
||||
|
||||
|
|
|
|||
|
|
@ -917,6 +917,14 @@ language_defn::value_string (struct gdbarch *gdbarch,
|
|||
|
||||
/* See language.h. */
|
||||
|
||||
frame_info_ptr
|
||||
language_defn::follow_static_link (const frame_info_ptr &frame) const
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
/* See language.h. */
|
||||
|
||||
struct type *
|
||||
language_bool_type (const struct language_defn *la,
|
||||
struct gdbarch *gdbarch)
|
||||
|
|
|
|||
|
|
@ -638,6 +638,18 @@ struct language_defn
|
|||
|
||||
virtual const struct lang_varobj_ops *varobj_ops () const;
|
||||
|
||||
/* Normally a "static link" (a reference to an outer frame) is
|
||||
represented by DW_AT_static_link in DWARF. However, some
|
||||
compilers do not emit this -- but do provide some
|
||||
language-specific way to find the correct outer frame. If the
|
||||
ordinary search for a static link fails for a given frame, then
|
||||
this method will be called for that frame's language. It should
|
||||
either return the correct outer instance, if one exists, or a
|
||||
null frame if no such frame exists. */
|
||||
|
||||
virtual frame_info_ptr follow_static_link (const frame_info_ptr &frame)
|
||||
const;
|
||||
|
||||
protected:
|
||||
|
||||
/* This is the overridable part of the GET_SYMBOL_NAME_MATCHER method.
|
||||
|
|
|
|||
35
gdb/testsuite/gdb.ada/nested-confounding.exp
Normal file
35
gdb/testsuite/gdb.ada/nested-confounding.exp
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
# Copyright 2026 Free Software Foundation, Inc.
|
||||
#
|
||||
# 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/>.
|
||||
|
||||
# A test case for a nested function that requires a static link.
|
||||
|
||||
load_lib "ada.exp"
|
||||
|
||||
require allow_ada_tests
|
||||
|
||||
standard_ada_testfile nested
|
||||
|
||||
if {[gdb_compile_ada "${srcfile}" "${binfile}" executable {debug}] != ""} {
|
||||
return
|
||||
}
|
||||
|
||||
clean_restart ${testfile}
|
||||
set bp_location [gdb_get_line_number "BREAK" "${testdir}/nested.adb"]
|
||||
runto "nested.adb:$bp_location"
|
||||
|
||||
# In the innermost call, the passed id and the reference to the outer
|
||||
# id are different.
|
||||
gdb_test "print id" [quotemeta {$@DECIMAL = 28}]
|
||||
gdb_test "print outer_id" [quotemeta {$@DECIMAL = 23}]
|
||||
52
gdb/testsuite/gdb.ada/nested-confounding/nested.adb
Normal file
52
gdb/testsuite/gdb.ada/nested-confounding/nested.adb
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
-- Copyright 2026 Free Software Foundation, Inc.
|
||||
--
|
||||
-- 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/>.
|
||||
|
||||
procedure Nested is
|
||||
type Proc_Access is access procedure (Id: Integer);
|
||||
|
||||
procedure Parent (My_Id : Integer; Call : access procedure (Id: Integer));
|
||||
|
||||
procedure Do_Nothing (Id : Boolean);
|
||||
|
||||
procedure Do_Nothing (Id : Boolean) is
|
||||
begin
|
||||
null;
|
||||
end Do_Nothing;
|
||||
|
||||
procedure Parent (My_Id : Integer; Call : access procedure (Id: Integer)) is
|
||||
procedure Inner (Id : Integer);
|
||||
|
||||
Outer_Id : Integer := My_Id;
|
||||
|
||||
procedure Inner (Id : Integer) is
|
||||
begin
|
||||
Do_Nothing (Id = Outer_Id); -- BREAK
|
||||
end Inner;
|
||||
|
||||
begin
|
||||
|
||||
-- This setup ensures that when Inner is reached, the most
|
||||
-- recent invocation of Parent will not be the correct one for
|
||||
-- the purposes of finding "Outer_Id".
|
||||
if Call = null then
|
||||
Parent (My_Id + 5, Inner'Access);
|
||||
else
|
||||
Call (Outer_Id);
|
||||
end if;
|
||||
end Parent;
|
||||
|
||||
begin
|
||||
Parent (23, null);
|
||||
end Nested;
|
||||
Loading…
Add table
Add a link
Reference in a new issue