Does it support using a custom linker script through the command line?

I would like to ask if Arduino CLI supports using parameters in the command line and then calling my own custom linker script (i.e., linkerscript.ld file)?

Hi @ryaaannnn. The commands that are ran while compiling a sketch are defined in the configuration files of the platform of the board you are compiling for. These configuration files are a set of properties in a key=value format, but with some advanced capabilities (including being able to reference one property in the definition of another).

It is possible to set the value of these properties via --build-property flags added to the arduino-cli compile command:

https://arduino.github.io/arduino-cli/latest/commands/arduino-cli_compile/#options

This feature allows an advanced user with a good understanding of the configuration of the platform of the board in use to modify the behavior of the compilation so it is possible you could accomplish your goal via this mechanism.

There is a common convention of the platform developer including a set of properties in the compilation commands specifically to make it more convenient for the user to inject additional arguments into the compilation commands. For example, you can see the list of such "extra_flags" properties here in the platform.txt file of the arduino:avr platform here:

Those lines provide the default empty string values, but you can override them via --build-property flags. The properties are then referenced in the appropriate "pattern" properties that serve as the templates from which the compilation commands are generated. For example, note the {compiler.c.elf.extra_flags} property reference in the recipe.c.combine.pattern definition:

https://github.com/arduino/ArduinoCore-avr/blob/1.8.6/platform.txt#L69

## Combine gc-sections, archives, and objects
recipe.c.combine.pattern="{compiler.path}{compiler.c.elf.cmd}" {compiler.c.elf.flags} -mmcu={build.mcu} {compiler.c.elf.extra_flags} {compiler.ldflags} -o "{build.path}/{build.project_name}.elf" {object_files} {compiler.libraries.ldflags} "{build.path}/{archive_file}" "-L{build.path}" -lm

Please note that each platform may be different, so you should carefully study the platform you are using to understand how you might adjust its behavior.

Dear ptillisch,

Thank you very much for your response. I carefully reviewed your reply and the information you mentioned about "--build-property." I found that the main component executing commands in Arduino CLI is usually "avr-gcc" or "avr-g++." Subsequently, I checked the official website of "avr-gcc" and discovered that it supports the use of a custom linker script (i.e., .ld file) by adding a "-T" parameter in the command line.

Therefore, may I preliminarily assume that, with some adjustments, Arduino CLI also supports the use of custom linker scripts? (Because, as mentioned earlier, the main components executing commands in Arduino CLI are usually "avr-gcc" or "avr-g++.")

Additionally, I came across a similar question about "finding linkerscript" on a website, where the final answer seemed to point to a file path under /home/kristof/gnu_avr_toolchain/avr/bin/../lib/ldscripts/avr5.xn. The files at this path have the .xn extension and appear to play a similar role in memory layout. Do you know the substantive differences between .xn files and .ld files? Or are there any specific considerations I should keep in mind when modifying them with my knowledge of .ld files?

kind regards,
Ryaaannnn

Arduino CLI doesn't provide any explicit support for custom linker scripts. It generates and invokes arbitrary commands based on the patterns in the configuration files of the platform of the board being compiled for. It is up to the platform developer (and the advanced user via the --build-property flag) to decide what those commands should be.


If there is something specific you are hoping to accomplish, it would be a good idea for you to provide a detailed description of it.

It is possible this is a case of an "XY problem", and that the forum helpers could advise you of a way to accomplish the "Y" goal without any need for the "X" of devising an Arduino CLI command that will run a custom linker script.

Hi ptillisch, I apologize for the previous vague description of my question. Let me rephrase.

The hardware I'm working with is Arduino Zero.

My concern is: when compiling and uploading a program using Arduino CLI, I want to specify the memory layout of the program, such as the sizes and starting positions of segments like data and text.

What I know now is: avr-gcc supports addressing this issue by allowing users to call their custom memory layout files (custom linker scripts, .ld files) during linking, using the "-T" parameter.

I'd like to ask: in your opinion, does Arduino CLI support such operations? If it does, do you know how to achieve this? Alternatively, if this is a case of an XY problem, do you have any better suggestions?

Looking forward to your response!

