Attribute "used" stops working when attribute "progmem" is also employed.

Go To Last Post
15 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

First off thanks to all for the help.  I've read several hundred threads over the years from AVRFreaks.net and they have been super helpful.  I have come to recognize users like clawson and abcminiuser among many others as very helpful and professional.

 

The issue:
I want to place a marker in my HEX file at the beginning of a struct so that I can search the file with custom software and observe certain aspects about the firmware.  To be clear, this struct is in no way used in the firmware but simply offers metadata for the file itself.  This allows me to enforce that the productCode matches the device as well as give hints to the user as to the firmware version, etc.  Here is the struct from my header:

struct __attribute__((packed)) ProductKey {
  char const searchKey[10] = { "UniqueKey" };
  uint16_t productCode = 37;
  uint8_t const fwMaj = 1;
  uint8_t const fwMin = 9;
};
typedef struct ProductKey ProductKey;

Then in a cpp file I do the following in global scope:

const ProductKey pk __attribute__((used, progmem));

Unfortunately this doesn't write the struct to the HEX file unless I remove progmem but doing that increases my dynamic memory footprint (SRAM).  So essentially the "used" attribute isn't working as I would expect if progmem is also used.

I can get it to work if I reference one of the members of the struct in an initializer somewhere, as follows:
 

  ...
  char tmp[10];
  strcpy_P(tmp, pk.searchKey);
  ...

 

...but that is cheating with respect to the "used" attribute which just doesn't seem to work in conjunction with progmem.  I suppose I could be satisfied with this approach (I get my RAM back when the variable goes out of scope) but I don't like it.  Am I missing something that could cause issues later?

 

My environment:
I'm working under Arduino IDE with ATmega1284p.  I would rather be working with Atmel Studio (now Microchip Studio) and avoid dependence on the Arduino libraries but there's so much code and not enough time to make the switch.

 

This topic has a solution.
Last Edited: Mon. Feb 22, 2021 - 04:10 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

make a volatile member-

volatile uint8_t const fwMaj =

 

and then-

