3

On ARM GCC (plain C code), when I declare a constant as in

__attribute__((used,section(".rodata.$AppID")))
const uint8_t   ApplicationID[16] = {
        0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00,
        0x12, 0x34, 0x00, 0x00
};

and I don't refer to it in the code, it's optimized out, and listed in Discarded input sections on map file. It's included in binary output only if I refer to it, somewhere else in the sources.

Shouldn't the "used" tag alone be enough? In the GCC manual (6.34.1 Common Variable Attributes) I read:

used

This attribute, attached to a variable with static storage, means that the variable must be emitted even if it appears that the variable is not referenced.

The meaning is to have it at a fixed memory address, in the specified section, for a separate application to check for it

I'm running ARM GCC as provided with NXP MCUXpresso 11.1, reporting verbose version as

GNU C17 (GNU Tools for Arm Embedded Processors 8-2019-q3-update) version 8.3.1 20190703 (release) [gcc-8-branch revision 273027] (arm-none-eabi)
compiled by GNU C version 5.3.1 20160211, GMP version 6.1.0, MPFR version 3.1.4, MPC version 1.0.3, isl version isl-0.18-GMP
AJM
  • 1,317
  • 2
  • 15
  • 30
LuC
  • 347
  • 2
  • 10
  • The map file you reference, is it the one of the linker? -- Did you look into the object file by `objdump`, is the variable included? – the busybee Apr 17 '20 at 05:52
  • @thebusybee The compiler issues a warning for "../src/protocol.c:150:23: warning: unused variable 'ApplicationID' [-Wunused-variable] extern const uint8_t ApplicationID[16];", the linker lists it in discarded sections. Its value is not emitted, so not present when exporting from elf with objcopy/objdump to any format. I would be quite surprised as well, if after being tagged as 'unused'/'discarded' it was still in ELF – LuC Apr 17 '20 at 07:58
  • An error in my comment above: the compiler warning was related to just an "extern" declaration I added, without a reference to it. So the what's worth is from the comment on the linker onwards. As said, if I add any valid reference to variable (like assigning it), linker lists it on map file, and it becomes present in ELF output – LuC Apr 17 '20 at 08:18
  • OK, we now have, correct me if I'm wrong: The compiler is (mostly) happy, the compiled object file contains the variable eventhough it is unused, but it is flagged as **unused**. Consequently the linkers discards it, reports this in its map file, and the variable is not in the resulting binary. – the busybee Apr 17 '20 at 10:45
  • 1
    Well, then you need to look into the linker's options. Unfortunately I'm not that familiar with them, and I'm too busy these times to do it myself. -- Another idea: Do you use a linker script? If so, you might like to extend it to keep the section. If not, it might be the time to copy the used default script, and to edit and to use that copy. – the busybee Apr 17 '20 at 10:45
  • Maybe the LD option "--gc-sections" is pruning too much, even if I flagged the variable as "used". Though I can't avoid it, or the system libraries throw everything in a mayhem. Linker script is nicely managed by the Eclipse IDE, (maybe) I could force a KEEP on that section, hand crafting it. However I liked the idea to explain everything in source code with __attribute__. Then again I will prefer resorting to a dummy reference, than intervening in linker script (as less visible). Browsed quickly LD options, but I can't see anything appropriate to feed on cmd line... – LuC Apr 17 '20 at 13:07

1 Answers1

9

Shouldn't the "used" tag alone be enough?

It is not sufficient, and it is not necessary. It is not relevant.

As per the GCC documentation that you have quoted, attribute used is applicable to definitions of static variables. And as an answer that is now deleted by the author pointed out, your ApplicationID is not static, so attribute used has no effect.

Here:

/* app_id_extern.c */

#include <stdint.h>

const uint8_t   ApplicationID[16] = {
        0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00,
        0x12, 0x34, 0x00, 0x00
};

we have ApplicationID defined, by default, as an extern variable. The default storage class of a filescope variable, like ApplicationID, is extern. The compiler will accordingly generate an object file in which the definition of ApplicationID is exposed for linkage, as we can see:

$ gcc -c app_id_extern.c
$ readelf -s app_id_extern.o

Symbol table '.symtab' contains 10 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     ...
     9: 0000000000000000    16 OBJECT  GLOBAL DEFAULT    4 ApplicationID

In the object file, ApplicationID is a 16-byte GLOBAL symbol in section #4 (which in this case happens to be .rodata). The GLOBAL binding means that the static linker can see this symbol.

And here:

/* app_id_static.c */

#include <stdint.h>

static const uint8_t   ApplicationID[16] = {
        0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00,
        0x12, 0x34, 0x00, 0x00
};

we have ApplicationID explicitly defined as a static variable. The compiler will accordingly generate an object file in which the definition of ApplicationID is not exposed for linkage, as we can also see:

$ gcc -c app_id_static.c
$ readelf -s app_id_static.o

Symbol table '.symtab' contains 10 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     ...
     6: 0000000000000000    16 OBJECT  LOCAL  DEFAULT    4 ApplicationID
     ...

In this object file, ApplicationID is a 16-byte LOCAL symbol in the .rodata section The LOCAL binding means that the static linker cannot see this symbol.

The compiler will always emit in the object file a definition of an extern variable, like that of ApplicationID in app_id_extern.c, even if that definition is not referenced in the object file, because the external definition will be available to the linker, and therefore might by be referenced at linktime from other object files, for all that the compiler can possibly know.

But if a variable is static, then the compiler knows that its definition is unavailable for linkage. So if it can determine that the definition is not referenced within the object file itself, it may conclude that the definition is redundant and not emit it in the object file at all. Like so:

$ gcc -O1 -c app_id_static.c

This time, we ask the compiler to perform minimal optimizations. And then

$ readelf -s app_id_static.o

Symbol table '.symtab' contains 8 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND
     1: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS app_id_static.c
     2: 0000000000000000     0 SECTION LOCAL  DEFAULT    1
     3: 0000000000000000     0 SECTION LOCAL  DEFAULT    2
     4: 0000000000000000     0 SECTION LOCAL  DEFAULT    3
     5: 0000000000000000     0 SECTION LOCAL  DEFAULT    5
     6: 0000000000000000     0 SECTION LOCAL  DEFAULT    6
     7: 0000000000000000     0 SECTION LOCAL  DEFAULT    4

the unreferenced definition of ApplicationID is no longer present in the object file at all. It was optimized out.

Now for certain unusual applications we may want the compiler to emit the definition of a symbol in an object file that does not refer to it, and conceal it from the static linker. That is where attribute used comes into play:

/* app_id_static_used .c */

#include <stdint.h>

static const uint8_t   ApplicationID[16] __attribute__((used,section(".rodata.$AppID"))) = {
        0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00,
        0x12, 0x34, 0x00, 0x00
};

Once again we compile with -O1 optimization:

$ gcc -O1 -c app_id_static_used.c
$ readelf -s app_id_static_used.o

Symbol table '.symtab' contains 10 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     ...
     6: 0000000000000000    16 OBJECT  LOCAL  DEFAULT    4 ApplicationID
     ...

But this time, thanks to attribute used, the LOCAL definition of ApplicationID reappears in section #4 (which in this object file is .rodata.$AppID)

That is how attribute used works. It affects the behaviour of the compiler: it has no influence on the linker.

We haven't done any linkage yet. Let's do some now.

/* hello_world.c */

#include <stdio.h>

int main(void)
{
    puts("Hello world!")
    return 0;
}

This program makes no reference to ApplicationID, but we'll input app_id_static_used.o to the linkage regardless:

$ gcc -O1 -c hello_world.c
$ gcc -o hello hello_world.o app_id_static_used.o -Wl,-gc-sections,-Map=mapfile.txt

In the linkage, I have asked for unused input sections to be dropped, and for a mapfile to be output (-Wl,-gc-sections,-Map=mapfile.txt)

In the mapfile we find:

Mapfile.txt

