I have an ARM board with ROM at 0x80000000 and RAM at 0x20000000. Board starts execution of raw binary code at 0x80000000.
I managed to get a simple ARM assembler program running on it, but I have to use C instead of ASM. I know I will need to use some kind of linker script, and then manually copy .data section to RAM and clear .bss, setup stack etc. but I haven't found a reliable solution how to do this yet, especially the linker script (it's very messy in my opinion).
Also, I can't get the linker to output a raw binary instead of ELF, but it's not a big deal as I can use objcopy later.
Thanks in advance.
This example is for STM32F051 MCU:
Very simple application as "blinky" (without delays):
// define used registers
#define RCC_AHB1 *(volatile unsigned int *)(0x40021014)
#define GPIOC_MODER *(volatile unsigned int *)(0x48000800)
#define GPIOC_BSRR *(volatile unsigned int *)(0x48000818)
// main program
void mainApp() {
RCC_AHB1 = 1 << 19; // enable clock for GPIOC
GPIOC_MODER = 1 << (9 * 2); // set output on GPIOC.P9
while (1) {
GPIOC_BSRR = 1 << 9; // set output on GPIOC.P9
GPIOC_BSRR = 1 << (9 + 16); // clear output on GPIOC.P9
}
}
// variables for testing memory initialisation
int x = 10;
int y = 0;
int z;
Here is also very simple startup file written in C (also will work in C++), which start application mainApp and initialise static variables .data initialised from ROM and .bss only set to zero, there are variables initialised to zero and uninitialised variables.
extern void mainApp();
// external variables defined in linker script
// address in FLASH where are stored initial data for .data section
extern unsigned int _data_load;
// defines start and end of .data section in RAM
extern unsigned int _data_start;
extern unsigned int _data_end;
// defines start and end of .bss section in RAM
extern unsigned int _bss_start;
extern unsigned int _bss_end;
void resetHandler() {
unsigned int *src, *dst;
// copy .data area
src = &_data_load;
dst = &_data_start;
while (dst < &_data_end) {
*dst++ = *src++;
}
// clear .bss area
dst = &_bss_start;
while (dst < &_bss_end) {
*dst++ = 0;
}
mainApp();
while(1);
}
// _stacktop is defined in linker script
extern unsigned int _stacktop;
// vector table, will be placed on begin of FLASH memory, is defined in linker script
// only reset vector defined, need add other used vectors (especially NMI)
__attribute__((section(".vectors"), used)) void *isr_vectors[] = {
&_stacktop, // first vector is not vector but initial stack position
(void *)resetHandler, // vector which is called after MCU start
};
And finaly linker script which define location and sizes of memories, sections for vector, program (text), data (.data and .bss) a stacktop
MEMORY {
FLASH(rx) : ORIGIN = 0x08000000, LENGTH = 64K
SRAM(rwx) : ORIGIN = 0x20000000, LENGTH = 8K
}
SECTIONS {
. = ORIGIN(FLASH);
.text : {
*(.vectors)
*(.text)
} >FLASH
. = ORIGIN(SRAM);
.data ALIGN(4) : {
_data_start = .;
*(.data)
. = ALIGN(4);
_data_end = .;
} >SRAM AT >FLASH
.bss ALIGN(4) (NOLOAD) : {
_bss_start = .;
*(.bss)
. = ALIGN(4);
_bss_end = .;
} >SRAM
_stacktop = ORIGIN(SRAM) + LENGTH(SRAM);
_data_load = LOADADDR(.data);
}
Build this with these command (build and link at once):
$ arm-none-eabi-gcc -mcpu=cortex-m0 -mthumb -nostartfiles main.c startup.c -T stm32f051x8.ld -o main.elf
In symbol table is possible to see where are stored data:
$ arm-none-eabi-nm -C -l -n -S main.elf
08000000 00000008 T isr_vectors
08000008 00000034 T mainApp
0800003c 0000005c T resetHandler
08000098 A _data_load
20000000 D _data_start
20000000 00000004 D x
20000004 D _data_end
20000004 B _bss_start
20000004 00000004 B y
20000008 B _bss_end
20000008 00000004 B z
20002000 A _stacktop
Also you can look into listing:
arm-none-eabi-objdump -S main.elf
Raw binary:
arm-none-eabi-objcopy -O binary main.elf main.bin
MEMORY
{
bob : ORIGIN = 0x8000, LENGTH = 0x1000
ted : ORIGIN = 0xA000, LENGTH = 0x1000
}
SECTIONS
{
.text : { *(.text*) } > bob
__data_rom_start__ = .;
.data : {
__data_start__ = .;
*(.data*)
} > ted AT > bob
__data_end__ = .;
__data_size__ = __data_end__ - __data_start__;
.bss : {
__bss_start__ = .;
*(.bss*)
} > ted
__bss_end__ = .;
__bss_size__ = __bss_end__ - __bss_start__;
}
of course change the addresses as you see fit. clearly the names of the sections mean nothing, you might try rom and ram if it helps you.
If you were to do something like this
MEMORY
{
rom : ORIGIN = 0x80000000, LENGTH = 0x1000
ram : ORIGIN = 0x20000000, LENGTH = 0x1000
}
SECTIONS
{
.text : { *(.text*) } > rom
.bss : { *(.bss*) } > ram
.rodata : { *(.rodata*) } > rom
.data : { *(.data*) } > ram
}
and then objcopy it then you are going to end up with a huge file, even if you only have one instruction of .text and one byte of data. because a binary format has to cover everything as in the memory so it will make a file that is 0x80000000+sizeof(text)-0x20000000. If you had one instruction only in ram and one byte of data then the file would be 0x60000004 bytes or 1.6 gig. leave it as an elf and use objdump -D until you have your linker script worked out THEN make a .bin if you really need one. or make an intel hex or s record and again you can examine it to see if it is all in the same address space before trying a binary.
the first key to your problem is the AT
MEMORY
{
bob : ORIGIN = 0x8000, LENGTH = 0x1000
ted : ORIGIN = 0xA000, LENGTH = 0x1000
}
SECTIONS
{
.text : { *(.text*) } > bob
.data : { *(.data*) } > ted AT > bob
.bss : { *(.bss*) } > bob
}
it says I want the .data in the address space ted, but place it in the binary in the bob space. it compiles for the ted address space, but the bits are loaded from bob space. exactly what you want. except you dont know how much .data to copy from rom to ram. you have to be super careful where you place the variables in the more complicated one or it wont work right.
Related
Im developing a bare metal OS for raspberry pi 1. The main idea of this OS is a simply kernel that will be able to run a function created in other machine and send it to the raspi. The function will use functions that were compilated with the kernel and that reside in memory.
I want to know how can I compile a function that when I will insert the payload in memory in a determinated address it will be able to call system functions and use the data that are defined in the function code.
void function()
{
while (1)
{
uart_puts("Hello\r\n");
}
}
This is my link.ld file and the function will be loaded at __binary_function:
ENTRY(_start)
SECTIONS
{
/* Starts at LOADER_ADDR. */
. = 0x8000;
__start = .;
__text_start = .;
.text :
{
KEEP(*(.text.boot))
*(.text)
}
. = ALIGN(4096); /* align to page size */
__text_end = .;
__rodata_start = .;
.rodata :
{
*(.rodata)
}
. = ALIGN(4096); /* align to page size */
__rodata_end = .;
__data_start = .;
.data :
{
*(.data)
}
. = ALIGN(4096); /* align to page size */
__data_end = .;
__bss_start = .;
.bss :
{
bss = .;
*(.bss)
}
. = ALIGN(4096); /* align to page size */
__bss_end = .;
/*Allocating memory for the heap*/
__heap_start = .;
. = . + 0x1000000; /*4MB Heap section*/
__heap_end = .;
__binary_function = .;
__end = .;
}
My idea is that the kernel will receive the payload via UART and then execute it. The payload must be able to call system functions such us the UART functions and access to the data that is define inside it.
I want to do this automatically with arm-none-eabi-gcc (GCC Toolchain for ARM architecture) that works like GCC and avoiding harcoding the system functions in the payload source.
Thanks!
I solve it using the hardcoding the kernel functions in the payload:
void (* uart_puts)(char *) = (void(*)(char * )) 0x0000000000008248;
I get the 0x0000000000008248 address from the .map file generated in the compilation of the kernel.
I'm debugging an embedded system with the stm32f746vg microcontroller where some speed critical sections of code are loaded in data RAM instead of flash. The rest of the non-critical code is loaded in flash, and the linker puts all of the .text sections in flash by default.
I do this relocation with nasty little function prototypes like this:
int main(void) __attribute__((section(".data")));
This forces main() to be loaded at address 0x2000 0000, which is what I want. Unfortunately, GDB expects main() to be loaded at address 0x0080 0000, where the rest of the program code is. As a result, GDB does not know where the line numbers for main() are. Is there any way to tell GDB "main() is actually at this address" ?
I DO have the newest version of arm-none-eabi-gdb and arm-none-eabi-gcc.
I am running arm-none-eabi-gcc with the -g option.
When I comment out the prototype for main that contains the __attribute__ directive, GDB has no problem finding line numbers.
Below is my linker script:
/* Entry Point */
ENTRY(Reset_Handler)
/* Generate a link error if heap and stack don't fit into RAM */
_Min_Heap_Size = 0; /* required amount of heap */
_Min_Stack_Size = 0x400; /* required amount of stack */
/* Specify the memory areas */
/**
* for the time being, we are going to buffer the image in sram1. sram1 is
* entirely dedicated to buffering images.
* DTCM_RAM will be used for user variables
*/
MEMORY
{
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 1024K
DTCM_SRAM (xrw) : ORIGIN = 0x20000000, LENGTH = 64K
/* RAM1 (xrw) : ORIGIN = 0x20010000, LENGTH = 240K*/
RAM1 (xrw) : ORIGIN = 0x20010000, LENGTH = 245760
RAM2 (xrw) : ORIGIN = 0x2004c000, LENGTH = 16K
ITCM_SRAM (xrw) : ORIGIN = 0x00000000, LENGTH = 16K
}
/* Define output sections */
SECTIONS
{
/* Vectors need to be loaded at the start of flash. */
.isr_vector :
{
. = ALIGN(4);
KEEP(*(.vector)) /* Startup code */
. = ALIGN(4);
} >FLASH
/* The program code and other data goes into FLASH */
.text :
{
. = ALIGN(4);
*(.text) /* .text sections (code) */
*(.text*) /* .text* sections (code) */
*(.rodata) /* .rodata sections (constants, strings, etc.) */
*(.rodata*) /* .rodata* sections (constants, strings, etc.) */
*(.glue_7) /* glue arm to thumb code */
*(.glue_7t) /* glue thumb to arm code */
*(.eh_frame)
/*KEEP (*(.init))*/
/*KEEP (*(.fini))*/
. = ALIGN(4);
_etext = .; /* define a global symbols at end of code */
_exit = .;
} >FLASH
.ARM.extab : { *(.ARM.extab* .gnu.linkonce.armextab.*) } >FLASH
.ARM : {
__exidx_start = .;
*(.ARM.exidx*)
__exidx_end = .;
} >FLASH
/* used by the startup to initialize data */
_sidata = .;
/* Initialized data sections goes into RAM, load LMA copy after code */
.data ORIGIN(DTCM_SRAM) :
{
. = ALIGN(4);
_sdata = .; /* create a global symbol at data start */
*(.data) /* .data sections */
*(.data*) /* .data* sections */
. = ALIGN(4);
_edata = .; /* define a global symbol at data end */
} AT > FLASH
__const_data_length__ = SIZEOF(.data);
/* Uninitialized data section */
. = ALIGN(4);
.bss . :
{
/* This is used by the startup in order to initialize the .bss secion */
_sbss = .; /* define a global symbol at bss start */
__bss_start__ = _sbss;
*(.bss)
*(.bss*)
*(COMMON)
. = ALIGN(4);
_ebss = .; /* define a global symbol at bss end */
__bss_end__ = _ebss;
} >DTCM_SRAM
/* User_heap_stack section, used to check that there is enough RAM left */
._user_heap_stack :
{
. = ALIGN(4);
PROVIDE ( end = . );
PROVIDE ( _end = . );
. = . + _Min_Heap_Size;
. = . + _Min_Stack_Size;
. = ALIGN(4);
} >DTCM_SRAM
/* ram1 section, vars must be located here explicitly */
/* Example: extern int foo(void) __attribute__ ((section (".ram1"))); */
/* No initialization is offered for this section.*/
.ram1 ORIGIN(RAM1) :
{
*(.ram1)
} >RAM1
/* Remove information from the standard libraries */
/DISCARD/ :
{
libc.a ( * )
libm.a ( * )
libgcc.a ( * )
}
.ARM.attributes 0 : { *(.ARM.attributes) }
}
These are my gcc flags
CFLAGS = -gstabs -Wall -std=gnu99 -ffunction-sections -Wno-unused-variable
CFLAGS += -mlittle-endian -mthumb -mcpu=cortex-m4 -mthumb-interwork
CFLAGS += -mfloat-abi=hard -mfpu=fpv4-sp-d16 -mlong-calls
CFLAGS += -I. -ggdb
CFLAGS += -DHSE_VALUE=$(HSE_VALUE)
CFLAGS += -DUSE_STDPERIPH_DRIVER
You acheive this by placing main() in special code section of your choosing, call it .fast_exec
int main(void) __attribute__((section(".fast_exec")));
Then, in the linker script, place this new section to execute from RAM. It will be copied from FLASH at start-up:
.fast_exec : {
*(.fast_exec)
} > DTCM_SRAM AT> FLASH
You can verify the effect of this, by checking the map file.
Refer to: https://stackoverflow.com/a/15142268/1096140
I have a linker script for a kernel with two absolute symbols: _kernel_start and _kernel_end. However, I get a linker relocation error for only _kernel_end:
In function `kernel::mem::mm::setup_memorymap':
/home/virtlink/kernel/src/mem/mm.rs:25:(.text._ZN3mem2mm15setup_memorymap):
relocation truncated to fit: R_X86_64_PC32 against symbol `_kernel_end'
defined in *ABS* section in ./kernel.bin
There have been many questions here on SO about this error, but I found none that solves my particular problem.
Apparently, this:
_kernel_start = .;
...is treated as 32-bit, whereas this:
. += KERNEL_BASE;
_kernel_end = . - KERNEL_BASE;
...is treated as 64-bit. If I move the _kernel_end symbol above the . += KERNEL_BASE line like this:
_kernel_end = .;
. += KERNEL_BASE;
...then it works again. But I want _kernel_end at the end of my linker script.
The linker script puts the boot code at the start of memory, and the rest of the code in the higher-half of the 64-bit virtual memory space. It looks like this:
OUTPUT_FORMAT(elf64-x86-64)
KERNEL_BASE = 0xFFFFFFFF80000000;
SECTIONS
{
/* Boot code at 1 MiB */
. = 1M;
_kernel_start = .;
.boot :
{
KEEP( *(.multiboot) )
*(.boot)
*(.bootdata)
}
/* Kernel code at high virtual address. */
. += KERNEL_BASE;
.text ALIGN(4K) : AT(ADDR(.text) - KERNEL_BASE)
{
*(.text)
*(.gnu.linkonce.t*)
}
.data ALIGN(4K) : AT(ADDR(.data) - KERNEL_BASE)
{
*(.data)
*(.gnu.linkonce.d*)
}
.rodata ALIGN(4K) : AT(ADDR(.rodata) - KERNEL_BASE)
{
*(.rodata)
*(.gnu.linkonce.r*)
}
.bss ALIGN(4K) : AT(ADDR(.bss) - KERNEL_BASE)
{
*(COMMON)
*(.bss)
*(.bss.*)
*(.gnu.linkonce.b*)
}
_kernel_end = . - KERNEL_BASE;
/DISCARD/ :
{
*(.comment)
*(.eh_frame)
}
}
The kernel is really small, so _kernel_start = 0x00100000 and _kernel_end = 0x00142000. It shouldn't give me relocation errors.
How can I rewrite the linker script such that _kernel_end give me no more relocation errors? I don't want to use mcmodel=large just for this one symbol.
Here's the code in which I'm using the symbols. It's Rust.
fn get_kernel_location() -> (*const u8, *const u8) {
extern {
static _kernel_start: u8;
static _kernel_end: u8;
}
let kernel_start: *const u8 = &_kernel_start;
let kernel_end: *const u8 = &_kernel_end;
println!("{:p}", kernel_start);
println!("{:p}", kernel_end);
(kernel_start, kernel_end)
}
Here are the entries in the relocation table of the Rust compiled object file:
Offset Info Type Sym. Value Sym. Name + Addend
000000000012 058800000009 R_X86_64_GOTPCREL 0000000000000000 _kernel_end - 4
000000000019 058900000009 R_X86_64_GOTPCREL 0000000000000000 _kernel_start - 4
I am working on an embedded system (Stellaris Launchpad) and writing a simple OS (as a hobby project). The used toolchain is gcc-none-eabi.
My next step is to get used to the MPU to allow the kernel to prevent user programs from altering specific data. I have a bunch of C files and I splitted them in two parts: kernel and other.
I have the following linker script to start out with:
MEMORY
{
FLASH (rx) : ORIGIN = 0x00000000, LENGTH = 0x00040000
SRAM (rwx) : ORIGIN = 0x20000000, LENGTH = 0x00008000
}
SECTIONS
{
.text :
{
_text = .;
KEEP(*(.isr_vector))
*(.text*)
*(.rodata*)
_etext = .;
} > FLASH
.data : /*AT(ADDR(.text) + SIZEOF(.text))*/ /*contains initialized data*/
{
_data = .;
*(vtable)
*(.data*)
_edata = .;
} > SRAM AT > FLASH
.bss : AT (ADDR(.data) + SIZEOF(.data)) /*contains unitialized data (should be set to all zero's)*/
{
_bss = .;
*(.bss*)
*(COMMON)
_ebss = .;
_start_heap = .;
} > SRAM
_stack_top = ORIGIN(SRAM) + LENGTH(SRAM) - 1; /*The starting point of the stack, at the very bottom of the RAM*/
}
And after reading up on linker scripts I know that I can replace the stars with filenames, and thus start splitting the flash in multiple parts. I would for example create a .kernel.bss section and put all of the kernel object files instead of the stars in that section.
My only problem left is that the kernel is not one file, it is a whole lot of files. And files might be added, removed etc. So how do I do this? How do I change my linker script so that a dynamic first group of files is mapped to the first place and a dynamic second group of files is mapped to a second place?
you know that you can specify what files are used as input for a section?
We use this for separating kernel and application code into fast internal flash, and slower external flash memory, like so:
.kernel_text :
{
build/kernel/*.o (.text*) /*text section from files in build/kernel*/
} > INT_FLASH
.app_text:
{
build/app/*.o(.text*)
} > EXT_FLASH
Section 4.6.4 might be helpful, (describes input sections in more detail)
https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/4/html/Using_ld_the_GNU_Linker/sections.html
I found a solution, allthough it feels a bit hacky. It does work though:
I found out that a linker script is OK with working on .a files if they are statically linked with ar. So lets say you have a buch of .o files that, together form the kernel: a.o, b.o, c.o. Use ar rcs kernel.a a.o, b.o, c.o. kernel.a is now your kernel, which you want to store seperately in memory.
The next thing you need to know is that the * in a linker script is actually a wildcard for everything not used yet. So we can create the following linker script:
MEMORY
{
FLASH (rx) : ORIGIN = 0x00000000, LENGTH = 0x00040000
SRAM (rwx) : ORIGIN = 0x20000000, LENGTH = 0x00008000
}
SECTIONS
{
.kernel.text :
{
_kernel_text = .;
KEEP(kernel.a(.isr_vector))
KEEP(kernel.a(_sbrk))
kernel.a(.text*)
kernel.a(.rodata*)
_kernel_etext = .;
_kernel_flash_data = ALIGN(0x4);
} > FLASH
.kernel.data : /*AT(ADDR(.text) + SIZEOF(.text))*/ /*contains initialized data*/
{
_kernel_data = .;
kernel.a(vtable)
kernel.a(.data*)
_kernel_edata = .;
} > SRAM AT > FLASH
.kernel.bss :
{
_kernel_bss = .;
kernel.a(.bss*)
kernel.a(COMMON)
_kernel_ebss = .;
} > SRAM
.text : /*AT (ADDR(.core.text) + SIZEOF(.core.text) + SIZEOF(.core.data))*/
{
_text = .;
*(.text*)
*(.rodata*)
_etext = .;
_flash_data = ALIGN(0x4);
} > FLASH
.data :
{
_data = .;
*(vtable)
*(.data*)
_edata = .;
} > SRAM AT > FLASH
.bss : AT (ADDR(.data) + SIZEOF(.data)) /*contains unitialized data (should be set to all zero's)*/
{
_bss = .;
*(.bss*)
*(COMMON)
_ebss = .;
_start_heap = .;
} > SRAM
}
This works but will probably lead to a new problem: the linker treats libraries as.. well, libraries. So if they contain the program start (as in my case) the linker does not actually look for it, the linker only looks trough the library for functions refered to by the actual o files. The solution I found for this is to add the -u <name> flag to the linker invocation. This flag causes a symbol to become undefined, so the linker will look for this symbol plus all symbols that are needed by this synbol.
My invocation, for references sake:
arm-none-eabi-ld -Tlinker_script.ld -nostdlib --entry ResetISR
--gc-sections -u _sbrk -u .isr_vector
-L./lib//hardfp
-L/home/me/gcc-arm-none-eabi/gcc-arm-none-eabi-4_9-2015q1/arm-none-eabi/lib/armv7e-m/fpu
-L/home/me/gcc-arm-none-eabi/gcc-arm-none-eabi-4_9-2015q1/lib/gcc/arm-none-eabi/4.9.3/armv7e-m/fpu
-Lrelease/
-o release/os
./user/obj/release/ledsDance.c.o ./user/obj/release/main.c.o ./validation/obj/release/val_floattest.c.o ./validation/obj/release/val_genTest.c.o ./validation/obj/release/val_gpiotest.c.o ./validation/obj/release/val_iotest.c.o ./validation/obj/release/val_proctest.c.o ./validation/obj/release/val_schedTest.c.o release/kernel.a release/core.a
-ldriver-cm4f
-luartstdio
-lm
-lc
-lgcc
Im having problems when I define global variables in a basic C program for an ARM9 processor. I'm using EABI GNU compiler and the binary generated from a 12KB elf is 4GB! I assume the issue is with my scatter file but Im having trouble getting my head around it.
I have 256KB of ROM (base address 0xFFFF0000) and 32KBs of RAM (base 0x01000000)
SECTIONS {
. = 0xFFFF0000;
.text : {
* (vectors);
* (.text);
}
.rodata : { *(.rodata) }
. = 0x01000000;
sbss = .;
.data : { *(.data) }
.bss : { *(.bss) }
ebss = .;
bssSize = ebss - sbss;
}
And my program is as follows:
int a=10;
int main() {
int b=5;
b = (a>b)? a : b;
return b;
};
If I declare a as a local variable, i.e. there is no .data section then everything works.
fine. Any help greatly appreciated.
--16th March 2011--
Can anyone help with this, Im getting nowhere and have read the manuals, forums etc...
My boot, compile command and objcopy commands are pasted below
.section "vectors"
reset: b start
undef: b undef
swi: b swi
pabt: b pabt
dabt: b dabt
nop
irq: b irq
fiq: b fiq
.text
start:
ldr sp, =0x01006000
bl main
stop: b stop
arm-none-eabi-gcc -mcpu=arm926ej-s -Wall -nostartfiles -Wall main.c boot.s -o main.elf -T \ scatter_file
arm-none-eabi-objcopy ./main.elf --output-target=binary ./main.bin
arm-none-eabi-objdump ./main.elf --disassemble-all > ./main.dis
I found the problem. The objcopy command will try to create the entire address space described in the linker script, from the lowest address to the highest including everything in between. You can tell it to just generate the ROM code as follows:
objcopy ./main.elf -j ROM --output-target=binary ./main.bin
I also changed the linker script slightly
MEMORY {
ram(WXAIL) : ORIGIN = 0x01000000, LENGTH = 32K
rom(RX) : ORIGIN = 0xFFFF0000, LENGTH = 32K
}
SECTIONS {
ROM : {
*(vectors);
*(.text);
*(.rodata);
} > rom
RAM : {
*(.data);
*(.bss);
} > ram
}
You are creating a file which will starts at address 0x01000000 and will contains at least up to address 0xFFFF0000. No wonder that it is nearly 4GB. What would you like? Try with options -R to remove the data segments if you don't want them (as it is probably the case if you are preparing a ROM initialization file).
Adding the (NOLOAD) argument worked for me. E.g.
MEMORY {
ram(WXAIL) : ORIGIN = 0x01000000, LENGTH = 32K
rom(RX) : ORIGIN = 0xFFFF0000, LENGTH = 32K
}
SECTIONS {
ROM : {
*(vectors);
*(.text);
*(.rodata);
} > rom
RAM (NOLOAD) : {
*(.data);
*(.bss);
} > ram
}