For gcc, it is the same as for ARM. The whole thing comes together in the linker script (.ld file). The linker script contains the information, at which address the code is executed and at which address the code is placed in the binary. It is important that the code is linked to the correct address (in your case, RAM address), otherwise location-dependent parts will not work. For reference you can look how the data section is handled. The global/static variables are placed in the data section and the initial values of those need to be in the flash. You need to the same.
Later, something needs to copy the code to flash, this is usually somewhere right after reset vector (startup.s or similar). Either you find the data section copy code and extend your section there (usually it means adding your section right after data section and moving some end-of-data-section pointer after your section in linker script) or you add your own memcpy at the correct location. The latter may be a little more safe as something might have messed up the code in ram and you jump into bad code (or worse, partially bad code that erases the flash but can not program). For this, just add pointers in linker script and use those with memcpy-like code to do the fresh copy after you have disabled interrupts.
Adding your section inside .data can sometimes be the simplest solution, but for some architectures, .data sections may be execute-protected and things do not work; in addition, the linker will throw warnings.
Things get more difficult when interrupts need to be left enabled while programming flash. For ARM, the isr vector table needs to be moved to RAM etc. as well.
For my projects, typical arrangement is:
.data :
{
. = ALIGN(4);
_sdata = .;
*(.ramfunc*)
*(.data)
*(.data*)
. = ALIGN(4);
_edata = .;
} >RAM AT> FLASH
_sidata = LOADADDR(.data);