Unfortunately I don't have any experience with custom linker scripts. My recommendation is for you to start with a simple experiment.

Create a sketch with the following content:

#ifndef A_COMMAND_LINE_MACRO
#error A_COMMAND_LINE_MACRO is not defined
#endif
void setup() {}
void loop() {}

Now compile it for the Arduino Zero:

$ arduino-cli compile --fqbn arduino:samd:arduino_zero_edbg --verbose

[...]

E:\git\arduino-cli\PERTILLISCH\sketches\000-support\forum\1199591\1199591.ino:2:2: error: #error A_COMMAND_LINE_MACRO is not defined
 #error A_COMMAND_LINE_MACRO is not defined
  ^~~~~

Used platform Version Path
arduino:samd  1.8.13  C:\Users\per\AppData\Local\Arduino15\packages\arduino\hardware\samd\1.8.13
Error during build: exit status 1

As expected, the compilation failed because A_COMMAND_LINE_MACRO was not defined.

Now examine the arduino:samd platform's configuration files. We see that, as with the arduino:avr platform I mentioned above, it provides the set of "extra_flags" properties for our use:

So let's use the compiler.cpp.extra_flags property to inject a custom -D flag into the C++ compilation command:

$ arduino-cli compile --fqbn arduino:samd:arduino_zero_edbg --verbose --build-property "compiler.cpp.extra_flags=-D A_COMMAND_LINE_MACRO"

[...]

Sketch uses 11600 bytes (4%) of program storage space. Maximum is 262144 bytes.
Global variables use 2980 bytes (9%) of dynamic memory, leaving 29788 bytes for local variables. Maximum is 32768 bytes.

Used platform Version Path
arduino:samd  1.8.13  C:\Users\per\AppData\Local\Arduino15\packages\arduino\hardware\samd\1.8.13

This time the compilation was successful because the A_COMMAND_LINE_MACRO macro was defined. If you examine the compilation commands shown in the arduino-cli compile --verbose output, you will see the arm-none-eabi-g++ command used to compile the C++ files included this argument:

-D A_COMMAND_LINE_MACRO

Now this demo doesn't directly apply to what you are trying to accomplish, but it shows that once you understand the platform configuration, it is possible to adjust the compilation commands from the Arduino CLI command line.

So far I have focused on these "extra_flags" properties because they were added by the platform developer specifically for this use case. But you can use the --build-property flag with any platform property. However, this is where an adequate familiarity with the platform properties system and with the configuration of the specific platform in use is important. You are overriding any value for the property that was set in the platform. In the case of the compiler.cpp.extra_flags, etc. properties in the arduino:samd platform, that is no problem because they are set to empty strings in the platform. But what if we decided to use the build.extra_flags property instead?:

$ arduino-cli compile --fqbn arduino:samd:arduino_zero_edbg  --verbose --build-property "build.extra_flags=-D A_COMMAND_LINE_MACRO"

[...]

In file included from C:\Users\per\AppData\Local\Arduino15\packages\arduino\hardware\samd\1.8.13\cores\arduino/Arduino.h:44:0,
                 from C:\Users\per\AppData\Local\Temp\arduino\sketches\623773B7344F47A324F5BE477215E633\sketch\1199591.ino.cpp:1:
C:\Users\per\AppData\Local\Arduino15\packages\arduino\hardware\samd\1.8.13\cores\arduino/WVariant.h:93:36: error: 'TCC_INST_NUM' was not declared in this scope
 extern const void* g_apTCInstances[TCC_INST_NUM+TC_INST_NUM] ;
                                    ^~~~~~~~~~~~
C:\Users\per\AppData\Local\Arduino15\packages\arduino\hardware\samd\1.8.13\cores\arduino/WVariant.h:93:49: error: 'TC_INST_NUM' was not declared in this scope
 extern const void* g_apTCInstances[TCC_INST_NUM+TC_INST_NUM] ;
                                                 ^~~~~~~~~~~
