dwwork
dwwork

Reputation: 858

C++ ELF object file symbol table has function listed twice

I've got a source file that defines a move constructor for a very large class. I compile it with g++ 4.9.2, on a linux system. When I dump the symbol table of the resulting ELF object file, I see 2 listings for the move constructor. Both listings have the same address, the same size, the same type, and the linker links it just fine, with no ODR violations. When I disassemble the object file, I only see one move constructor function. My conclusion is that the symbol table has two entries that point to the same location.

This same behavior also happens for the constructor of this particular class, which is defined in this same source file.

The only compile flag I see that I don't fully understand is '-m64', but I don't know how this would affect the symbol table.

I tried this with g++ 9.2.0 as well, and now I have 3 entries in the symbol table! Two of which point to the same address, and the third points to address 0x0, is in the .text.unlikely section, and is marked as [clone .cold].

Why is this?


edit: I can reproduce this at home with a very small class, actually.

// class.h
class VeryLargeClass
{
    int data;

    public:
    VeryLargeClass(VeryLargeClass&&);
};

// class.cpp
#include "class.h"

VeryLargeClass::VeryLargeClass(VeryLargeClass&& other)
{
    data = other.data;
    other.data = 0;
}

If I compile this with g++ -c -O3 class.cpp -o class.o, and then dump the symbol table with objdump -t class.o | c++filt I get the following: class.o: file format elf64-x86-64

SYMBOL TABLE:
0000000000000000 l    df *ABS*  0000000000000000 main.cc
0000000000000000 l    d  .text  0000000000000000 .text
0000000000000000 l    d  .data  0000000000000000 .data
0000000000000000 l    d  .bss   0000000000000000 .bss
0000000000000000 l    d  .note.GNU-stack    0000000000000000 .note.GNU-stack
0000000000000000 l    d  .eh_frame  0000000000000000 .eh_frame
0000000000000000 l    d  .comment   0000000000000000 .comment
0000000000000000 g     F .text  000000000000000b VeryLargeClass::VeryLargeClass(VeryLargeClass&&)
0000000000000000 g     F .text  000000000000000b VeryLargeClass::VeryLargeClass(VeryLargeClass&&)

Notice how the move constructor shows up twice in the symbol table? I'm guessing I just don't understand something about the ELF format. This was done using g++ 10.2.

Upvotes: 6

Views: 2418

Answers (1)

Employed Russian
Employed Russian

Reputation: 213686

TL;DR: If you want to understand what's going on

  1. don't use objdump to examine ELF files, use readelf instead (see below)
  2. don't demangle names with c++filt -- different symbols can produce the same demangled name (i.e. it's not a one-to-one transformation).

Details:

cat foo.cc

struct Foo {
  Foo(Foo&& f);
  void *p;
};

Foo::Foo(Foo &&f) {
  p = f.p;
  f.p = nullptr;
}
g++ -c foo.cc
objdump -t foo.o | grep Foo | c++filt

0000000000000000 g     F .text  0000000000000028 Foo::Foo(Foo&&)
0000000000000000 g     F .text  0000000000000028 Foo::Foo(Foo&&)

objdump -t foo.o | grep Foo 
0000000000000000 g     F .text  0000000000000028 _ZN3FooC2EOS_
0000000000000000 g     F .text  0000000000000028 _ZN3FooC1EOS_

Note that the symbols are different. You can read about the C1 and C2 constructors here.

P.S. Why should you never use objdump to look at ELF files?

objdump is part of binutils. While binutils are still actively maintained, they were written long before ELF existed, and use libbfd. The latter has an internal data model which can't adequately describe ELF file format.

So when you use objdump on an ELF file, first libbfd parses the file into this internal / inadequate data model, and then objdump presents this model in a human readable form. There is a lot that gets lost in translation.

Upvotes: 4

Related Questions