cgccarmnxp-microcontroller

Setting __attribute__((used)) to C variable/constant has no effect


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

Solution

  • 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.