conditional statements for linker command language LD
Asked Answered
D

3

5

Are there conditional statements for the GNU LD linker command language?

Context: I am developing firmware for an arm cortex m0+, which consists of a bootloader and an application. Both are compiled and flashed to target in separate projects, but I use a framework with symbolic links to the drivers, makefile and loader scripts so that I can reuse those for every app I make without copying these files for each app. Currently I have two loader files, for bootloader and application (makefile automatically specifies the appropriate one), with memory assigment as follows:

bootloader

MEMORY { 
  flash (rx)  : ORIGIN = 0x00000000, LENGTH = 16K
  ram   (rwx) : ORIGIN = 0x1FFFF000, LENGTH =  16K
}

app

MEMORY { 
  flash (rx)  : ORIGIN = 0x00004000, LENGTH = 112K
  ram   (rwx) : ORIGIN = 0x1FFFF000, LENGTH =  16K
}

Like the makefile, I want to merge these to something like this (using C expressions to clarify)

MEMORY { 
#ifdef(bootloaderSymbol)
  flash (rx)  : ORIGIN = 0x00000000, LENGTH = 16K
#else
  flash (rx)  : ORIGIN = 0x00004000, LENGTH = 112K
#endif
  ram   (rwx) : ORIGIN = 0x1FFFF000, LENGTH =  16K
}
Deracinate answered 4/3, 2016 at 12:45 Comment(1)
Check out some of the Linux kernel linker scripts. They are heavily macro-ized, and always a great example of the kinds of crazy things you can do with GCC and Binutils.Unders
L
5

Although its not its primary purpose, you can always run the C preprocessor (cpp) on your linker scripts:

#if defined(MACHINE1)
#    define TARGET_ADDRESS 0x80000000
#    define SDRAM_START xxx
#    define SDRAM_SIZE yyy
#    define ROMFLAGS   rx
#elif defined(MACHINE2)
#    define TARGET_ADDRESS 0x40000000
#    define SDRAM_START zzz
#    define SDRAM_SIZE  aaa
#    define ROMFLAGS rwx
#else
#    error unknown machine
#endif

MEMORY
{
   rom (ROMFLAGS) : ORIGIN = TARGET_ADDRESS, LENGTH = 0x00100000
   ram (WX) : ORIGIN = SDRAM_START + SDRAM_SIZE - 0x00200000, LENGTH = 0x00100000
   driver_ram (WX) : ORIGIN = SDRAM_START + SDRAM_SIZE - 0x00100000, LENGTH = 0x00100000
}

...

You just need to make sure your macros don't collide with linker script syntax. Then save your linker script as xxx.lk.in (instead of xxx.lk) and add a recipe to your Makefile:

xxx.lk: xxx.lk.in
        $(CPP) -P $(INCLUDE) -D$(MACHINE) $< $@

All that's left to do is to add xxx.lk as dependency to your final executables build recipe. I'm using similar processes on many of my projects successfully.