...
Discarded input sections
  ...
  .rodata.$AppID
                0x0000000000000000       0x10 app_id_static_used.o
  ...

The linker has discarded section .rodata.$AppID input from app_id_static_used.o because no symbol defined in that section is referenced in the program. With attribute used, we compelled the compiler to emit the definition of that static symbol in app_id_static_used.o. That doesn't compell the linker to need it, or keep it in the executable.

In we switch from app_id_static_used.c to:

/* app_id_extern_used.c */

#include <stdint.h>

const uint8_t   ApplicationID[16] __attribute__((used,section(".rodata.$AppID"))) = {
        0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00,
        0x12, 0x34, 0x00, 0x00
};

then we're doing what you did, applying attribute used to an extern definition. Attribute used has no effect in that case, because the compiler is bound to emit the extern definition in any case. And the linker will still discard the .rodata.$AppID input section from the executable if the program does not refer to anything in it.

So far, your app-id source file might as well be:

/* app_id_extern_section.c */

#include <stdint.h>

const uint8_t   ApplicationID[16] __attribute__((section(".rodata.$AppID"))) = {
        0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00,
        0x12, 0x34, 0x00, 0x00
};

And what you then need to do is inform the linker that you want the definition of the symbol ApplicationID kept, even if it is not referenced by your program, and if even if unused sections are dropped.

To achieve that, use the linker option --undefined=ApplicationID. This will direct the linker to assume from the start that the linkage of your program has encountered an undefined reference to ApplicationID and compel the linker to find and link its definition, if any input file provides one. Thus:

$ gcc -O1 -c app_id_extern_section.c
$ gcc -o hello hello_world.o app_id_extern_section.o -Wl,-gc-sections,--undefined=ApplicationID

Now the program contains the definition of ApplicationID, despite not referring to it:

$ readelf -s hello | grep ApplicationID
    58: 0000000000002010    16 OBJECT  GLOBAL DEFAULT   18 ApplicationID

Section #18 is the .rodata section of the program:

$ readelf --sections hello | grep '.rodata'
  [18] .rodata           PROGBITS         0000000000002000  00002000

Lastly, note that the input section .rodata.$AppID from app_id_extern_section.o has been merged into the output section .rodata, because the linker's default linker script specifies:

.rodata         : { *(.rodata .rodata.* .gnu.linkonce.r.*) }

i.e. all input sections matching .rodata, .rodata.* or .gnu.linkonce.r.* will be output to .rodata. This means that even:

__attribute__((section(".rodata.$AppID")))

is redundant. So the app-id source file might as well simply be the one I started with, app_id_extern.c, and the linkage option --undefined=ApplicationID is all that is necessary to keep the unreferenced symbol in the program. Unless your linker is different in that respect you will find the same.

Mike Kinghan
  • 55,740
  • 12
  • 153
  • 182
  • Thanks for your very detailed answer and analysis: this solves my question! I don't know what made me think that the "used" attribute could flag a behavior for linker as well, maybe I've seen it used somewhere along with "section" specifier and interpreted it the wrong way. Forgive me if I will extremely simplify it with "use --undefined=xxx with linker" :D – LuC Apr 17 '20 at 17:04
  • Just a separate note on section(".rodata.$AppID"): I still need it, as my data has to located at a __fixed__ memory address, not allocated with the rest of ".rodata" of the application. – LuC Apr 17 '20 at 17:09
  • @LuC And are you now finding that your linker *does* merge it into `.rodata` in the executable, or is it remaining distinct as you want? – Mike Kinghan Apr 17 '20 at 18:22
  • It's emitted in the separate .rodata.$AppID, as I expected, at a fixed Flash memory address. I've also found what made me think that attribute "used" was meaningful: it's used by NXP code itself, to declare the interrupt vectors section -- again, this is extern storage, global to the whole application, so it's meaningless even in their code. Note however that they have a KEEP directive in linker script for vectors sections, so even if "used" is... useless, their result is fine – LuC Apr 18 '20 at 14:08