In file included from C:\Users\per\AppData\Local\Arduino15\packages\arduino\hardware\samd\1.8.13\variants\arduino_zero/variant.h:42:0,
                 from C:\Users\per\AppData\Local\Arduino15\packages\arduino\hardware\samd\1.8.13\cores\arduino/Arduino.h:51,
                 from C:\Users\per\AppData\Local\Temp\arduino\sketches\623773B7344F47A324F5BE477215E633\sketch\1199591.ino.cpp:1:
C:\Users\per\AppData\Local\Arduino15\packages\arduino\hardware\samd\1.8.13\cores\arduino/SERCOM.h:147:16: error: expected ')' before '*' token
   SERCOM(Sercom* s) ;
                ^
In file included from C:\Users\per\AppData\Local\Arduino15\packages\arduino\hardware\samd\1.8.13\variants\arduino_zero/variant.h:42:0,
                 from C:\Users\per\AppData\Local\Arduino15\packages\arduino\hardware\samd\1.8.13\cores\arduino/Arduino.h:51,
                 from C:\Users\per\AppData\Local\Temp\arduino\sketches\623773B7344F47A324F5BE477215E633\sketch\1199591.ino.cpp:1:
C:\Users\per\AppData\Local\Arduino15\packages\arduino\hardware\samd\1.8.13\cores\arduino/SERCOM.h:217:3: error: 'Sercom' does not name a type; did you mean 'perror'?
   Sercom* sercom;
   ^~~~~~
   perror
In file included from C:\Users\per\AppData\Local\Arduino15\packages\arduino\hardware\samd\1.8.13\cores\arduino/SafeRingBuffer.h:25:0,
                 from C:\Users\per\AppData\Local\Arduino15\packages\arduino\hardware\samd\1.8.13\cores\arduino/Uart.h:23,
                 from C:\Users\per\AppData\Local\Arduino15\packages\arduino\hardware\samd\1.8.13\variants\arduino_zero/variant.h:43,
                 from C:\Users\per\AppData\Local\Arduino15\packages\arduino\hardware\samd\1.8.13\cores\arduino/Arduino.h:51,
                 from C:\Users\per\AppData\Local\Temp\arduino\sketches\623773B7344F47A324F5BE477215E633\sketch\1199591.ino.cpp:1:
