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

Time:09-29

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

CodePudding user response:

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.

CodePudding user response:

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.

  • Related