Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pretty print Fortran dynamic type in gdb

Tags:

gdb

fortran

Printing the values of Fortran allocatable polymorphic variables in gdb is very painful. Given the program below, in order to see the value of alloc_ext, I have to do the following:

(gdb) p alloc_ext
$1 = ( _data = 0x606260, _vptr = 0x400ce0 <__foo_MOD___vtab_foo_My_extended_type> )
(gdb) ptype alloc_ext
type = Type __class_foo_My_base_type_a
PTR TO -> (     Type my_base_type :: _data)
PTR TO -> (     Type __vtype_foo_My_base_type :: _vptr)
End Type __class_foo_My_base_type_a
(gdb) ptype alloc_ext%_data
type = PTR TO -> ( Type my_base_type
character*4 :: base_char
End Type my_base_type )
(gdb) p alloc_ext%_data
$2 = (PTR TO -> ( Type my_base_type )) 0x606260
(gdb) p *(my_extended_type*)(alloc_ext%_data)
$3 = ( my_base_type = ( base_char = 'base' ), extended_char = 'ext ' )

This quickly gets very painful if a derived type contains, say, an array of other polymorphic derived types. I've tried investigating the python pretty printing API, but I still can't seem to get hold of the actual dynamic type, or even the label on the _vptr address, which would be enough information to pretty print something.

I'm using gdb 8.0.1 and gfortran 7.2.1.

MVCE:

module foo
  implicit none

  type my_base_type
     character(len=4) :: base_char = "base"
  end type my_base_type

  type, extends(my_base_type) :: my_extended_type
     character(len=4) :: extended_char = "ext "
  end type my_extended_type

contains

  subroutine bar(arg)
    class(my_base_type), intent(in) :: arg

    print*, "breakpoint here"
    select type(arg)
    type is (my_base_type)
       print*, "my_base_type ", arg%base_char
    type is (my_extended_type)
       print*, "my_extended_type ", arg%base_char, " ", arg%extended_char
    end select
  end subroutine bar
end module foo

program mvce
  use foo
  implicit none

  type(my_base_type) :: base
  type(my_extended_type) :: ext
  class(my_base_type), allocatable :: alloc_base
  class(my_base_type), allocatable :: alloc_ext

  allocate(alloc_base, source=base)
  allocate(alloc_ext, source=ext)

  call bar(alloc_base)
  call bar(alloc_ext)
end program mvce
like image 480
Yossarian Avatar asked Oct 17 '22 03:10

Yossarian


1 Answers

I've made a proof-of-concept that makes this a bit nicer: https://github.com/ZedThree/Fortran-gdb-pp. It's not perfect, but it does demonstrate a way of at least printing dynamic types and see their actual values instead of just the _data and _vptr components. I've included the explanation from the README below.


How does it work?

Unfortunately, we have to work around several limitations of the gdb python API. Firstly, gdb reports the dynamic_type of a polymorphic variable as being its base type, and not its actual dynamic type! This means we need some other way of getting its dynamic type. Luckily, the symbol for the _vptr component (at least with gfortran 7.2) contains the dynamic type, so we can use this. Briefly, we do the following:

  1. Look up symbol for the value's _vptr
  2. Parse the symbol to get the dynamic type
  3. Cast the _data component to a pointer to the dynamic type and dereference

For 1., we need to get the _vptr symbol. We can do this in gdb with info symbol foo%_vptr. The python API lacks such a function, so instead we do:

gdb.execute("info symbol {:#x}".format(int(val['_vptr'])))

int(val['_vptr']) gets the address of _vptr

Next, we need to parse the symbol. With gfortran 7.2, _vptr symbols look like either:

  • __<module name>_MOD___vtab_<module name>_<Dynamic type> for types defined in modules, or
  • __vab_<program name>_<Dynamic type>.nnnn for types defined in programs

Module and program names can contain underscores, but luckily the type starts with a capital letter while everything else is in lower case.

Lastly, we need to actually print the _data component as the dynamic type. While the python API does provide a Value.cast(type) method, the type argument must be a gdb.Type object. No matter, we can use the gdb.lookup_type(name) function... except that this doesn't work with Fortran types. This time, we fallback to using gdb.parse_and_eval:

cast_string = "*({type}*)({address:#x})".format(
    type=real_type, address=int(val['_data']))
real_val = gdb.parse_and_eval(cast_string)

where real_type is a string containing the dynamic type. This basically executes *(<dynamic type>)(value%_data) and then we can pass the resulting value to a pretty printer that just returns str(val), i.e. like the default printer.

like image 74
Yossarian Avatar answered Oct 21 '22 02:10

Yossarian