Leman answered 6/3, 2016 at 8:59 Comment(5)
This method worked! I pulled the following tricks in addition: 1. Add -P to the invocation of the preprocessor so it suppresses line number output. Otherwise, the linker I use (arm-none-eabi-ld) doesn't accept the processed script. 2. I added FORCE to the dependencies as phony dependency because you can change MACHINE without editing xxx.lk.in, which would not trigger re-processing to xxx.lk. However, obviously, changing MACHINE is exactly why you'd want to replace xxx.lk.Deracinate
@joop: the way I handle that is with includefiles. Instead of defining macros directly in the linker script, I just #include separate definition files depending on MACHINE. With a little caution, these can be written in a way I can also (re)use them as "true" C includes having only one single place to care about platform specials. Forgot to mention the -P switch, glad you found it yourself.Leman
Don't have enough reputation to upvote this answer yet, but put everything in a header file too, exactly what I needed!Deracinate
EXTREMELY ingenious! But why .lk for the linker script file extension, instead of the more common .ld?Goulet
@AJM: just a matter of personal taste and to somehow remind myself that this is a generated file (that e.g. isn't supposed to be checked in). I usually name my ordinary "static" linker scripts indeed .ldLeman
W
6

I think you can try "DEFINED(symbol)" according to https://sourceware.org/binutils/docs/ld/Builtin-Functions.html

Also, please don't forget to pass "--defsym=bootloaderSymbol=1" to ld.

MEMORY {
    flash (rx)  : ORIGIN = DEFINED(bootloaderSymbol) ? 0x00000000 : 0x00004000, LENGTH = DEFINED(bootloaderSymbol) ? 112K : 16K
    ram   (rwx) : ORIGIN = 0x1FFFF000, LENGTH =  16K
}
Windshield answered 9/12, 2018 at 13:49 Comment(1)
I confirm that the proposed solution works at least with GNU ld (GNU Tools for Arm Embedded Processors 7-2018-q2-update) 2.30.0.20180329.Ruffina
L
5

Although its not its primary purpose, you can always run the C preprocessor (cpp) on your linker scripts:

#if defined(MACHINE1)
#    define TARGET_ADDRESS 0x80000000
#    define SDRAM_START xxx
#    define SDRAM_SIZE yyy
#    define ROMFLAGS   rx
#elif defined(MACHINE2)
#    define TARGET_ADDRESS 0x40000000
#    define SDRAM_START zzz
#    define SDRAM_SIZE  aaa
#    define ROMFLAGS rwx
#else
#    error unknown machine
#endif

MEMORY
{
   rom (ROMFLAGS) : ORIGIN = TARGET_ADDRESS, LENGTH = 0x00100000
   ram (WX) : ORIGIN = SDRAM_START + SDRAM_SIZE - 0x00200000, LENGTH = 0x00100000
   driver_ram (WX) : ORIGIN = SDRAM_START + SDRAM_SIZE - 0x00100000, LENGTH = 0x00100000
}

...

You just need to make sure your macros don't collide with linker script syntax. Then save your linker script as xxx.lk.in (instead of xxx.lk) and add a recipe to your Makefile:

xxx.lk: xxx.lk.in
        $(CPP) -P $(INCLUDE) -D$(MACHINE) $< $@

All that's left to do is to add xxx.lk as dependency to your final executables build recipe. I'm using similar processes on many of my projects successfully.

Leman answered 6/3, 2016 at 8:59 Comment(5)
This method worked! I pulled the following tricks in addition: 1. Add -P to the invocation of the preprocessor so it suppresses line number output. Otherwise, the linker I use (arm-none-eabi-ld) doesn't accept the processed script. 2. I added FORCE to the dependencies as phony dependency because you can change MACHINE without editing xxx.lk.in, which would not trigger re-processing to xxx.lk. However, obviously, changing MACHINE is exactly why you'd want to replace xxx.lk.Deracinate
@joop: the way I handle that is with includefiles. Instead of defining macros directly in the linker script, I just #include separate definition files depending on MACHINE. With a little caution, these can be written in a way I can also (re)use them as "true" C includes having only one single place to care about platform specials. Forgot to mention the -P switch, glad you found it yourself.Leman
Don't have enough reputation to upvote this answer yet, but put everything in a header file too, exactly what I needed!Deracinate
EXTREMELY ingenious! But why .lk for the linker script file extension, instead of the more common .ld?Goulet
@AJM: just a matter of personal taste and to somehow remind myself that this is a generated file (that e.g. isn't supposed to be checked in). I usually name my ordinary "static" linker scripts indeed .ldLeman
A
1

I've gone down this same path before, and later found out there's an ld command line argument to specify segment origin, which alleviates the need to figure it out in the linker script. From man page:

   -Tbss=org
   -Tdata=org
   -Ttext=org
       Same as --section-start, with ".bss", ".data" or ".text" as the section name.

So in your case, you would have -Ttext=0 (bootloader) or -Ttext=0x00004000 (app)

Acetylide answered 18/3, 2016 at 20:21 Comment(2)
So if I understand correctly what I have right now MEMORY { flash (rx) : ORIGIN = FLASH_ORG, LENGTH = FLASH_LEN } SECTIONS { vectors : { *(.vectors) } >flash would become an invocation of LD, without the linker script, but rather -Tvectors=0x0 ?Deracinate
Yes. This will work as well. Beware it's not (exactly) the same thing, however. MEMORY allows you to tell the linker that memory outside the defined region isn't physically available. You will get an error message if you attempt to generate code outside the defined region. With the segment start addresses, you won't (only if they overlap). MEMORY can avoid headaches if your code eventually outgrows your device's limits.Leman

© 2022 - 2024 — McMap. All rights reserved.