C++ Constructors (other than in main.cpp)

markwj
Posts: 90
Joined: Tue Mar 08, 2016 5:03 am

C++ Constructors (other than in main.cpp)

Postby markwj » Sun Jun 25, 2017 2:06 pm

I'm seeing a problem with how the ESP IDF build system is setup to use libmain.a. Let me explain with some code:

Here is main.cpp:

Code: Select all

#include <stdio.h>

extern "C"
  {
  void app_main(void);
  }

void app_main(void)
  {
  puts("app_main() runs");
  }

void appmain_ctor (void) __attribute__ ((constructor));

void appmain_ctor (void)
  {
  puts("appmain_ctor() runs");
  }
The result looks like this:
appmain_ctor() runs
app_main() runs
All looks good. So, now add another file to the project, called extra.cpp:

Code: Select all

#include <stdio.h>

void extra_ctor (void) __attribute__ ((constructor));

void extra_ctor (void)
  {
  puts("extra_ctor() runs");
  }
Now, run, and we get this:
appmain_ctor() runs
app_main() runs
Where is the "extra_ctor() runs"?

When I do the same on a standard g++ build on my desktop, I get the correct output. But, then I'm just putting the main.cpp and extra.cpp on the same build command line.

The ESP32 IDF seems to put everything in the 'main' directory into a libmain.a static library:

$ xtensa-esp32-elf-nm build/main/libmain.a
extra.o:
00000000 T _Z10extra_ctorv
U puts

main.o:
00000000 T _Z12appmain_ctorv
00000000 T app_main
U puts
But then that is linked together with all the framework components, to produce the resulting elf/bin firmware.

The issue is that as extra.cpp is not referenced, and not explicitly linked in on the command line like a normal straightforward build, it is just not included in the elf/bin firmware. It behaves like an unused component.

It seems that the gcc people have thought about this and have a '-Wl,--no-whole-archive' option to workaround this, but I can't find a simple way of getting that infront of the '-lmain' on the build command line. We'd only want to do this for libmain.

I know that I can probably work around this by making main.cpp require something from extra.cpp, but those extra.cpp modules are supposed to silently extend the system if they are added to the project, and having to change the main.cpp to reference them is messy.

Any ideas?

ESP_igrr
Posts: 2072
Joined: Tue Dec 01, 2015 8:37 am

Re: C++ Constructors (other than in main.cpp)

Postby ESP_igrr » Sun Jun 25, 2017 4:03 pm

Here's the thing we do for all our unit test components. Since they are never referenced from the test runner directly and rely on constructor attribute, we need to use -Wl,--whole-archive option for them:

https://github.com/espressif/esp-idf/bl ... nent.mk#L1

(same in other test components).

markwj
Posts: 90
Joined: Tue Mar 08, 2016 5:03 am

Re: C++ Constructors (other than in main.cpp)

Postby markwj » Mon Jun 26, 2017 3:21 am

ESP_igrr wrote:Here's the thing we do for all our unit test components. Since they are never referenced from the test runner directly and rely on constructor attribute, we need to use -Wl,--whole-archive option for them:

https://github.com/espressif/esp-idf/bl ... nent.mk#L1

(same in other test components).
Thanks for the hint. That shows me how to use the --whole-archive option nicely.

That is probably workable, but still messy as I'd need to separate out all these uses as components. Not really the structure I was hoping to achieve. I'm just wondering why --whole-archive isn't used for the -lmain in the first place? If the code is in that 'main' directory, it is part of the main package, and presumably always required.

ESP_igrr
Posts: 2072
Joined: Tue Dec 01, 2015 8:37 am

Re: C++ Constructors (other than in main.cpp)

Postby ESP_igrr » Mon Jun 26, 2017 4:36 am

You can use the same line inside component.mk of the main component. Then you can keep all the pluggable modules inside main component and they will be linked into the application.

We don't do this by default for the main component mostly for consistency — main component is no different from the other components, and the default for all the components is --no-whole-archive. That being said, you can use above mentioned line to customize that behavior for your main component.

markwj
Posts: 90
Joined: Tue Mar 08, 2016 5:03 am

Re: C++ Constructors (other than in main.cpp)

Postby markwj » Mon Jun 26, 2017 7:24 am

ESP_igrr wrote:You can use the same line inside component.mk of the main component. Then you can keep all the pluggable modules inside main component and they will be linked into the application.
Tried that, and it worked great.

So, simple one-line solution is to add to main/component.mk:

Code: Select all

COMPONENT_ADD_LDFLAGS = -Wl,--whole-archive -l$(COMPONENT_NAME) -Wl,--no-whole-archive
That will cause all code modules in main component to be always linked in and included in firmware, regardless of whether they are referenced or not. Many thanks.

ESP_igrr
Posts: 2072
Joined: Tue Dec 01, 2015 8:37 am

Re: C++ Constructors (other than in main.cpp)

Postby ESP_igrr » Mon Jun 26, 2017 10:21 am

markwj wrote: That will cause all code modules in main component to be always linked in and included in firmware, regardless of whether they are referenced or not.
Note that the linker will still garbage-collect unused sections (due to -Wl,-gc-sections flag), and because we use -ffunction-sections, -fdata-sections compiler flags, this means that the functions and data not referenced anywhere will not actually be included into the output file.
Global constructors are an exception to this (due to KEEP() statement in the linker script), and since they reference class constructors, class constructors will be pulled into the output file as well, along with anything they reference.

Who is online

Users browsing this forum: Majestic-12 [Bot] and 159 guests