int main(

    void(pk.fwMaj);

 

 

I think the 'used' is doing its job (you will see then see the pk data in the .s file), but the linker then flushes it away as requested for unused sections. With the above the linker sees the section being used (although not really). The above also means the 'used' attribute is not needed as it IS being used. If you put a KEEP in the linker script for the progmem section, and then use the 'used' attribute on the var, the compiler will keep it because of 'used', and the linker will 'KEEP" it from the unused sections garbage collector (but then if not marked 'used', it never gets to the linker as the compiler already optimized away).

 

Or something.

 

This reply has been marked as the solution. 
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

 

"used" (as an attribute) does not mean what most people seem to hope it may mean - it is not a directive to the linker.

 

The way to retain something that is being discarded is to ask the linker. See --undefined:

 

https://sourceware.org/binutils/...

 

 

-u symbol

--undefined=symbol

 

So build with:

-Wl,-u=pk

 

 

Last Edited: Fri. Feb 19, 2021 - 09:53 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I put this in C (not C++) and it worked (using home-built avr-gcc 8.3.0):

#include <avr/io.h>

struct __attribute__((packed)) ProductKey {
  char const searchKey[10];
  uint16_t productCode;
  uint8_t const fwMaj;
  uint8_t const fwMin;
};
typedef struct ProductKey ProductKey;

const ProductKey pk __attribute__((used, progmem))
  = { "DDDDDDDD@@", 37, 1, 9 };

void main() {
  uint8_t val;
  while (1) {
    val = PORTB.IN;
    PORTC.OUT = val;
  }
}
$ avr-nm y01.elf | grep ' T '
...
000000d0 T _exit
000000c6 T main
000000a0 T pk
$  cat y01.hex 
...
:100090000C9461000C9461000C9461000C9461005C
:1000A000444444444444444440402500010911244C
:1000B0001FBECFEFCDBFDFE3DEBF0E9463000C9415
:1000C00068000C9400008091280480934404FBCFC6
:0400D000F894FFCFD2
:00000001FF

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

MattRW wrote:
and it worked (using home-built avr-gcc 8.3.0):
And you were using -fdata-sections and -gc-sections as compile and link options?

 

When I try your code on Windows (I picked some Xmega at random):

D:\discard>type discard.c
#include <avr/io.h>

struct __attribute__((packed)) ProductKey {
  char const searchKey[10];
  uint16_t productCode;
  uint8_t const fwMaj;
  uint8_t const fwMin;
};
typedef struct ProductKey ProductKey;

const ProductKey pk __attribute__((used, progmem))
  = { "DDDDDDDD@@", 37, 1, 9 };

void main() {
  uint8_t val;
  while (1) {
    val = PORTB.IN;
    PORTC.OUT = val;
  }
}

D:\discard>avr-gcc -mmcu=atxmega128a1u -fdata-sections -Wl,-gc-sections -Wl,-print-gc-sections discard.c -o discard.elf
c:/one/sysgcc/avr/bin/../lib/gcc/avr/5.3.0/../../../../avr/bin/ld.exe: Removing unused section '.progmem.data.pk' in file 'C:\Users\myID\AppData\Local\Temp\cc8n4B4L.o'

D:\discard>avr-nm discard.elf | grep " T "
0000021c T __bad_interrupt
000001fc T __ctors_end
000001fc T __ctors_start
000001fc T __dtors_end
000001fc T __dtors_start
000001fc T __trampolines_end
000001fc T __trampolines_start
00000000 T __vectors
00000244 T _etext
00000240 T _exit
00000220 T main

As you can see the data section is discarded. Now observe with added -u:

D:\discard>avr-gcc -mmcu=atxmega128a1u -fdata-sections -Wl,-gc-sections -Wl,-print-gc-sections -Wl,-u,pk discard.c -o discard.elf

D:\discard>avr-nm discard.elf | grep " T "
0000022a T __bad_interrupt
0000020a T __ctors_end
0000020a T __ctors_start
0000020a T __dtors_end
0000020a T __dtors_start
000001fc T __trampolines_end
000001fc T __trampolines_start
00000000 T __vectors
00000252 T _etext
0000024e T _exit
0000022e T main
000001fc T pk

I said it before and I'll said it again: __attribute__((used)) does not do what people hope it might do. It is NOT an instruction to the linker's garbage collector to not discard a no reference section. You achieve that with -Wl,--unused or the short form -Wl,-u which is an instruction to the linker to say :

 

https://sourceware.org/binutils/...

 

In effect you are making the reference it needs to prevent -gc-sections from garbage collecting it.

Last Edited: Fri. Feb 19, 2021 - 03:25 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

clawson, You are right.   Thanks for pointing that out (again).

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0


I did find another option.  Not totally sure of the consequences if it.  Instead of -Wl,-u,pk I tried -Wl,-gc-keep-exported

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Doesn't that then undo the entire raison d'etre for -fdata-sections ?!?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Yes.  I read as symbols, not sections.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Yeah but all -fdata-sections does is put each symbol in a separate section (so they can be individually discarded if unreferenced)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 2

I would probably just edit the linker script, avr51.xn-

 

     KEEP(*(.progmemKEEP*)) /* added */
     *(.progmem*)

 

then use that section-

const ProductKey pk __attribute__((used, section(".progmemKEEP")));

 

the 'used' attribute will keep the compiler from discarding, and the use of the KEEP section will prevent the linker from unused section discarding.

 

The additional section is harmless if not used (so can put in the 'global' linker script), but it is there when you need data stored in flash that needs outside access, or the access is not obvious. It also means you can specify what you want at the point of creation, and eliminates the need to add command line switches or use other workaround methods.

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thanks again for the help! And sorry for the delay... as I was finishing up my reply I got interrupted by a meeting which lasted too long and Windows decided to update and restart.  sad no no no

 

 

I'll be brief the second time around:

Tried with the example code that clawson had working.

Using Arduino IDE so not easy to add command line switches AFAIK.  What I did was add a file called platform.local.txt to my custom board:

compiler.c.elf.extra_flags=-Wl,-u,pk

I know it added the switch because here's my output when compiling:

"C:\\Program Files (x86)\\Arduino\\hardware\\tools\\avr/bin/avr-gcc" -Os -g -flto -fuse-linker-plugin -Wl,--gc-sections -mmcu=atmega1284p -Wl,-u,pk -o      ...plus a lot of file paths...

All the other switches were added by Arduino IDE.  Honestly I need to research how the compiler and linker work because to me it's just a black box that transforms my source code into a hex file.  Any suggestions on where to start?

I checked out the elf file with avr-nm as shown by clawson (I installed grep first).  There was no entry for pk with a T, there was with a U:
 

C:\Users\...\arduino_build_301318>avr-nm DevTalk_Demo.ino.elf | grep " T "
000000e2 T __bad_interrupt
0000008e T __ctors_end
0000008c T __ctors_start
0000009a T __do_clear_bss
000000aa T __do_copy_data
000000c4 T __do_global_ctors
0000008e T __dtors_end
0000008e T __dtors_start
00001088 T __tablejump2__
0000008c T __trampolines_end
0000008c T __trampolines_start
000007c0 T __vector_18
00000754 T __vector_20
00000700 T __vector_21
00000000 T __vectors
000012fa T _etext
000012f6 T _exit
00001078 T eeprom_read_byte
000011d2 T free
000008fc T main
0000109a T malloc
000012e4 T memcpy

C:\Users\...\arduino_build_301318>avr-nm DevTalk_Demo.ino.elf | grep " U "
         U pk

So that's where I'm at now.  At this point, I'm assuming it's something to do with Arduino or those compiler switches that the IDE added that I don't understand. 

 

I'm also okay leaving my solution as it is in the original post because it works as long as I provide a reference... I just wanted to know why and that has been satisfactorily answered.

Last Edited: Sat. Feb 20, 2021 - 01:51 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I would probably just edit the linker script, avr51.xn-

 

     KEEP(*(.progmemKEEP*)) /* added */
     *(.progmem*)

 

then use that section-

const ProductKey pk __attribute__((used, section(".progmemKEEP")));

 

the 'used' attribute will keep the compiler from discarding, and the use of the KEEP section will prevent the linker from unused section discarding.

 

The additional section is harmless if not used (so can put in the 'global' linker script), but it is there when you need data stored in flash that needs outside access, or the access is not obvious. It also means you can specify what you want at the point of creation, and eliminates the need to add command line switches or use other workaround methods.

 

This looks promising! 

 

I have no idea what the linker script is or where it is.  

 

As I just posted, I'll need to do some self-education on this subject.

Last Edited: Sat. Feb 20, 2021 - 01:48 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

> I have no idea what the linker script is or where it is

 

avr/lib/ldscripts/avr51.xn

 

The linker script is a 'guide map' for the linker to use when it assembles the various compiled pieces together into the final elf file (where you hex file comes from). As soon as you open up a linker script file it will probably become more obvious what is going on even though it has its own syntax that takes some learning. You don't have to become an expert in its use, but the more you know the easier it is to solve problems that are otherwise difficult. Like in this case, one line in the linker script will solve the problem that otherwise could require more work.

 

You can also copy a linker script to the project folder and specify to the linker a specific linker script to use instead of the default one. Sometimes you may want some changes that are project specific so want to leave the default linker script alone. In this case, it does not matter if the default linker script is changed as the addition does nothing when the section is unused.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

curtvm wrote:

> I have no idea what the linker script is or where it is

 

avr/lib/ldscripts/avr51.xn

 

The linker script is a 'guide map' for the linker to use when it assembles the various compiled pieces together into the final elf file (where you hex file comes from). As soon as you open up a linker script file it will probably become more obvious what is going on even though it has its own syntax that takes some learning. You don't have to become an expert in its use, but the more you know the easier it is to solve problems that are otherwise difficult. Like in this case, one line in the linker script will solve the problem that otherwise could require more work.

 

You can also copy a linker script to the project folder and specify to the linker a specific linker script to use instead of the default one. Sometimes you may want some changes that are project specific so want to leave the default linker script alone. In this case, it does not matter if the default linker script is changed as the addition does nothing when the section is unused.


 

Thanks again curtvm!  This works well, is very simple, and as you said the linker script addition is inert unless you define the section in code so it won't mess with my other projects.  Also, the struct is never referenced by the executing code and therefore it stays out of dynamic memory (not added to SRAM).

 

For future reference to others, if you aren't using the ATmega1284p you may need to modify a different linker script.  Which script can be determined by the following table: 
https://github.com/embecosm/avr-gcc/blob/avr-gcc-mainline/gcc/config/avr/avr-mcus.def

 

Linker scripts for the Arduino platform are found at this path for my default setup: C:\Program Files (x86)\Arduino\hardware\tools\avr\avr\lib\ldscripts​​​​​