On 2019-09-03 5:38 p.m., Andrew Burgess wrote:
> This patch is a rebase and update of the following three patches:
>
> https://sourceware.org/ml/gdb-patches/2018-11/msg00298.html
> https://sourceware.org/ml/gdb-patches/2018-11/msg00302.html
> https://sourceware.org/ml/gdb-patches/2018-11/msg00301.html
>
> I have merged these together into a single commit as the second patch,
> adding scope support to nested subroutines, means that some of the
> changes in the first patch are now no longer useful and would have to
> be backed out. The third patch is tightly coupled to the changes in
> the second of these patches and I think deserves to live together with
> it.
>
> There is an extra change in cp-namespace.c that is new, this resolves
> an issue with symbol lookup when placing breakpoints from within
> nested subroutines.
>
> There is also an extra test added to this commit 'nested-funcs-2.exp'
> that was written by Richard Bunt from ARM, this offers some additional
> testing of breakpoints on nested functions.
>
> After this commit it is possible to place breakpoints on nested
> Fortran subroutines and functions by using a fully scoped name, for
> example, given this simple Fortran program:
>
> program greeting
> call message
> contains
> subroutine message
> print *, "Hello World"
> end subroutine message
> end program greeting
>
> It is possible to place a breakpoint in 'message' with:
>
> (gdb) break greeting::message
> Breakpoint 1 at 0x4006c9: file basic.f90, line 5.
>
> What doesn't work with this commit is placing a breakpoint like this:
>
> (gdb) break message
> Function "message" not defined.
>
> Making this work will come in a later commit.
Hi Andrew,
I tried to look at and understand the cp-namespace.c change. I am not
knowledgeable enough in that area to convince myself 100% that the change
is fine. But to my eyes it does not look wrong.
The code with your patch makes more sense to me: why would we initiate a
search in the VAR_DOMAIN if the requested domain was a different one.
I was trying to figure out if, for C++, `domain` could ever be something
else than VAR_DOMAIN, but couldn't find a case where it was something else.
So it would suggest that for C++, you change is a no-op, therefore ok. And
I guess that since you did that change, it's because it happens that `domain`
is something else than VAR_DOMAIN when debugging Fortran. And as I said above,
it wouldn't make sense to return something from the VAR_DOMAIN when another
domain is requested.
Also this reminded me that GNU C also allows nested functions, like this:
int main() {
void hello() {
printf("hello ");
void world() {
printf("world\n");
}
world();
}
hello();
return 0;
}
But GDB doesn't let you put breakpoints on these inner functions:
(gdb) b hello
Function "hello" not defined.
Make breakpoint pending on future shared library load? (y or [n]) n
(gdb) b main::hello
Function "main::hello" not defined.
Make breakpoint pending on future shared library load? (y or [n]) n
I suppose it's a very similar situation than what you're doing here for Fortran.
In Fortran, is the "::" operator a thing, or it's the GDB-specific way
of accessing inner elements?
> @@ -8847,12 +8851,15 @@ add_partial_symbol (struct partial_die_info *pdi, struct dwarf2_cu *cu)
> case DW_TAG_subprogram:
> addr = (gdbarch_adjust_dwarf2_addr (gdbarch, pdi->lowpc + baseaddr)
> - baseaddr);
> - if (pdi->is_external || cu->language == language_ada)
> - {
> - /* brobecker/2007-12-26: Normally, only "external" DIEs are part
> - of the global scope. But in Ada, we want to be able to access
> - nested procedures globally. So all Ada subprograms are stored
> - in the global scope. */
> + if (pdi->is_external
> + || cu->language == language_ada
> + || (cu->language == language_fortran
> + && pdi->die_parent
pdi->die_parent != NULL
> + && pdi->die_parent->tag == DW_TAG_subprogram))
> + {
> + /* Normally, only "external" DIEs are part of the global scope.
> + But in Ada and Fortran, we want to be able to access nested procedures
> + globally. So all Ada subprograms are stored in the global scope. */
I guess the last sentence should mention Fortran too.
Simon
@@ -278,8 +278,9 @@ cp_search_static_and_baseclasses (const char *name,
/* If the scope is a function/method, then look up NESTED as a local
static variable. E.g., "print 'function()::static_var'". */
- if (TYPE_CODE (scope_type) == TYPE_CODE_FUNC
- || TYPE_CODE (scope_type) == TYPE_CODE_METHOD)
+ if ((TYPE_CODE (scope_type) == TYPE_CODE_FUNC
+ || TYPE_CODE (scope_type) == TYPE_CODE_METHOD)
+ && domain == VAR_DOMAIN)
return lookup_symbol (nested, SYMBOL_BLOCK_VALUE (scope_sym.symbol),
VAR_DOMAIN, NULL);
@@ -16473,6 +16473,10 @@
types. Also suitable for unions. As unions aren't part of regular Fortran,
this can only happen when accessing a register that uses a gdbarch-defined
union type.
+@item ::
+The scope operator. Normally used to access variables in modules or
+to set breakpoints on subroutines nested in modules or in other
+subroutines (internal subroutines).
@end table
@node Fortran Defaults
@@ -8747,6 +8747,7 @@ partial_die_parent_scope (struct partial_die_info *pdi,
return NULL;
}
+ /* Nested subroutines in Fortran get a prefix. */
if (pdi->tag == DW_TAG_enumerator)
/* Enumerators should not get the name of the enumeration as a prefix. */
parent->scope = grandparent_scope;
@@ -8756,7 +8757,10 @@ partial_die_parent_scope (struct partial_die_info *pdi,
|| parent->tag == DW_TAG_class_type
|| parent->tag == DW_TAG_interface_type
|| parent->tag == DW_TAG_union_type
- || parent->tag == DW_TAG_enumeration_type)
+ || parent->tag == DW_TAG_enumeration_type
+ || (cu->language == language_fortran
+ && parent->tag == DW_TAG_subprogram
+ && pdi->tag == DW_TAG_subprogram))
{
if (grandparent_scope == NULL)
parent->scope = parent->name;
@@ -8847,12 +8851,15 @@ add_partial_symbol (struct partial_die_info *pdi, struct dwarf2_cu *cu)
case DW_TAG_subprogram:
addr = (gdbarch_adjust_dwarf2_addr (gdbarch, pdi->lowpc + baseaddr)
- baseaddr);
- if (pdi->is_external || cu->language == language_ada)
- {
- /* brobecker/2007-12-26: Normally, only "external" DIEs are part
- of the global scope. But in Ada, we want to be able to access
- nested procedures globally. So all Ada subprograms are stored
- in the global scope. */
+ if (pdi->is_external
+ || cu->language == language_ada
+ || (cu->language == language_fortran
+ && pdi->die_parent
+ && pdi->die_parent->tag == DW_TAG_subprogram))
+ {
+ /* Normally, only "external" DIEs are part of the global scope.
+ But in Ada and Fortran, we want to be able to access nested procedures
+ globally. So all Ada subprograms are stored in the global scope. */
add_psymbol_to_list (actual_name, strlen (actual_name),
built_actual_name != NULL,
VAR_DOMAIN, LOC_BLOCK,
@@ -9108,7 +9115,7 @@ add_partial_subprogram (struct partial_die_info *pdi,
if (! pdi->has_children)
return;
- if (cu->language == language_ada)
+ if (cu->language == language_ada || cu->language == language_fortran)
{
pdi = pdi->die_child;
while (pdi != NULL)
@@ -10544,6 +10551,12 @@ process_die (struct die_info *die, struct dwarf2_cu *cu)
read_type_unit_scope (die, cu);
break;
case DW_TAG_subprogram:
+ /* Nested subprograms in Fortran get a prefix. */
+ if (cu->language == language_fortran
+ && die->parent != NULL
+ && die->parent->tag == DW_TAG_subprogram)
+ cu->processing_has_namespace_info = true;
+ /* Fall through. */
case DW_TAG_inlined_subroutine:
read_func_scope (die, cu);
break;
@@ -18567,10 +18580,10 @@ load_partial_dies (const struct die_reader_specs *reader,
inside functions to find template arguments (if the name of the
function does not already contain the template arguments).
- For Ada, we need to scan the children of subprograms and lexical
- blocks as well because Ada allows the definition of nested
- entities that could be interesting for the debugger, such as
- nested subprograms for instance. */
+ For Ada and Fortran, we need to scan the children of subprograms
+ and lexical blocks as well because these languages allow the
+ definition of nested entities that could be interesting for the
+ debugger, such as nested subprograms for instance. */
if (last_die->has_children
&& (load_all
|| last_die->tag == DW_TAG_namespace
@@ -18585,7 +18598,8 @@ load_partial_dies (const struct die_reader_specs *reader,
|| last_die->tag == DW_TAG_interface_type
|| last_die->tag == DW_TAG_structure_type
|| last_die->tag == DW_TAG_union_type))
- || (cu->language == language_ada
+ || ((cu->language == language_ada
+ || cu->language == language_fortran)
&& (last_die->tag == DW_TAG_subprogram
|| last_die->tag == DW_TAG_lexical_block))))
{
@@ -21535,14 +21549,15 @@ new_symbol (struct die_info *die, struct type *type, struct dwarf2_cu *cu,
SYMBOL_ACLASS_INDEX (sym) = LOC_BLOCK;
attr2 = dwarf2_attr (die, DW_AT_external, cu);
if ((attr2 && (DW_UNSND (attr2) != 0))
- || cu->language == language_ada)
+ || cu->language == language_ada
+ || cu->language == language_fortran)
{
/* Subprograms marked external are stored as a global symbol.
- Ada subprograms, whether marked external or not, are always
- stored as a global symbol, because we want to be able to
- access them globally. For instance, we want to be able
- to break on a nested subprogram without having to
- specify the context. */
+ Ada and Fortran subprograms, whether marked external or
+ not, are always stored as a global symbol, because we want
+ to be able to access them globally. For instance, we want
+ to be able to break on a nested subprogram without having
+ to specify the context. */
list_to_add = cu->get_builder ()->get_global_symbols ();
}
else
@@ -22537,6 +22552,16 @@ determine_prefix (struct die_info *die, struct dwarf2_cu *cu)
return name;
}
return "";
+ case DW_TAG_subprogram:
+ /* Nested subroutines in Fortran get a prefix with the name
+ of the parent's subroutine. */
+ if (cu->language == language_fortran)
+ {
+ if ((die->tag == DW_TAG_subprogram)
+ && (dwarf2_name (parent, cu) != NULL))
+ return dwarf2_name (parent, cu);
+ }
+ return determine_prefix (parent, cu);
case DW_TAG_enumeration_type:
parent_type = read_type_die (parent, cu);
if (TYPE_DECLARED_CLASS (parent_type))
new file mode 100644
@@ -0,0 +1,156 @@
+# Copyright 2019 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/> .
+
+# Further testing of placing breakpoints in nested subroutines.
+
+if {[skip_fortran_tests]} { return -1 }
+load_lib "fortran.exp"
+
+standard_testfile ".f90"
+
+if {[prepare_for_testing ${testfile}.exp ${testfile} \
+ ${srcfile} {debug f90}]} {
+ return -1
+}
+
+set int4 [fortran_int4]
+
+# When WITH_SRC_PREFIX_P is true then some symbol references will be
+# prefixed with the filename. When WITH_NEST_PREFIX_P is true then
+# nested subroutine symbols will be prefixed with their parent
+# subroutine scope.
+proc do_bp_tests {with_src_prefix_p with_nest_prefix_p} {
+ global testfile srcfile
+ global int4
+ global hex
+
+ clean_restart ${testfile}
+
+ if { $with_src_prefix_p } {
+ set src_prefix "${srcfile}:"
+ } else {
+ set src_prefix ""
+ }
+
+ if { $with_nest_prefix_p } {
+ set nest_prefix "contains_keyword::"
+ } else {
+ set nest_prefix ""
+ }
+
+ # Test setting up breakpoints and otherwise examining nested
+ # functions before the program starts.
+ with_test_prefix "before start" {
+ foreach entry \
+ [list \
+ [list "increment" "${int4} \\\(${int4}\\\)"] \
+ [list "increment_program_global" "${int4} \\\(void\\\)"] \
+ [list "hidden_variable" "void \\\(void\\\)"]] {
+ set function [lindex $entry 0]
+ set type [lindex $entry 1]
+
+ # Currently referencing symbols using 'info',
+ # 'whatis' and 'ptype' before the program is
+ # started doesn't work. This is the same
+ # behaviour we see in C++ so I don't think this
+ # is a failure, just a limitation in current GDB.
+ if { ${with_nest_prefix_p} } {
+ gdb_test "info symbol ${nest_prefix}${function}" \
+ "${function} in section .*"
+ gdb_test "whatis ${nest_prefix}${function}" "type = ${type}"
+ gdb_test "ptype ${nest_prefix}${function}" "type = ${type}"
+ gdb_test "print ${nest_prefix}${function}" "{${type}} $hex <contains_keyword::${function}>"
+ }
+
+ gdb_breakpoint "${src_prefix}${nest_prefix}${function}"
+ }
+ }
+
+ # Break on a contained function and run to it.
+ if {![runto ${src_prefix}[gdb_get_line_number "pre_init"]]} then {
+ perror "couldn't run to breakpoint pre_init"
+ continue
+ }
+
+ # Call a contained function.
+ if { ${with_nest_prefix_p} } {
+ gdb_test "call ${nest_prefix}subroutine_to_call()" " called"
+ }
+
+ # Break on another contained function and run to it.
+ gdb_breakpoint "${src_prefix}${nest_prefix}increment"
+ gdb_continue_to_breakpoint "increment" ".*increment = i \\\+ 1"
+ gdb_breakpoint ${src_prefix}[gdb_get_line_number "post_increment"]
+ gdb_continue_to_breakpoint "post_increment"
+
+ # Check arguments and locals report the correct values. 12 is
+ # passed in and 13 is the result after adding 1.
+ gdb_test "info args" "i = 12"
+ gdb_test "info locals" " = 13"
+
+ # Check we can see variables from an outer program scope.
+ gdb_breakpoint ${src_prefix}[gdb_get_line_number "post_increment_global"]
+ gdb_continue_to_breakpoint "post_increment_global" \
+ ".*print \\\*, program_i ! post_increment_global"
+ gdb_test "info args" "No arguments." \
+ "no argument subroutine has no arguments"
+ gdb_test "p program_i" " = 7" "printing outer scoped variable"
+
+ # Stepping into a contained subroutine.
+ gdb_breakpoint ${src_prefix}[gdb_get_line_number "pre_step"]
+ gdb_continue_to_breakpoint "pre_step" ".*call step\\\(\\\) ! pre_step"
+ gdb_test "step" \
+ ".*print '\\\(A\\\)', \\\"step\\\" ! post_step" \
+ "step into the correct place"
+
+ # Local hides program global.
+ gdb_breakpoint ${src_prefix}[gdb_get_line_number "post_hidden"]
+ gdb_continue_to_breakpoint "post_hidden" \
+ ".*print \\\*, program_i ! post_hidden"
+ gdb_test "p program_i" " = 30" "printing hidden global"
+
+ # Check that the methods in the container module still require the
+ # module name as context.
+ gdb_test_no_output "set confirm off"
+ gdb_test "break print_from_module" \
+ "Function \\\"print_from_module\\\" not defined."
+
+ # Check info symbol, whatis and ptype can find information on
+ # these nested functions.
+ foreach entry \
+ [list \
+ [list "increment" "${int4} \\\(${int4}\\\)"] \
+ [list "increment_program_global" "${int4} \\\(void\\\)"]] {
+ set function [lindex $entry 0]
+ set type [lindex $entry 1]
+ with_test_prefix $function {
+ gdb_test "info symbol ${nest_prefix}$function" \
+ "$function in section .*"
+ gdb_test "whatis ${nest_prefix}$function" \
+ "type = ${type}"
+ gdb_test "ptype ${nest_prefix}$function" \
+ "type = ${type}"
+ }
+ }
+}
+
+foreach_with_prefix src_prefix { 0 1 } {
+ # For now this loop is only run with a value of '1'. A later
+ # patch will extend this with the value '0', at which point this
+ # comment will be removed.
+ foreach_with_prefix nest_prefix { 1 } {
+ do_bp_tests ${src_prefix} ${nest_prefix}
+ }
+}
new file mode 100644
@@ -0,0 +1,62 @@
+! Copyright 2019 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/> .
+
+module container
+ implicit none
+ integer :: a
+ contains
+ subroutine print_from_module()
+ print *, "hello."
+ end subroutine
+end module
+
+program contains_keyword
+ use container
+ implicit none
+ integer :: program_i, program_j
+ program_j = 12 ! pre_init
+ program_i = 7
+ program_j = increment(program_j) ! pre_increment
+ program_i = increment_program_global() ! pre_increment_program_global
+ call subroutine_to_call()
+ call step() ! pre_step
+ call hidden_variable()
+ call print_from_module()
+ print '(I2)', program_j, program_i ! post_init
+
+contains
+ subroutine subroutine_to_call()
+ print *, "called"
+ end subroutine
+ integer function increment(i)
+ integer :: i
+ increment = i + 1
+ print *, i ! post_increment
+ end function
+ integer function increment_program_global()
+ increment_program_global = program_i + 1
+ ! Need to put in a dummy print here to break on as on some systems the
+ ! variables leave scope at "end function", but on others they do not.
+ print *, program_i ! post_increment_global
+ end function
+ subroutine step()
+ print '(A)', "step" ! post_step
+ end subroutine
+ subroutine hidden_variable()
+ integer :: program_i
+ program_i = 30
+ print *, program_i ! post_hidden
+ end subroutine
+end program contains_keyword
@@ -30,6 +30,10 @@ if ![runto MAIN__] then {
continue
}
+# Test if we can set a breakpoint in a nested function
+gdb_breakpoint "testnestedfuncs::sub_nested_outer"
+gdb_continue_to_breakpoint "testnestedfuncs::sub_nested_outer" ".*local_int = 19"
+
# Test if we can access local and
# non-local variables defined one level up.
gdb_breakpoint [gdb_get_line_number "! BP_outer"]
@@ -43,6 +47,10 @@ gdb_test "print local_int" "= 19" "print local_int in outer function"
gdb_test "up"
gdb_test "print index" "= 42" "print index at BP1, one frame up"
+# Test if we can set a breakpoint in a nested function
+gdb_breakpoint "testnestedfuncs::sub_nested_inner"
+gdb_continue_to_breakpoint "testnestedfuncs::sub_nested_inner" ".*local_int = 17"
+
# Test if we can access local and
# non-local variables defined two level up.
gdb_breakpoint [gdb_get_line_number "! BP_inner"]
@@ -57,6 +65,18 @@ gdb_continue_to_breakpoint "! BP_outer_2" ".*! BP_outer_2"
gdb_test "print local_int" "= 19" \
"print local_int in outer function, after sub_nested_inner"
+# Test if we can set a breakpoint in public routine with the same name as the internal
+gdb_breakpoint "sub_nested_outer"
+gdb_continue_to_breakpoint "sub_nested_outer" ".*name = 'sub_nested_outer external'"
+
+# Test if we can set a breakpoint in public routine with the same name as the internal
+gdb_breakpoint "sub_with_sub_nested_outer::sub_nested_outer"
+gdb_continue_to_breakpoint "sub_with_sub_nested_outer::sub_nested_outer" ".*local_int = 11"
+
+# Test if we can set a breakpoint in public routine with the same name as the internal
+gdb_breakpoint "mod1::sub_nested_outer"
+gdb_continue_to_breakpoint "mod1::sub_nested_outer" ".*name = 'sub_nested_outer_mod1'"
+
# Sanity check in main.
gdb_breakpoint [gdb_get_line_number "! BP_main"]
gdb_continue_to_breakpoint "! BP_main" ".*! BP_main"
@@ -13,8 +13,62 @@
! You should have received a copy of the GNU General Public License
! along with this program. If not, see <http://www.gnu.org/licenses/>.
-program TestNestedFuncs
+module mod1
+ integer :: var_i = 1
+ integer :: var_const
+ parameter (var_const = 20)
+
+CONTAINS
+
+ SUBROUTINE sub_nested_outer
+ integer :: local_int
+ character (len=20) :: name
+
+ name = 'sub_nested_outer_mod1'
+ local_int = 11
+
+ END SUBROUTINE sub_nested_outer
+end module mod1
+
+! Public sub_nested_outer
+SUBROUTINE sub_nested_outer
+ integer :: local_int
+ character (len=16) :: name
+
+ name = 'sub_nested_outer external'
+ local_int = 11
+END SUBROUTINE sub_nested_outer
+
+! Needed indirection to call public sub_nested_outer from main
+SUBROUTINE sub_nested_outer_ind
+ character (len=20) :: name
+
+ name = 'sub_nested_outer_ind'
+ CALL sub_nested_outer
+END SUBROUTINE sub_nested_outer_ind
+
+! public routine with internal subroutine
+SUBROUTINE sub_with_sub_nested_outer()
+ integer :: local_int
+ character (len=16) :: name
+
+ name = 'subroutine_with_int_sub'
+ local_int = 1
+
+ CALL sub_nested_outer ! Should call the internal fct
+
+CONTAINS
+ SUBROUTINE sub_nested_outer
+ integer :: local_int
+ local_int = 11
+ END SUBROUTINE sub_nested_outer
+
+END SUBROUTINE sub_with_sub_nested_outer
+
+! Main
+program TestNestedFuncs
+ USE mod1, sub_nested_outer_use_mod1 => sub_nested_outer
IMPLICIT NONE
TYPE :: t_State
@@ -22,10 +76,13 @@ program TestNestedFuncs
END TYPE t_State
TYPE (t_State) :: v_state
- integer index
+ integer index, local_int
index = 13
- CALL sub_nested_outer
+ CALL sub_nested_outer ! Call internal sub_nested_outer
+ CALL sub_nested_outer_ind ! Call external sub_nested_outer via sub_nested_outer_ind
+ CALL sub_with_sub_nested_outer ! Call external routine with nested sub_nested_outer
+ CALL sub_nested_outer_use_mod1 ! Call sub_nested_outer imported via module
index = 11 ! BP_main
v_state%code = 27