Understanding Weak Symbols

If a developer closely looks at framework and library files, they may come across functions that have __weak in front of them. The weak symbol is a special linker symbol that denotes a function that can be overridden during link time. In today’s post, we will take quick look at weakly linked functions.

Weak symbols are very useful in libraries and framework code because they provide a default function that can be used to successfully compile the code base if a user does not provide their own function. For example, if you were to examine the code generated from STM32CubeIDE for an STM32 microcontroller, you would find in the processor start-up code something that looks like the following:

  .weak      TIM1_CC_IRQHandler

  .thumb_set TIM1_CC_IRQHandler,Default_Handler

This code is telling the linker to assign the Default_Handler to TIM1_CC_IRQHandler if a developer does not provide a TIM1_CC_IRQHandler function themselves. In fact, if you look through the whole start-up code, you will find similar code for every possible interrupt handler. This allows the code to create a default handler for the developer without requiring the developer to assign a default handler to all interrupts explicitly.

It’s very easy for a developer to override these weak symbols. For example, if I wanted to provide my own custom TIM1_CC_IRQHandler to my application, all I need to do is provide a TIM1_CC_IRQHandler function like the following:

void TIM1_CC_IRQHandler(void)
{
    HAL_TIM_IRQHandler(&htim1);
}

Notice that this function does not have the __weak symbol, it is therefore by default strong and will replace any existing functions of the same name that are defined as weak! This is different behavior compared to if a developer were to define to strong functions with the same name that then results in a linker error.

It’s important to note that the C standards do not mention anything about weak and strong symbols. This is a practical technique that is provided by compilers to simplify code management in a code base. This means that the use of weak symbols is generally not portable friendly and may require rework if you plan to port the code or use multiple compilers.

Weak symbols have several different uses. First, they are often used to define interrupt handlers as we have already seen. Second, they can be used to define callback functions. Callback functions are often used in interrupts or event driven applications to define behaviors that may change from application to application or that may be customized based on configuration. Finally, they can be used for any framework, library or driver function where a developer wants the user with an option to customize their application.

When looking through a code base, all the __weak symbols can leave a developer scratching their head. The good news is that weak symbols are not a complicated topic, and point developers to areas of the code where function replacement and customization are possible.

2 thoughts on “Understanding Weak Symbols”

  1. Using weak symbols may be handy for overriding target dependencies with test-doubles for TDD and test automation. I think it may be a legacy code technique as I think it is preferable to separate functions you want to override from functions you do not want to override into different compilation units.

    If your target compiler does not provide __weak, you can always use a forced include, or command line switch to define __weak as nothing.

    1. What I don’t like about __weak is that it might make the code more error-prone.
      One need to make sure that all the relevant functions are re-defined and the toolchain will not help here (e.g. by saying that some function is undefined.)
      I’ve actually recently written a scanner (in Python) to help manage __weak symbols in OP-TEE. The point is, how one can know when a new weak definition is introduced in a library? (Yes, RTFM. ;-))

      > First, they are often used to define interrupt handlers as we have already seen.
      For me, such a use is only correct for some example code one wants to run and test quickly.
      For production systems, EVERY interrupt handler should be examined, and a proper code shall be prepared for it.
      That is, for unused IRQ-s, code logging that _impossible_ has just happened, should be in place.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.