RX automatic interrupt vector entry usage
The GNU linked provides the --gc-sections flag, which removes unused functions from the final binary, thus reducing code size. One of the drawbacks of this technique is that it will by-default remove all interrupt service routines (ISRs).
This is unsurprising, because ISRs are normally not referenced anywhere from within the C code (which is how the GNU linker determines what to remove). However, it is possible to inform the linker not to remove the ISRs, using one of two techniques:
- Marking ISR $tableentry$NN$vect auto-generated symbols with KEEP()
- Annotating ISR C functions with an additional section((".isr-sect")) section, and KEEP()'ing the section
1. Marking ISR $tableentry$NN$vect auto-generated symbols with KEEP()
The advantage of this approach is that it does not require changing the source code, and does not require any modification after initial set-up.
Consider the following two files:
main.c:
#include <stdio.h> int main(void) { int ret; ret = printf("Hello World!\r\n"); return ret; }
int.c:
__attribute__((interrupt(2))) void f(void) { return; }
If we try to link the resultant object files with -Wl,--gc-sections -Wl,--print-gc-sections passed to the GCC invocation, we will find our function removed:
... gcc_8.3.0.202204_rx_elf/bin/../lib/gcc/rx-elf/8.3.0.202204-GNURX/../../../../rx-elf/bin/ld: removing unused section '.text.f' in file 'int.o' ...
This is expected behaviour. We aren't "calling" f from anywhere, and hence the linker deduces that it is unused.
However, when the compiler compiles a function with the interrupt attribute, it will create a special symbol in the output assembly file:
.global _f .type _f, @function _f: .global $tableentry$2$vect $tableentry$2$vect: ; Note: Interrupt Handler push.l r10 ...
Since there are 255 possible ISR vectors, there are a finite number of these symbols that can exist. We can take advantage of this by creating a separate hand-crafted assembler file, called int-guard.s, and populating it with all possible ISR symbol names:
.section .intvecguard .weak $tableentry$0$vect .word $tableentry$0$vect .weak $tableentry$1$vect .word $tableentry$1$vect .weak $tableentry$2$vect .word $tableentry$2$vect ... .weak $tableentry$255$vect .word $tableentry$255$vect
(The weak will be explained a bit later.)
And in our linker script, at the very end we add the following line:
... .intvecguard : { KEEP(*(.intvecguard)) } }
Now the code corresponding to the ISR will be preserved in the final binary, because the dummy symbolic references in the .intvecguard section effectively mark the corresponding function as 'used'.
The reason we marked the symbols as weak, is so that the linker automatically defines the symbol if the user did not create an ISR corresponding to that particular vector. What this means is that the int-guard.s file will work without modification for all possible use cases.
2. Annotating ISR C functions with an additional section((".isr-sect")) section, and KEEP()'ing the section.
This method was documented by user "NoMaY" on the Renesas support forums. The method is as follows:
Annotate all ISR functions with an additional section((".isr-text")) attribute, e.g. If the code normally was
__attribute__((interrupt(2))) void f(void) { }
Then it should become
__attribute__((interrupt(2), section(".isr-text"))) void f(void) { }
Next, simply mark the section as KEEP() within the .text output section of the linker script:
... .text 0xFFE000A0: AT(0xFFE000A0) { *(.text) *(.text.*) *(P) etext = .; KEEP(*(.isr-text)) } > ROM ... }