ARM Cortex-M33 Linker Errors During Compilation of liblc3 Library

When compiling Google’s liblc3 library for the Cortex-M33 target using the arm-none-eabi-gcc toolchain, linker errors frequently arise. These errors are particularly perplexing because the same library compiles successfully for Cortex-M3 and Cortex-M4 targets, which are based on the Armv7-M architecture. The Cortex-M33, however, is based on the Armv8-M architecture, introducing several architectural differences that can lead to subtle but significant issues during compilation and linking. This post delves into the root causes of these linker errors, explores the architectural nuances between Armv7-M and Armv8-M, and provides detailed troubleshooting steps to resolve the issue.


Architectural Differences Between Armv7-M and Armv8-M: Memory Model and Security Extensions

The Cortex-M33 is part of the Armv8-M architecture, which introduces several key differences compared to the Armv7-M architecture used in Cortex-M3 and Cortex-M4 processors. These differences are not merely incremental but represent a significant evolution in the ARM architecture, particularly in terms of memory model, security features, and instruction set enhancements. Understanding these differences is crucial for diagnosing linker errors during compilation.

Memory Model and Address Space

The Armv8-M architecture introduces a more sophisticated memory model compared to Armv7-M. One of the most notable changes is the introduction of the Security Extension, which divides the memory space into Secure and Non-secure regions. This division is enforced by the Memory Protection Unit (MPU) and requires careful handling of memory access permissions. If the linker script or the library being compiled does not account for this division, it can lead to linker errors due to invalid memory region assignments or access violations.

Additionally, the Armv8-M architecture supports a larger address space, which can affect how the linker assigns addresses to symbols and sections. If the linker script is not updated to reflect the expanded address space, it may fail to resolve symbols correctly, leading to errors such as "undefined reference" or "section overlaps."

Security Extensions and TrustZone

The Cortex-M33 incorporates Arm’s TrustZone technology, which provides hardware-enforced isolation between Secure and Non-secure states. This feature is absent in Cortex-M3 and Cortex-M4 processors. TrustZone requires that certain functions and data be explicitly marked as Secure or Non-secure, and the linker must be aware of these markings to generate the correct memory layout. If the library being compiled does not include the necessary annotations or if the linker script does not account for TrustZone, it can result in linker errors related to symbol resolution or memory region conflicts.

Instruction Set and Compiler Flags

The Armv8-M architecture introduces new instructions and enhancements to the Thumb-2 instruction set. While the arm-none-eabi-gcc compiler supports these instructions, the compiler flags used during compilation must be appropriate for the target architecture. For example, the -mcpu=cortex-m33 flag enables support for Armv8-M instructions, but additional flags such as -mcmse (for Cortex-M Security Extensions) may be required if the library or application uses TrustZone features. Omitting these flags can lead to linker errors due to incompatible object files or unresolved symbols.


Linker Script Misconfiguration and Symbol Resolution Issues

Linker errors during compilation for the Cortex-M33 often stem from misconfigurations in the linker script or issues with symbol resolution. These problems are exacerbated by the architectural differences between Armv7-M and Armv8-M, particularly when transitioning from Cortex-M3/M4 to Cortex-M33.

Linker Script Misconfiguration

The linker script (*.ld) defines the memory layout of the target device, including the placement of code, data, and stack sections. When targeting the Cortex-M33, the linker script must account for the Secure and Non-secure memory regions introduced by TrustZone. If the linker script does not define these regions or incorrectly assigns sections to them, the linker will fail to resolve symbols or generate a valid memory map.

For example, a typical linker script for Cortex-M3/M4 might define a single memory region for Flash and RAM. However, for Cortex-M33, the linker script must define separate regions for Secure Flash, Non-secure Flash, Secure RAM, and Non-secure RAM. Failure to do so can result in linker errors such as "section overlaps" or "invalid memory region."

Symbol Resolution Issues

Symbol resolution issues often arise when the library or application being compiled contains references to symbols that are not defined or are incorrectly defined. In the context of Cortex-M33, these issues can be caused by the following factors:

  1. Undefined Secure/Non-secure Symbols: If the library contains functions or variables that are not explicitly marked as Secure or Non-secure, the linker may fail to resolve these symbols when TrustZone is enabled. This can result in errors such as "undefined reference to symbol_name."

  2. Incompatible Object Files: Object files generated for Cortex-M3/M4 may not be compatible with Cortex-M33 due to differences in the instruction set or memory model. If the library being compiled includes pre-built object files for Cortex-M3/M4, the linker may fail to resolve symbols or generate errors related to incompatible object formats.

  3. Missing Compiler Flags: As mentioned earlier, the absence of compiler flags such as -mcmse can lead to symbol resolution issues. These flags ensure that the compiler generates code that is compatible with the Cortex-M33 architecture and TrustZone features.


