I have a large program that's generating object files that are much larger than I expect. My suspicion is that somewhere in the program, someone is using inefficient template metaprogramming that's generating O(n**2) template types. Is there a command-line tool that I can use to list all of the template types that exist in an object file (.o)?
Normally I would suspect nm
or objdump
is the right tool for this kind of thing, but it's not obvious to me what flags to pass to list the template types.
I've verified that the information is in the .o file using this simple test program:
template <typename T, typename... Ts>
struct foo : public foo<Ts...> {};
template <>
struct foo<int> {};
void bar() {
foo<int, int, int, int, int, int, int, int> x;
}
Then running:
gcc -g -c test.cc -o test.o && strings test.o
Outputs:
foo<int, int, int, int, int, int, int, int>
GNU C++17 13.2.0 -mtune=generic -march=x86-64 -g -fasynchronous-unwind-tables
_Z3barv
foo<int, int, int, int, int, int, int>
foo<int, int, int, int>
foo<int, int, int, int, int, int>
foo<int, int, int>
foo<int>
foo<int, int>
foo<int, int, int, int, int>
/tmp
test.cc
/tmp
test.cc
test.cc
GCC: (Debian 13.2.0-10) 13.2.0
test.cc
_Z3barv
.symtab
.strtab
.shstrtab
.text
.data
.bss
.rela.debug_info
.debug_abbrev
.rela.debug_aranges
.rela.debug_line
.debug_str
.debug_line_str
.comment
.note.GNU-stack
.rela.eh_frame
I'm looking for a command that will output foo<int>
, foo<int, int>
, etc. from test.o.
In my previous, now deleted, answer I kicked off with:
By the time you've got to an object file, classes have no representation, therefore classes that instantiate templates have no representation
To which you commented:
Re: "classes that instantiate templates have no representation". I made a simple test.cc file that instantiates many template classes that have no methods or members. If I compile it with debugging information and run strings test.o, I can see the names of the instantiated templates in the object file. They're definitely in there.
Which is so. I should have chosen my words more carefully. Classes have no linkage representation.
But that's quite immaterial if you can readily make an object file containing the strings your're looking
for. The strings you are seeing are DWARF debugging info, which GCC by default packages into the
object the file (or optionally with a separate .dwo
file). I didn't consider getting the debug info, but indeed it is - or it contains -
the information you want.
Naturally you can get at the debug info in a straightforwardly structured format unpolluted with whatever other strings the
object file might contain. If I compile my previous example.cpp
like so:
$ g++ -g -ggnu-pubnames -c example.cpp
then the compiler is instructed (GCC manual: 3.10 Options for Debugging Your Program):
-ggnu-pubnames
Generate .debug_pubnames and .debug_pubtypes sections in a format suitable for conversion into a GDB index. This option is only useful with a linker that can produce GDB index version 7.
Then, the .debug_pubtypes
section will itemise every type that is used in the object file, and they can be
specifically extracted with:
$ readelf --debug-dump=pubtypes example.o | c++filt
Contents of the .debug_gnu_pubtypes section:
Length: 1670
Version: 2
Offset into .debug_info section: 0
Size of area in .debug_info section: 4479
Offset Kind Name
37 g,type std::integral_constant<bool, true>
be9 s,type bool
37 g,type std::integral_constant<bool, true>
b2 g,type std::integral_constant<bool, false>
b2 g,type std::integral_constant<bool, false>
12d g,type std::integral_constant<long unsigned int, 0>
c0b s,type long unsigned int
12d g,type std::integral_constant<long unsigned int, 0>
c21 s,type unsigned char
c28 s,type short unsigned int
c2f s,type unsigned int
c36 s,type long long unsigned int
c42 s,type __int128 unsigned
1a8 g,type std::__make_unsigned_selector_base
1a8 g,type std::__make_unsigned_selector_base
264 s,type std::size_t
c65 s,type signed char
c6c s,type short int
c73 s,type int
c7f s,type long int
c86 s,type long long int
c8d s,type __int128
c94 s,type wchar_t
c9b s,type char16_t
ca2 s,type char32_t
284 g,type std::__is_memcmp_ordered_with<std::byte, std::byte, true>
284 g,type std::__is_memcmp_ordered_with<std::byte, std::byte, true>
d6e s,type long double
d75 s,type double
d7c s,type float
2b8 g,type std::in_place_t
2b8 g,type std::in_place_t
2f3 g,type std::piecewise_construct_t
2f3 g,type std::piecewise_construct_t
32e g,type std::integral_constant<long unsigned int, 2>
32e g,type std::integral_constant<long unsigned int, 2>
cc3 g,type __gnu_cxx::__numeric_traits_integer<long long unsigned int>
cc3 g,type __gnu_cxx::__numeric_traits_integer<long long unsigned int>
d07 g,type __gnu_cxx::__numeric_traits_integer<long unsigned int>
d07 g,type __gnu_cxx::__numeric_traits_integer<long unsigned int>
d3c g,type __gnu_cxx::__numeric_traits_integer<unsigned int>
d3c g,type __gnu_cxx::__numeric_traits_integer<unsigned int>
3b1 g,type std::__array_traits<int, 2>
3b1 g,type std::__array_traits<int, 2>
3de g,type std::array<int, 2>
3de g,type std::array<int, 2>
de9 g,type point<int, 2>
7c5 g,type std::__array_traits<double, 3>
7c5 g,type std::__array_traits<double, 3>
7f2 g,type std::array<double, 3>
7f2 g,type std::array<double, 3>
ee3 g,type point<double, 3>
1177 s,type char
This appears to be exactly what you need, and I am wiser than I was :)