sdbbs
sdbbs

Reputation: 5384

Prevent gcc from optimization/removal of variables when using -Wl,--gc-sections?

I have an ARM project, where I would like to keep certain unused variables and their data, until the time they are used.

I have seen prevent gcc from removing an unused variable :

__attribute__((used)) did not work for me with a global variable (the documentation does imply it only works on functions) (arm-none-eabi gcc 7), but putting the symbol in a different section via __attribute__((section(".data"))) did work. This is presumably because the linker's is only able to strip symbols when they are given their own section via -fdata-sections. I do not like it, but it worked.

So, I tried this approach, but the variables were not kept - and I think this is because something in that project enables -Wl,--gc-sections during linking. Here is a minimal example showing what I've tried to do (basically the main file only refers to the header where the variables to be "kept" are declared as extern - and other than that, main program has does not use these variables; and then those same variables are defined in a separate .c file):

test.c

#include <stdio.h>
#include "test_opt.h"

const char greeting[] = "Hello World - am used";

int main(void) {
  printf("%s!\n", greeting);
  return 0;
}

test_opt.h

#include <stdint.h>

extern const char mystring[];

struct MyStruct {
  uint16_t param_one;
  uint8_t param_two;
  unsigned char param_three[32];
};
typedef struct MyStruct MyStruct_t;
extern const MyStruct_t mystruct;

mystruct.c

#include "test_opt.h"

const char __attribute__((section(".MYSTRING"))) mystring[] = "Me, mystring, I am not being used";
const MyStruct_t __attribute__((section(".MYSTRUCT"))) mystruct = {
  .param_one = 65535,
  .param_two = 42,
  .param_three = "myStructer here",
};

Test with usual MINGW64 gcc

Let's first try without -Wl,--gc-sections:

$ gcc -Wall -g  mystruct.c test_opt.c -o test_opt.exe

$ strings ./test_opt.exe | grep -i 'mystring\|mystruct'
Me, mystring, I am not being used
*myStructer here
mystring
MyStruct
MyStruct_t
mystruct
mystruct.c
mystruct.c
mystruct.c
mystruct.c
mystring
mystruct
.MYSTRING
.MYSTRUCT
.MYSTRING
.MYSTRUCT

Clearly, variables and content are visible here.

Now let's try -Wl,--gc-sections:

$ gcc -Wall -g -Wl,--gc-sections mystruct.c test_opt.c -o test_opt.exe

$ strings ./test_opt.exe | grep -i 'mystring\|mystruct'
mystring
MyStruct
MyStruct_t
mystruct
mystruct.c
mystruct.c
mystruct.c
mystruct.c
mystring
mystruct

Apparently, here we still have some symbol debugging info left - but there are no sections, nor data being reported.


Test with ARM gcc

Let's re-do same experiment with ARM gcc - first without -Wl,--gc-sections:

$ arm-none-eabi-gcc -Wall -g test_opt.c mystruct.c -o test_opt.elf -lc -lnosys

$ arm-none-eabi-strings ./test_opt.elf | grep -i 'mystring\|mystruct'
Me, mystring, I am not being used
*myStructer here
mystruct.c
MyStruct_t
MyStruct
mystruct
mystruct.c
mystring
mystruct.c
mystring
mystruct
.MYSTRING
.MYSTRUCT

Same as before, variables, content and section names are visible.

Now let's try with -Wl,--gc-sections:

$ arm-none-eabi-gcc -Wall -g -Wl,--gc-sections test_opt.c mystruct.c -o test_opt.elf -lc -lnosys

$ arm-none-eabi-strings ./test_opt.elf | grep -i 'mystring\|mystruct'

Note that, unlike the previous case, here there is neither any data content left, nor any debugging info/symbol names!


So, my question is: assuming that -Wl,--gc-sections is enabled in the project, and I otherwise do not want to remove it (because I like the functionality otherwise), can I somehow specify in code for some special variables, "keep these variables even if the are unused/unreferenced", in such a way that they are kept even with -Wl,--gc-sections enabled?

Note that adding keep to attributes, say:

const char __attribute__((keep,section(".MYSTRING"))) mystring[] = "Me, mystring, I am not being used";

... and compiling with (or without) -Wl,--gc-sections typically results with compiler warning:

mystruct.c:3:1: warning: 'keep' attribute directive ignored [-Wattributes]
    3 | const char __attribute__((keep,section(".MYSTRING"))) mystring[] = "Me, mystring, I am not being used";
      | ^~~~~

... I guess, because the variables are already declared const if I read that arrow correctly (or maybe because a section is already assumed to be "kept")? So attribute keep is definitely not the answer here ...

Upvotes: 3

Views: 970

Answers (4)

Alexander Surminsky
Alexander Surminsky

Reputation: 11