C:\Users\per\AppData\Local\Arduino15\packages\arduino\hardware\samd\1.8.13\cores\arduino/sync.h: In constructor '__Guard::__Guard()':
C:\Users\per\AppData\Local\Arduino15\packages\arduino\hardware\samd\1.8.13\cores\arduino/sync.h:12:22: error: '__get_PRIMASK' was not declared in this scope
  __Guard() : primask(__get_PRIMASK()), loops(1) {
                      ^~~~~~~~~~~~~
C:\Users\per\AppData\Local\Arduino15\packages\arduino\hardware\samd\1.8.13\cores\arduino/sync.h:12:22: note: suggested alternative: '__PRIMAX'
  __Guard() : primask(__get_PRIMASK()), loops(1) {
                      ^~~~~~~~~~~~~
                      __PRIMAX
C:\Users\per\AppData\Local\Arduino15\packages\arduino\hardware\samd\1.8.13\cores\arduino/sync.h:13:3: error: '__disable_irq' was not declared in this scope
   __disable_irq();
   ^~~~~~~~~~~~~
C:\Users\per\AppData\Local\Arduino15\packages\arduino\hardware\samd\1.8.13\cores\arduino/sync.h:13:3: note: suggested alternative: 'uhd_disable_sof'
   __disable_irq();
   ^~~~~~~~~~~~~
   uhd_disable_sof
C:\Users\per\AppData\Local\Arduino15\packages\arduino\hardware\samd\1.8.13\cores\arduino/sync.h: In destructor '__Guard::~__Guard()':
C:\Users\per\AppData\Local\Arduino15\packages\arduino\hardware\samd\1.8.13\cores\arduino/sync.h:17:4: error: '__enable_irq' was not declared in this scope
    __enable_irq();
    ^~~~~~~~~~~~
C:\Users\per\AppData\Local\Arduino15\packages\arduino\hardware\samd\1.8.13\cores\arduino/sync.h:17:4: note: suggested alternative: '_rename_r'
    __enable_irq();
    ^~~~~~~~~~~~
    _rename_r
C:\Users\per\AppData\Local\Arduino15\packages\arduino\hardware\samd\1.8.13\cores\arduino/sync.h:19:4: error: '__ISB' was not declared in this scope
    __ISB();
    ^~~~~
C:\Users\per\AppData\Local\Arduino15\packages\arduino\hardware\samd\1.8.13\cores\arduino/sync.h:19:4: note: suggested alternative: '__FILE'
    __ISB();
    ^~~~~
    __FILE
In file included from C:\Users\per\AppData\Local\Arduino15\packages\arduino\hardware\samd\1.8.13\cores\arduino/USB/USB_host.h:23:0,
                 from C:\Users\per\AppData\Local\Arduino15\packages\arduino\hardware\samd\1.8.13\cores\arduino/Arduino.h:113,
                 from C:\Users\per\AppData\Local\Temp\arduino\sketches\623773B7344F47A324F5BE477215E633\sketch\1199591.ino.cpp:1:
C:\Users\per\AppData\Local\Arduino15\packages\arduino\hardware\samd\1.8.13\cores\arduino/USB/samd21_host.h: At global scope:
C:\Users\per\AppData\Local\Arduino15\packages\arduino\hardware\samd\1.8.13\cores\arduino/USB/samd21_host.h:26:49: error: 'UsbHostDescriptor' does not name a type; did you mean 'EndpointDescriptor'?
 extern __attribute__((__aligned__(4))) volatile UsbHostDescriptor usb_pipe_table[USB_EPT_NUM];
                                                 ^~~~~~~~~~~~~~~~~
                                                 EndpointDescriptor


Used platform Version Path
arduino:samd  1.8.13  C:\Users\per\AppData\Local\Arduino15\packages\arduino\hardware\samd\1.8.13
Error during build: exit status 1

Why did we get all those errors? The property is set to an empty string in platform.txt just like compiler.cpp.extra_flags:

but the value of any property set in platform.txt can be overridden in one of the platform's other configuration files. If we look in boards.txt, we find that the build.extra_flags (which, despite the similar name, was not provided by the platform developer for the convenience of the user who wants to inject arguments into the compilation commands as was the other "extra_flags" properties) was already used by the platform for something important:

https://github.com/arduino/ArduinoCore-samd/blob/1.8.13/boards.txt#L41

arduino_zero_edbg.build.extra_flags=-D__SAMD21G18A__ {build.usb_flags}

so by overriding the value set in the platform, we removed some arguments from the compilation commands and thus broke the code that was dependent on those arguments.

In this case, we probably should have preserved the original value of the property, appending the additional content we wanted:

$ arduino-cli compile --fqbn arduino:samd:arduino_zero_edbg  --verbose --build-property "build.extra_flags=-D__SAMD21G18A__ {build.usb_flags} -D A_COMMAND_LINE_MACRO"

[...]

Sketch uses 11600 bytes (4%) of program storage space. Maximum is 262144 bytes.
Global variables use 2980 bytes (9%) of dynamic memory, leaving 29788 bytes for local variables. Maximum is 32768 bytes.

Used platform Version Path
arduino:samd  1.8.13  C:\Users\per\AppData\Local\Arduino15\packages\arduino\hardware\samd\1.8.13

Hello, ptillisch. I have carefully reviewed your generous and detailed response, and I sincerely appreciate it.
May I confirm if my understanding is correct: as long as I adhere to the rules of the "--build-property" game and gcc(g++) itself supports the functionality I desire (e.g., by adding specific flags on the command line), I can achieve the desired functionality. This is because arudino-cli will ultimately send the flags from "--build-property" to gcc to execute.

kind regards,
Ryaaannnn

I can't confirm or deny because I don't have any experience doing what you are attempting.

Maybe one of the other forum members who is more knowledgeable on the subject matter of custom linker scripts will comment here with some advice.

But for now I recommend you just jump in and give it a try. This is very advanced stuff. If you are set on doing it you are going to need to have the initiative and perseverance to learn how the system works and make it do your bidding.

Nope. Arduino CLI only defines properties according to the --build-property flag. Those properties are only passed to the GCC tools if the platform configuration references the property in the pattern from which the tool's command is generated.

Let's go back to my original demo:

This command only sets the value of the compiler.cpp.extra_flags property to -D A_COMMAND_LINE_MACRO. The reason you then see that flag in the arm-none-eabi-g++ command in the verbose compilation output is because the property is referenced in the recipe.cpp.o.pattern property in the arduino:samd platform's platform.txt file. Note that property value includes the reference {compiler.cpp.extra_flags}.

If the platform developer hadn't added that {compiler.cpp.extra_flags} reference to the recipe.cpp.o.pattern property, then the --build-property flag in the command above wouldn't have any effect at all on the compilation process.

Hello, ptillisch. Thank you very much for your detailed response. I greatly appreciate your guidance and corrections on many details.
You're right, it's time to get hands-on, and I will share any issues I encounter here. Thank you.

kind regards,
Ryaaannnn

Hello, ptillisch. I just noticed a small issue. Could you please check line 104 of the ArduinoCore-samd /platform.txt, possibly at the following link:https://github.com/arduino/ArduinoCore-samd/blob/1.8.13/platform.txt#L104.

I'd like to ask, do you know where the values for the *{build.variant.path}* and {build.ldscript} parameters in the command "{build.variant.path}/{build.ldscript}" are replaced with actual values? I've searched through all possible files, but there's no mention of these parameters. Once I know what the actual values are, I can determine the final actual path. I believe this path corresponds to the custom linker script I've been referring to. This way, I can identify which default linker script is in use. I also noticed that *{compiler.c.elf.extra_flags}* appears here, so it seems there is some hope for using a custom linker script. I hope you can help me with this. Looking forward to your reply.

kind regards,
Ryaaannnn

This property is automatically generated by the Arduino boards platform framework:

https://arduino.github.io/arduino-cli/dev/platform-specification/#platform-terminology:~:text={-,build.variant.path,-}%3A%20The%20path

It is important to understand that some of the properties are just arbitrary containers for data. For example, if I was creating a boards platform, I could add this:

salutation_property=Hello, world!
message_property=My Arduino boards platform has a message for you: {salutation_property} 

Other properties (e.g., build.variant.path) are automatically defined by the framework.

Other properties are manually defined, but are used in special ways by the framework (e.g., recipe.cpp.o.pattern).

You can learn about the latter two from the Arduino Platform Specification. You can assume any property you find in a platform that is not mentioned in the specification is an arbitrary property without any special significance other than the usage you see within the platform configuration files.

This is an arbitrary property which is defined in the boards.txt file:

https://github.com/arduino/ArduinoCore-samd/blob/1.8.13/boards.txt#L42

arduino_zero_edbg.build.ldscript=linker_scripts/gcc/flash_with_bootloader.ld

boards.txt has a special system where the properties have a board ID prefix. Only the properties with the prefix for the currently selected board will be selected. The line I referenced above is the property definition when you are using the arduino:samd:arduino_zero_edbg FQBN in your arduino-cli compile command (or if you have selected "Arduino Zero (Programming Port)" in Arduino IDE). If you were instead using the arduino:samd:arduino_zero_native FQBN then this is the active property definition instead:

https://github.com/arduino/ArduinoCore-samd/blob/1.8.13/boards.txt#L89

arduino_zero_native.build.ldscript=linker_scripts/gcc/flash_with_bootloader.ld

Hi, ptillisch!

Thank you very much for your help; you've really been a great assistance to me. Now I think I know what I need to focus on, which is to try replacing

"flash_with_bootloader.ld"

in

"arduino_zero_edbg.build.ldscript=linker_scripts/gcc/flash_with_bootloader.ld"

with my own custom linker script. Then, I'll test it repeatedly until the entire platform (or the entire framework) can work smoothly. If there are indeed many detailed issues to address (such as coordinating the entire framework), I will do my best to provide an interface in platform.txt after resolving all issues, allowing for the quick replacement of the .ld file (hopefully making it work similarly to the existing "extra_flags" in platform.txt), so that future users can easily utilize this feature.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.