Where is the linkerscript?

In order to integrate PlatformIO into Embeetle IDE (https://embeetle.com), we need to know where to find the linkerscript of any given PIO project.

I extract data from a project through these commands:

  • $ pio project data
  • $ pio run --target envdump

However, nowhere I could find the linkerscript location.

Note: I tried on an Arduino Uno sample project.

In the Uno case, there is no explicit linker script given to the compiler. That can be observed by verbose-compiling (pio run -v) a standard Uno project.

avr-g++ -o .pio\build\uno\firmware.elf -Os -mmcu=atmega328p -Wl,--gc-sections -flto -fuse-linker-plugin .pio\build\uno\src\main.cpp.o -L.pio\build\uno -Wl,--start-group .pio\build\uno\libFrameworkArduinoVariant.a .pio\build\uno\libFrameworkArduino.a -lm -Wl,--end-group

avr-gcc figures out based on the -mmcu flag which script it should default to. As explained in https://www.avrfreaks.net/forum/where-default-linker-script?page=all, adding -Wl,--verbose to the linker command shows verbose information, specifically,

opened script file c:\users\max\.platformio\packages\toolchain-atmelavr\avr\bin\../lib\ldscripts/avr5.xn
using external linker script:
==================================================
/* Script for -n: mix text and data on same page */
/* Copyright (C) 2014-2015 Free Software Foundation, Inc.
   Copying and distribution of this script, with or without modification,
   are permitted in any medium without royalty provided the copyright
   notice and this notice are preserved.  */
OUTPUT_FORMAT("elf32-avr","elf32-avr","elf32-avr")
OUTPUT_ARCH(avr:5)
__TEXT_REGION_LENGTH__ = DEFINED(__TEXT_REGION_LENGTH__) ? __TEXT_REGION_LENGTH__ : 128K;
__DATA_REGION_LENGTH__ = DEFINED(__DATA_REGION_LENGTH__) ? __DATA_REGION_LENGTH__ : 0xffa0;
__EEPROM_REGION_LENGTH__ = DEFINED(__EEPROM_REGION_LENGTH__) ? __EEPROM_REGION_LENGTH__ : 64K;
__FUSE_REGION_LENGTH__ = DEFINED(__FUSE_REGION_LENGTH__) ? __FUSE_REGION_LENGTH__ : 1K;
__LOCK_REGION_LENGTH__ = DEFINED(__LOCK_REGION_LENGTH__) ? __LOCK_REGION_LENGTH__ : 1K;
__SIGNATURE_REGION_LENGTH__ = DEFINED(__SIGNATURE_REGION_LENGTH__) ? __SIGNATURE_REGION_LENGTH__ : 1K;
__USER_SIGNATURE_REGION_LENGTH__ = DEFINED(__USER_SIGNATURE_REGION_LENGTH__) ? __USER_SIGNATURE_REGION_LENGTH__ : 1K;
__DATA_REGION_ORIGIN__ = DEFINED(__DATA_REGION_ORIGIN__) ? __DATA_REGION_ORIGIN__ : 0x800060;
MEMORY
{
  text   (rx)   : ORIGIN = 0, LENGTH = __TEXT_REGION_LENGTH__
  data   (rw!x) : ORIGIN = __DATA_REGION_ORIGIN__, LENGTH = __DATA_REGION_LENGTH__
  eeprom (rw!x) : ORIGIN = 0x810000, LENGTH = __EEPROM_REGION_LENGTH__
  fuse      (rw!x) : ORIGIN = 0x820000, LENGTH = __FUSE_REGION_LENGTH__
  lock      (rw!x) : ORIGIN = 0x830000, LENGTH = __LOCK_REGION_LENGTH__
  signature (rw!x) : ORIGIN = 0x840000, LENGTH = __SIGNATURE_REGION_LENGTH__
  user_signatures (rw!x) : ORIGIN = 0x850000, LENGTH = __USER_SIGNATURE_REGION_LENGTH__
}
[...]

So, PlatformIO itself in this case doesn’t explicitly know what linkerscript is used, it’s handled compiler-internally, in this case, c:\users\max\.platformio\packages\toolchain-atmelavr\avr\lib\ldscripts\avr5.xn

Other platforms make use of explicit linker scripts. The standard is that the linkerscript is either written in the board’s JSON manifest in the buildldscript info, or automatically determined by a builder script and written into env["LDSCRIPT_PATH"].

See documentation and e.g. the STM32Duino PlatformIO builder script.

Note that in this special case, there are even two linkerscripts in play, with -Wl,--default-script. A verbose compilation of a standard nucleo_f103rb Arduino project shows

arm-none-eabi-g++ -o .pio/build/nucleo_f103rb/firmware.elf -T C:/Users/Max/.platformio/packages/framework-arduinoststm32/system/ldscript.ld -Os -mthumb -mcpu=cortex-m3 --specs=nano.specs -Wl,--gc-sections,--relax -Wl,--check-sections -Wl,--entry=Reset_Handler -Wl,--unresolved-symbols=report-all -Wl,--warn-common -Wl,--defsym=LD_MAX_SIZE=131072 -Wl,--defsym=LD_MAX_DATA_SIZE=20480 -Wl,--defsym=LD_FLASH_OFFSET=0x0 -Wl,--default-script C:/Users/Max/.platformio/packages/framework-arduinoststm32/variants/STM32F1xx/F103R(8-B)T/ldscript.ld [...]

So both a

  • -T C:/Users/Max/.platformio/packages/framework-arduinoststm32/system/ldscript.ld
  • and -Wl,--default-script C:/Users/Max/.platformio/packages/framework-arduinoststm32/variants/STM32F1xx/F103R(8-B)T/ldscript.ld

script is used for building. Linkerscripts can be additive and even include each other, leading to such possibilities.

PlatformIO has the capability of outputting the compile commands without actually compiling the project, this is referred to as the compile_commands.json and can also be output by running pio run -t compiledb. Sadly I just found out that that does not include the critical final linker command for the .elf where the used linkerscript would be shown – too bad.

A work-around for this could be to inspect env["LINKFLAGS"], since that is the construction variable used by SCons to construct the linker command.

As shown in pio run -t envdump for a Nucleo F103RB project again, it shows

  'LINKFLAGS': [ '-T',
                 'C:\\Users\\Max\\.platformio\\packages\\framework-arduinoststm32\\system\\ldscript.ld',
                 '-Os',
                 '-mthumb',
                 '-mcpu=cortex-m3',
                 '--specs=nano.specs',
                 '-Wl,--gc-sections,--relax',
                 '-Wl,--check-sections',
                 '-Wl,--entry=Reset_Handler',
                 '-Wl,--unresolved-symbols=report-all',
                 '-Wl,--warn-common',
                 '-Wl,--defsym=LD_MAX_SIZE=131072',
                 '-Wl,--defsym=LD_MAX_DATA_SIZE=20480',
                 '-Wl,--defsym=LD_FLASH_OFFSET=0x0',
                 ( '-Wl,--default-script',
                   'C:\\Users\\Max\\.platformio\\packages\\framework-arduinoststm32\\variants\\STM32F1xx/F103R(8-B)T\\ldscript.ld')],

which clearly show the used linkerflags and the therein contained refernces to both linkerscripts.

1 Like

Hi @maxgerhardt ,
Thank you very much for this quick reply!

Probably I should split the problem in two parts.

1. Linkerscript for Arduino-based boards

I’m aware of the problem with Arduino. When building an Arduino project in the Arduino IDE, I could hardly find the linkerscript (see this post c++ - What linkerscript does Arduino IDE use when compiling for the Arduino UNO? - Stack Overflow)

Eventually I found the linkerscripts for Arduino projects. For the native Embeetle projects (makefile-based), I just copy them into the project. This way, the user has a local linkerscript in his project and is able to modify it at will. Case closed.

For PIO projects, I don’t want to do this. I let go of my earlier plans to modify existing PIO projects. Instead, I want to modify Embeetle IDE such that it can open a PIO project as-is.

That being said, Embeetle IDE would still like to know where the linkerscript is located, just to analyze it. It looks like this is no easy task. It would be very unfortunate if I have to build the project first with full verbosity, then look throughout the build output with regexes to locate the linkerscript. I could do this, but it’s opening a can of worms. What if the build fails? Just to mention one issue…

2. Other platforms

You say:

How can I access that env["LDSCRIPT_PATH"] field?