Skip to content

Instantly share code, notes, and snippets.

@phretor

phretor/issue.md Secret

Created November 22, 2022 17:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save phretor/fe79370c2d172cf8e0938ac318717851 to your computer and use it in GitHub Desktop.
Save phretor/fe79370c2d172cf8e0938ac318717851 to your computer and use it in GitHub Desktop.
Linking order problem with virtual/override methods

Hello!

I'm the developer of RFQuack, an RF-hacking library framework and hardware toolkit. The project is based on PlatformIO and I found a linking issue that results in the wrong method being called.

RFQuack depends on (a fork of) RadioLib, which is now 100% aligned with the upstream. RFQuack wraps some classes of RadioLib, adding some more methods and reusing (i.e., using) some existing ones.

Situation

The desired chain of calls is as follows:

RFQRF69::begin() (see code) -> RadioLibWrapper:begin() (see code) -> RF69::begin() (in dep. library RadioLib, see code) -> RF69::setFrequency(float) (in dep. library RadioLib, see code)

Background

The last step is because of this part in RFQRFM69.h:

class RFQRF69 : public RadioLibWrapper<RF69> {
public:
    using RF69::setPreambleLength;
    using RF69::setOutputPower;
    using RF69::setPromiscuousMode;
    using RF69::variablePacketLengthMode;
    using RF69::setCrcFiltering;
    using RF69::setRxBandwidth;
    using RF69::fixedPacketLengthMode;

    using RF69::setFrequency;        // <--- this

    using RF69::setFrequencyDeviation;
    using RF69::getFrequencyDeviation;
    using RF69::setBitRate;

RFQRF69 implements RadioLibWrapper but it uses the generic's implementation of setFrequency(float). The reason for this is that I want RadioLibWrapper to define the interface (minimum methods to be implemented), but leave the developer to either use the generic's implementation or to override.

Problem

However, the linker confuses RF69::setFrequency(float) with virtual RadioLibWrapper::setFrequency(float). So, the runtime call chain of calls is as follows:

RFQRF69::begin() (see code) -> RadioLibWrapper:begin() (see code) -> RF69::begin() (in dep. library RadioLib, see code) -> RadioLibWrapper::setFrequency(float) (in see code)

Possible Explanation

I guess this is due to linking order as per how PlatformIO "decides" to order libraries.

To verify this, I changed RadioLibWrapper.h as follows:

    // virtual int16_t setFrequency(float freq) {
    //   Log.error(F("setFrequency was not implemented."));

    //   return ERR_COMMAND_NOT_IMPLEMENTED;
    // }

And the correct method RF69::setFrequency(float) is linked and resolved at runtime. Because the virtual simply doesn't exist, so the linker couldn't get confused.

The Build Configuration

The build configuration is as follows.

[platformio]
default_envs = featheresp32

[env]
build_unflags = -fno-rtti
framework = arduino

# This installs all requirements from
# RFQuack's `library.json` (RadioLib is not there)
lib_deps =
	file://.

# Add extra libs
lib_extra_dirs =
	lib/RadioLib

custom_nanopb_protos = 
	+<src/rfquack.proto>
custom_nanopb_options = 
	--error-on-unmatched
extra_scripts = 
	pre:scripts/pio/main_cpp_j2.py
monitor_speed = 115200

[env:featheresp32]
platform = platformio/espressif32@^5.2.0
board = featheresp32

Reproduction

I understand it's quite hard to reproduce this bug without proper hardware. However, it can compile anywhere pretty easily through PlatformIO:

git clone --recursive https://github.com/rfquack/RFQuack
cd RFQuack
pip install -r requirements.pip
vim build.env
make clean build

Read more thorough build instructions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment