Section-start not being respected in ELF linker?

Hi all,

I’m having trouble with using ld.lld as a drop in replacement for avr-ld in my scripts.

this is the rather complex link command I’m running:

 "/Users/carlpeto/Library/Developer/Xcode/DerivedData/Swift_For_Arduino-dzhbxpqoovuaqygrxzyqoxidqiqr/Build/Products/Debug/Swift For"  -L"/Users/carlpeto/Library/Application Support/SwiftForArduino/S4A/126/Modules" -L"/Users/carlpeto/Library/Application Support/SwiftForArduino/Extensions/Modules" -L"/Users/carlpeto/Library/Developer/Xcode/DerivedData/Swift_For_Arduino-dzhbxpqoovuaqygrxzyqoxidqiqr/Build/Products/Debug/Swift For" -L"/Users/carlpeto/Library/Developer/Xcode/DerivedData/Swift_For_Arduino-dzhbxpqoovuaqygrxzyqoxidqiqr/Build/Products/Debug/Swift For" --static -Tdata 0x800100 --gc-sections "/Users/carlpeto/Library/Developer/Xcode/DerivedData/Swift_For_Arduino-dzhbxpqoovuaqygrxzyqoxidqiqr/Build/Products/Debug/Swift For" -z dead-reloc-in-nonalloc='.debug_*=0xffffffff' -z dead-reloc-in-
nonalloc='.debug_loc=0xfffffffe' -z dead-reloc-in-nonalloc='.debug_ranges=0xfffffffe' --undefined=_mmcu --section-start=.mmcu=0x910000 -u vfprintf -lprintf_flt -L"/Users/carlpeto/Library/Developer/Xcode/DerivedData/Swift_For_Arduino-dzhbxpqoovuaqygrxzyqoxidqiqr/Build/Products/Debug/Swift For" -Tavr5.xn -o main.elf main.o trace.c.o --defsym=__TEXT_REGION_LENGTH__=32768 --defsym=__DATA_REGION_LENGTH__=2048 "/Users/carlpeto/Library/Developer/Xcode/DerivedData/Swift_For_Arduino-dzhbxpqoovuaqygrxzyqoxidqiqr/Build/Products/Debug/Swift For" -lAVR   -L "/Users/carlpeto/Library/Developer/Xcode/DerivedData/Swift_For_Arduino-dzhbxpqoovuaqygrxzyqoxidqiqr/Build/Products/Debug/Swift For" -lSwift -lSwiftExperimentalRuntime --start-group -lgcc -lm -lc -latmega328p --end-group

…this throws the error…

ld.lld: error: no memory region specified for section '.mmcu'
ld.lld: warning: address (0x8001ca) of section .bss is not a multiple of alignment (4)

…if i change this to use avr-ld then it works fine…

avr-ld  -L"/Users/carlpeto/Library/Application Support/SwiftForArduino/S4A/126/Modules" -L"/Users/carlpeto/Library/Application Support/SwiftForArduino/Extensions/Modules" -L"/Users/carlpeto/Library/Developer/Xcode/DerivedData/Swift_For_Arduino-dzhbxpqoovuaqygrxzyqoxidqiqr/Build/Products/Debug/Swift For" -L"/Users/carlpeto/Library/Developer/Xcode/DerivedData/Swift_For_Arduino-dzhbxpqoovuaqygrxzyqoxidqiqr/Build/Products/Debug/Swift For" --static -Tdata 0x800100 --gc-sections "/Users/carlpeto/Library/Developer/Xcode/DerivedData/Swift_For_Arduino-dzhbxpqoovuaqygrxzyqoxidqiqr/Build/Products/Debug/Swift For" -z dead-reloc-in-nonalloc='.debug_*=0xffffffff' -z dead-reloc-in-nonalloc='.debug_loc=0xfffffffe' -z dead-reloc-in-nonalloc='.debug_ranges=0xfffffffe' --undefined=_mmcu --section-start=.mmcu=0x910000 -u vfprintf -lprintf_flt -L"/Users/carlpeto/Library/Developer/Xcode/DerivedData/Swift_For_Arduino-dzhbxpqoovuaqygrxzyqoxidqiqr/Build/Products/Debug/Swift For" -Tavr5.xn -o main.elf main.o trace.c.o --defsym=__TEXT_REGION_LENGTH__=32768 --defsym=__DATA_REGION_LENGTH__=2048 "/Users/carlpeto/Library/Developer/Xcode/DerivedData/Swift_For_Arduino-dzhbxpqoovuaqygrxzyqoxidqiqr/Build/Products/Debug/Swift For" -lAVR   -L "/Users/carlpeto/Library/Developer/Xcode/DerivedData/Swift_For_Arduino-dzhbxpqoovuaqygrxzyqoxidqiqr/Build/Products/Debug/Swift For" -lSwift -lSwiftExperimentalRuntime --start-group -lgcc -lm -lc -latmega328p --end-group
avr-ld: warning: -z dead-reloc-in-nonalloc=.debug_*=0xffffffff ignored
avr-ld: warning: -z dead-reloc-in-nonalloc=.debug_loc=0xfffffffe ignored
avr-ld: warning: -z dead-reloc-in-nonalloc=.debug_ranges=0xfffffffe ignored

the section that is not being linked correctly (.mmcu) comes from trace.c.o and is supposed to be there, I want it in the final elf. avr-ld does this correctly.

Can anyone suggest what I might be doing wrong?

(p.s. I checked that both are using the same linker script avr5.xn.)

I think we’ll need to see the linker script to be able to tell.

From the error message you’ll have a MEMORY command (MEMORY (LD)). LLD will give an error message if it can’t assign an OutputSection to any memory region. Normally sections are either assigned to a specific region using something like >region or they are matched by section flags such as (rx). It maybe that implicit matching rules are slightly different between linkers.

I don’t think it has anything to do with the --section-start=.mmcu=0x910000, at least as far as I can see with a cursory reading of the code. If .mmcu isn’t in the linker script then I’d expect LLD to treat it as an orphan section and that should follow the previous output section rather than attempting to match flags.

I’d start by checking the section flags of .mmcu and then the flags of the memory regions to see if one matches. Failing that you may have to manually assign it to a named region.

That’s very helpful thank you!

Here’s the linker script…

/* Script for -n */
/* Modified for s4a */
/* Copyright (C) 2014-2020 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.  */
  text   (rx)   : ORIGIN = 0, LENGTH = __TEXT_REGION_LENGTH__
  data   (rw!x) : ORIGIN = 0x800060, 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__
  /* Internal text space or external memory.  */
  .text   :
    /* For data that needs to reside in the lower 64k of progmem.  */
    /* PR 13812: Placing the trampolines here gives a better chance
       that they will be in range of the code that uses them.  */
    . = ALIGN(2);
    __trampolines_start = . ;
    /* The jump trampolines for the 16-bit limited relocs will reside here.  */
    __trampolines_end = . ;
    /* avr-libc expects these data to reside in lower 64K. */
    . = ALIGN(2);
    /* For code that needs to reside in the lower 128k progmem.  */
     __ctors_start = . ;
     __ctors_end = . ;
     __dtors_start = . ;
     __dtors_end = . ;
    KEEP (*(.ctors))
    KEEP (*(.init_array))
    KEEP (*(.dtors))
    /* From this point on, we do not bother about whether the insns are
       below or above the 16 bits boundary.  */
    *(.init0)  /* Start here after reset.  */
    KEEP (*(.init0))
    KEEP (*(.init1))
    *(.init2)  /* Clear __zero_reg__, set up stack pointer.  */
    KEEP (*(.init2))
    KEEP (*(.init3))
    *(.init4)  /* Initialize data and BSS.  */
    KEEP (*(.init4))
    KEEP (*(.init5))
    *(.init6)  /* C++ constructors.  */
    KEEP (*(.init6))
    KEEP (*(.init7))
    KEEP (*(.init8))
    *(.init9)  /* Call main().  */
    KEEP (*(.init9))
    . = ALIGN(2);
    . = ALIGN(2);
    *(.fini9)  /* _exit() starts here.  */
    KEEP (*(.fini9))
    KEEP (*(.fini8))
    KEEP (*(.fini7))
    *(.fini6)  /* C++ destructors.  */
    KEEP (*(.fini6))
    KEEP (*(.fini5))
    KEEP (*(.fini4))
    KEEP (*(.fini3))
    KEEP (*(.fini2))
    KEEP (*(.fini1))
    *(.fini0)  /* Infinite loop after program termination.  */
    KEEP (*(.fini0))
    /* For code that needs not to reside in the lower progmem.  */
    . = ALIGN(2);
    /* For tablejump instruction arrays.  We do not relax
       JMP / CALL instructions within these sections.  */
    _etext = . ;
  }  > text
  .data          :
     PROVIDE (__data_start = .) ;
    *(.rodata)  /* We need to include .rodata here if gcc is used */
    *(.rodata*) /* with -fdata-sections.  */
    . = ALIGN(2);
     _edata = . ;
     PROVIDE (__data_end = .) ;
  }  > data AT> text
  .bss  ADDR(.data) + SIZEOF (.data)   : AT (ADDR (.bss))
     PROVIDE (__bss_start = .) ;
     PROVIDE (__bss_end = .) ;
  }  > data
   __data_load_start = LOADADDR(.data);
   __data_load_end = __data_load_start + SIZEOF(.data);
  /* Global data not cleared after reset.  */
  .noinit  ADDR(.bss) + SIZEOF (.bss)  :  AT (ADDR (.noinit))
     PROVIDE (__noinit_start = .) ;
     PROVIDE (__noinit_end = .) ;
     _end = . ;
     PROVIDE (__heap_start = .) ;
  }  > data
  .eeprom  :
    /* See .data above...  */
     __eeprom_end = . ;
  }  > eeprom
  .fuse  :
  }  > fuse
  .lock  :
  }  > lock
  .signature  :
  }  > signature
  /* Stabs debugging sections.  */
  .stab 0 : { *(.stab) }
  .stabstr 0 : { *(.stabstr) }
  .stab.excl 0 : { *(.stab.excl) }
  .stab.exclstr 0 : { *(.stab.exclstr) }
  .stab.index 0 : { *(.stab.index) }
  .stab.indexstr 0 : { *(.stab.indexstr) }
  .comment 0 : { *(.comment) }   : { *( }
  /* DWARF debug sections.
     Symbols in the DWARF debugging sections are relative to the beginning
     of the section so we begin them at 0.  */
  /* DWARF 1 */
  .debug          0 : { *(.debug) }
  .line           0 : { *(.line) }
  /* GNU DWARF 1 extensions */
  .debug_srcinfo  0 : { *(.debug_srcinfo) }
  .debug_sfnames  0 : { *(.debug_sfnames) }
  /* DWARF 1.1 and DWARF 2 */
  .debug_aranges  0 : { *(.debug_aranges) }
  .debug_pubnames 0 : { *(.debug_pubnames) }
  /* DWARF 2 */
  .debug_info     0 : { *(.debug_info .gnu.linkonce.wi.*) }
  .debug_abbrev   0 : { *(.debug_abbrev) }
  .debug_line     0 : { *(.debug_line .debug_line.* .debug_line_end) }
  .debug_frame    0 : { *(.debug_frame) }
  .debug_str      0 : { *(.debug_str) }
  .debug_loc      0 : { *(.debug_loc) }
  .debug_macinfo  0 : { *(.debug_macinfo) }
  /* SGI/MIPS DWARF 2 extensions */
  .debug_weaknames 0 : { *(.debug_weaknames) }
  .debug_funcnames 0 : { *(.debug_funcnames) }
  .debug_typenames 0 : { *(.debug_typenames) }
  .debug_varnames  0 : { *(.debug_varnames) }
  /* DWARF 3 */
  .debug_pubtypes 0 : { *(.debug_pubtypes) }
  .debug_ranges   0 : { *(.debug_ranges) }
  /* DWARF Extension.  */
  .debug_macro    0 : { *(.debug_macro) }
  .debug_addr     0 : { *(.debug_addr) }

I can modify it, that’s not a problem. But I was a bit unsure how to handle the fact that this section should end up in the final ELF but will never actually be loaded into memory (flash/RAM/eeprom). It is not used in a normal program (uploaded onto the microcontroller flash/eeprom), it is read only when the program is being run in a simulator (simavr).

So in my mind that means that it shouldn’t have a MEMORY section? Or maybe I’m understanding them wrong?

I notice the debug_ sections don’t seem to have MEMORY assigned to them either, and they seem like a very close analogue to what I’m trying to get working?

I might not understand the flags properly, does this help?

Carls-MacBook-Pro:neopixel test carlpeto$ avr-objdump -x trace.c.o 

trace.c.o:     file format elf32-avr
architecture: avr:5, flags 0x00000010:
start address 0x00000000

Idx Name          Size      VMA       LMA       File off  Algn
  0 .text         00000000  00000000  00000000  00000034  2**2
  1 .mmcu         00000094  00000000  00000000  00000034  2**0
00000000 l    df *ABS*	00000000 trace.c
00000000 g     O .mmcu	00000042 _AVR_MMCU_TAG_NAME
00000042 g     O .mmcu	00000006 _AVR_MMCU_TAG_FREQUENCY4
00000048 g     O .mmcu	00000002 _mmcu
0000004a g     O .mmcu	0000004a _mytrace
00000000         *UND*	00000000 __do_copy_data
00000000         *UND*	00000000 __do_clear_bss

Thanks for the details. The important flag is ALLOC which is the ELF flag SHF_ALLOC (Sections)

The section occupies memory during process execution. Some control sections do not reside in the memory image of an object file; this attribute is off for those sections.

The .debug.* sections don’t have the SHF_ALLOC flag so they are not assigned to a MEMORY region.

So it looks like .mmcu doesn’t have the right section flags to be metadata like .debug. With the flags it has, the linker will treat it as read-only data. I would expect .mmcu to be treated as an orphan as it doesn’t match any pattern in the linker script, it may be that where LLD ends up placing the section is awkward for assigning to a memory region.

I don’t know enough about AVR to know where the .mmcu section comes from. If it is assembler generated then you can look at the .section directive and remove the “a” flag Section (Using as)

If the section flags can’t be altered, my advice would be to put the .mmcu in its own Output section in the script, it will take up some extra space in the program, but hopefully will still work.

That’s very useful and informative! The section is being defined by an attribute in a macro in C code: #define _MMCU_ __attribute__((section(".mmcu"))). It’s not AVR specific really, it’s specific to this code and it’s a mechanism used by the simavr simulator to mark its metadata. Unfortunately I don’t think that approach allows me to specify the section flags? Maybe I should add an output section in the linker script and set the section flags in the linker script somehow?

As to how it works on ld, maybe the linker is more permissive in some way with orphan sections?

I’ve attempted to make a trivial example to see if I can get LLD to fail with the same error message but I’ve not been able to. I’m using a relatively recent LLD built from source so one thing worth trying is the latest version of LLD. It could just be that my trivial example isn’t enough to trigger the problem though.
My trivial example:

        .word 0

        .word 0

        .word 0

        .section .mmcu, "a"
        .word 0

With linker script:

  text (rx) : ORIGIN = 0, LENGTH = 10M
  data (rw!x) : ORIGIN = 0x800060, LENGTH = 128K

  .text : { *.text } >text
  .data : { *.data } >data
  .bss  : { *.bss } >data

With ld.bfd (what I’m assuming avr-ld is based on)

text             0x0000000000000000 0x0000000000a00000 xr
data             0x0000000000800060 0x0000000000020000 rw !x
*default*        0x0000000000000000 0xffffffffffffffff

Linker script and memory map

LOAD memregion.o

.text           0x0000000000000000        0x2
 .text          0x0000000000000000        0x2 memregion.o

.iplt           0x0000000000000008        0x0
 .iplt          0x0000000000000008        0x0 memregion.o

.data           0x0000000000800060        0x2
 .data          0x0000000000800060        0x2 memregion.o

.got            0x0000000000800068        0x0
 .got           0x0000000000800068        0x0 memregion.o

.got.plt        0x0000000000800068        0x0
 .got.plt       0x0000000000800068        0x0 memregion.o

.igot.plt       0x0000000000800068        0x0
 .igot.plt      0x0000000000800068        0x0 memregion.o

.bss            0x0000000000800062        0x2
 .bss           0x0000000000800062        0x2 memregion.o
Address of section .mmcu set to 0x910000
OUTPUT(a.out elf64-x86-64)

.mmcu           0x0000000000910000        0x2
 .mmcu          0x0000000000910000        0x2 memregion.o

With lld

             VMA              LMA     Size Align Out     In      Symbol
               0                0        2     4 .text
               0                0        2     4         memregion.o:(.text)
               0                0       13     1 .comment
               0                0       13     1         <internal>:(.comment)
               0                0       18     8 .symtab
               0                0       18     8         <internal>:(.symtab)
               0                0       3b     1 .shstrtab
               0                0       3b     1         <internal>:(.shstrtab)
               0                0        1     1 .strtab
               0                0        1     1         <internal>:(.strtab)
          910000           910000        2     1 .mmcu
          910000           910000        2     1         memregion.o:(.mmcu)
          800060           800060        2     1 .data
          800060           800060        2     1         memregion.o:(.data)
          800062           800062        2     1 .bss
          800062           800062        2     1         memregion.o:(.bss)

Doing some searching of .mmcu on the web I came across a few things:
The .mmcu section seems to have the SHF_ALLOC flag in all cases. I think that this will be the case from C-code as I don’t think that there is a way of writing non SHF_ALLOC data in C without using inline assembly.

2.2. Tips — SimAVR 1.0.0 documentation which has:

  • set location of .mmcu without that, you get a avr-ld: section .mmcu loaded at [XXX,YYY] overlaps section .data loaded at [XXX,YYY]:


Looking at the memory map in the linker script it seems like the highest memory region user_signatures starts at 0x850000 and has a default length of 1024 bytes. 0x910000 would be outside all of the memory regions. This looks like it is trying to place something outside the memory map, possibly by convention avrsim won’t load this memory?

One way of dealing with this would be to make .mmcu explicit with something like:

  text (rx) : ORIGIN = 0, LENGTH = 10M
  data (rw!x) : ORIGIN = 0x800060, LENGTH = 128K
  mmcu (r) : ORIGIN=0x9100000, LENGH=1K

  .text : { *.text } >text
  .data : { *.data } >data
  .bss  : { *.bss } >data
  .mmcu : { *.mmcu } > mmcu

You’d then not need the section-start.

I’d hope that would produce the same ELF file as orphan placement + a section start.

Interesting stuff. Thanks so much for the extremely comprehensive reply!

I’ll try the example you wrote above with my version of lld first to see if it’s effectively a linker bug due to me having older code. (Although I think mine is only 12 to 20 months old.)

It’ll have to be next week now, when I finally get some personal dev time on holiday. :joy:

I think what you’re saying about shf_alloc being unavoidable in C code sections makes sense. After all C is not assembly language.

It’s fairly trivial to adopt assembly in my toolchain and might be useful. So I can always go that route.

Also your linker script with a defined memory area for mmcu seems the right approach. I had also tried that my end last week and it seemed to work. I guess this is my naivety about linker scripts, I thought I shouldn’t put sections into a memory section if I don’t want to actually end up with them in memory?

But in reality in my toolchain, when building firmware, I copy only specific sections into the BIN or HEX files for upload. My Makefile already skips over any sections that don’t match the right pattern. So I don’t think the new memory segment would ever end up in real firmware copied to a real device.

Will get back early next week with the results of my experiments. Exciting! :slight_smile:

Hi, apologies, I was hoping to get a chance to try this on holiday but my time got taken up by bugs in more core features. In the end, the linker script workaround you suggested seems to be giving me what I needed and I can’t update my linker to modern code because it causes other issues (I’ll be writing a separate question about that on this forum shortly!)

So what I’m going with is using my existing 15 month or so old lld code, which works for everything else I need and just a slightly modified linker script to handle these unusual sections that I only need for simavr special functions. That’s a perfectly good way to work for now and should last me a good long time until I feel like doing more digging!

Thanks so much for your help, we can “mark this as resolved”, if there is such a thing!