Resolving Linker Errors: Updating Linker Scripts and Compiler Flags

To resolve linker errors when compiling for Cortex-M33, it is essential to update the linker script and ensure that the correct compiler flags are used. The following steps provide a detailed guide to troubleshooting and fixing these issues.

Step 1: Update the Linker Script for Armv8-M and TrustZone

The first step in resolving linker errors is to update the linker script to account for the architectural differences between Armv7-M and Armv8-M. This includes defining Secure and Non-secure memory regions and ensuring that sections are correctly assigned to these regions.

For example, a basic linker script for Cortex-M33 might look like this:

MEMORY
{
  FLASH (rx)  : ORIGIN = 0x00000000, LENGTH = 512K
  RAM (xrw)   : ORIGIN = 0x20000000, LENGTH = 128K
  FLASH_NS (rx) : ORIGIN = 0x10000000, LENGTH = 512K
  RAM_NS (xrw)  : ORIGIN = 0x30000000, LENGTH = 128K
}

SECTIONS
{
  .text :
  {
    *(.text*)
    *(.rodata*)
  } > FLASH

  .data :
  {
    *(.data*)
  } > RAM

  .bss :
  {
    *(.bss*)
  } > RAM

  .text_ns :
  {
    *(.text_ns*)
    *(.rodata_ns*)
  } > FLASH_NS

  .data_ns :
  {
    *(.data_ns*)
  } > RAM_NS

  .bss_ns :
  {
    *(.bss_ns*)
  } > RAM_NS
}

In this example, the linker script defines separate memory regions for Secure and Non-secure Flash and RAM. The .text, .data, and .bss sections are assigned to the Secure regions, while the .text_ns, .data_ns, and .bss_ns sections are assigned to the Non-secure regions. This ensures that the linker can correctly resolve symbols and generate a valid memory map.

Step 2: Use the Correct Compiler Flags

The second step is to ensure that the correct compiler flags are used during compilation. For Cortex-M33, the following flags are essential:

  • -mcpu=cortex-m33: Enables support for the Cortex-M33 architecture and Armv8-M instructions.
  • -mcmse: Enables support for Cortex-M Security Extensions (TrustZone).
  • -mfloat-abi=hard or -mfloat-abi=softfp: Enables hardware floating-point support if the target device includes a Floating-Point Unit (FPU).

For example, the compilation command might look like this:

arm-none-eabi-gcc -mcpu=cortex-m33 -mcmse -mfloat-abi=hard -o output.elf source.c

These flags ensure that the compiler generates code that is compatible with the Cortex-M33 architecture and TrustZone features.

Step 3: Verify Symbol Definitions and Annotations

The final step is to verify that all symbols in the library or application are correctly defined and annotated for Secure and Non-secure states. This includes:

  1. Marking Functions and Variables as Secure or Non-secure: Functions and variables that are intended to be used in the Secure state must be annotated with the __attribute__((cmse_nonsecure_entry)) attribute. Similarly, Non-secure functions and variables must be annotated with the __attribute__((cmse_nonsecure_call)) attribute.

  2. Checking for Undefined Symbols: Use the arm-none-eabi-nm tool to inspect the object files and verify that all symbols are correctly defined. Look for symbols that are marked as "undefined" or "weak" and ensure that they are resolved in the final binary.

  3. Rebuilding Pre-built Object Files: If the library includes pre-built object files for Cortex-M3/M4, rebuild these files using the correct compiler flags for Cortex-M33. This ensures that the object files are compatible with the target architecture.

By following these steps, you can resolve linker errors when compiling for Cortex-M33 and ensure that the library or application is correctly configured for the Armv8-M architecture and TrustZone features.


In conclusion, transitioning from Armv7-M to Armv8-M introduces several architectural differences that can lead to linker errors during compilation. By understanding these differences, updating the linker script, using the correct compiler flags, and verifying symbol definitions, you can successfully compile and link libraries such as liblc3 for the Cortex-M33 target.

Similar Posts

Leave a Reply

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