I've compared how attributes "used" and "retain" affect the content of an assembler file generated by arm-none-eabi-gcc (Arm GNU Toolchain 13.2 rel.1), which, as I thought, must somehow instruct the linker on what should not be discarded.

// Source code part, attributes comparison
const uint8_t A1[100] __attribute__((__used__)); 
const uint8_t A2[100] __attribute__((__retain__)); 
const uint8_t A3[100];

Compiled with options -fdata-sections -ffunction-sections -save-temps. Linked with options --gc-sections. And it seems that gcc just ignores all those attributes.

The picture 1 shows, that unused arrays A1,A2,A3 go into asm-file all in the same way and are placed in sections .rodata.A1...A3 . But on the linking stage, the garbage collector discards those sections. Though "retain" is supposed to retain A2 as GCC doc says.

The possible solution I see to protect the arrays from discarding is to put them into a dedicated section (say .rodata_nogc for constants), and handle this section in linker_script with KEEP command 2.

// Source code part, array protection
const uint8_t A1[100] __attribute__((section(".rodata_nogc"))); 
const uint8_t A2[100] __attribute__((section(".rodata_nogc"))); 
const uint8_t A3[100] __attribute__((section(".rodata_nogc")));

/* Linker script part, .rodata section */
.rodata :
{
  . = ALIGN(4);
  KEEP(*(.rodata_nogc))
  *(.rodata)
  *(.rodata*)
  . = ALIGN(4);
} >FLASH

Upvotes: 1

Fabian T.
Fabian T.

Reputation: 354

--require-defined=symbol worked for me.

Require that symbol is defined in the output file. This option is the same as option --undefined except that if symbol is not defined in the output file then the linker will issue an error and exit. The same effect can be achieved in a linker script by using "EXTERN", "ASSERT" and "DEFINED" together. This option can be used multiple times to require additional symbols.

(https://man.archlinux.org/man/ld.1.en)

Upvotes: 0

yugr
yugr

Reputation: 21878

To inform linker that some variable needs to be preserved you should use the -Wl,--undefined=XXX option:

gcc ... -Wl,--undefined=greeting

Note that __attribute__((used)) is a compiler-only flag to suppress -Wunused-variable warning.

Upvotes: 2

sdbbs
sdbbs

Reputation: 5384

OK - I found something; not ideal, but at least its just a "syntax hack", and I don't have to come up with stupid stuff to do with the structs just so they show up in the executable (and usually even the code I come up with in that case, gets optimized away :)).

I first tried the (void) varname; hack used for How can I suppress "unused parameter" warnings in C? - I left it below just to show it doesn't work.

What ended up working is: basically, just have a static const void* where the main() is, and assign a pointer to the struct to it (EDIT: in the main()!); I guess because of "static const", the compiler will not remove the variable and its section, even with -Wl,--gc-sections. So test_opt.c now becomes:

#include <stdio.h>
#include "test_opt.h"

const char greeting[] = "Hello World - am used";

static const void *fake; //, *fakeB;

int main(void) {
  fake = &mystruct;
  (void) &mystring; //fakeB = &mystring;
  printf("%s!\n", greeting);
  return 0;
}

... and we can test with:

$ arm-none-eabi-gcc -Wall -g -Wl,--gc-sections test_opt.c mystruct.c -o test_opt.elf -lc -lnosys

$ arm-none-eabi-readelf -a ./test_opt.elf | grep -i 'mystring\|mystruct'
  [ 5] .MYSTRUCT         PROGBITS        00013780 013780 000024 00   A  0   0  4
   01     .init .text .fini .rodata .MYSTRUCT .ARM.exidx .eh_frame
     5: 00013780     0 SECTION LOCAL  DEFAULT    5 .MYSTRUCT
   379: 00000000     0 FILE    LOCAL  DEFAULT  ABS mystruct.c
   535: 00013780    36 OBJECT  GLOBAL DEFAULT    5 mystruct

$ arm-none-eabi-strings ./test_opt.elf | grep -i 'mystring\|mystruct'
*myStructer here
mystruct.c
MyStruct_t
mystruct
MyStruct
mystruct.c
mystring
mystruct.c
mystruct
.MYSTRUCT

Note that only mystruct in above example ended up being preserved - mystring still got optimized away.


EDIT: note that if you try to cheat and move the assignment outside of main:

static const void *fake = &mystruct, *fakeB = &mystring;

int main(void) {
...

... then the compiler will see through your shenanigans, and greet you with:

test_opt.c:6:39: warning: 'fakeB' defined but not used [-Wunused-variable]
    6 | static const void *fake = &mystruct, *fakeB = &mystring;
      |                                       ^~~~~
test_opt.c:6:20: warning: 'fake' defined but not used [-Wunused-variable]
    6 | static const void *fake = &mystruct, *fakeB = &mystring;
      |                    ^~~~

... and you're none the better off still.

Upvotes: -1

Related Questions