main()
function. The first part of the article is about building and debugging tools. The second is the processing of exception vectors, the third is the initialization of stacks and memory.
printf, mktime, malloc
and everything else that C programmers are used to. Using glibc will not work, because it is too big. Instead, they usually use the free newlib . It is part of WinARM / YAGARTO, but Linux users will have to assemble it manually. Again - read the documentation :)
ldr pc, ResetHandlerAddr
ldr pc, UndefHandlerAddr
ldr pc, SWIHandlerAddr
ldr pc, PrefetchAbtHandlerAddr
ldr pc, DataAbtHandlerAddr
nop
ldr pc, IRQHandlerAddr
ldr pc, FIQHandlerAddr
pc
addresses of real handlers. A sort of unconditional transition. Next in the code are variables that store these same addresses:
ResetHandlerAddr: .word ResetHandler
UndefHandlerAddr: .word UndefHandler
SWIHandlerAddr: .word SWIHandler
PrefetchAbtHandlerAddr: .word PrefetchAbtHandler
DataAbtHandlerAddr: .word DataAbtHandler
IRQHandlerAddr: .word IRQHandler
FIQHandlerAddr: .word FIQHandler
UndefHandler: B UndefHandler
SWIHandler: B SWIHandler
PrefetchAbtHandler: B PrefetchAbtHandler
DataAbtHandler: B DataAbtHandler
IRQHandler: B IRQHandler
FIQHandler: B FIQHandler
B
is an unconditional branch command (Branch)
sp
for each of the modes of operation. Thus, if exceptions occur, the handler will already have its own stack. Only at the beginning we describe the sizes of all the stacks.
.EQU IRQ_STACK_SIZE, 0x100
.EQU FIQ_STACK_SIZE, 0x100
.EQU ABT_STACK_SIZE, 0x100
.EQU UND_STACK_SIZE, 0x100
.EQU SVC_STACK_SIZE, 0x100
.EQU ARM_MODE_FIQ, 0x11
.EQU ARM_MODE_IRQ, 0x12
.EQU ARM_MODE_SVC, 0x13
.EQU ARM_MODE_ABT, 0x17
.EQU ARM_MODE_UND, 0x1B
.EQU ARM_MODE_USR, 0x10
.EQU I_BIT, 0x80
.EQU F_BIT, 0x40
I_BIT
and F_BIT
are bits that prohibit simple and fast interrupts, respectively. Now we are ready to initialize the stacks. This is done simply: we load into the register r0
pointer to the top of the stack, then we go into the desired mode, write the value r0
into sp
, then reduce r0
by the size of the stack and repeat.
.RAM_TOP:
.word __TOP_STACK
ResetHandler:
ldr sp, .RAM_TOP
msr CPSR_c, #ARM_MODE_FIQ | I_BIT | F_BIT
mov sp, r0
sub r0, r0, #FIQ_STACK_SIZE
msr CPSR_c, #ARM_MODE_IRQ | I_BIT | F_BIT
mov sp, r0
sub r0, r0, #IRQ_STACK_SIZE
msr CPSR_c, #ARM_MODE_SVC | I_BIT | F_BIT
mov sp, r0
sub r0, r0, #SVC_STACK_SIZE
msr CPSR_c, #ARM_MODE_ABT | I_BIT | F_BIT
mov sp, r0
sub r0, r0, #ABT_STACK_SIZE
msr CPSR_c, #ARM_MODE_UND | I_BIT | F_BIT
mov sp, r0
sub r0, r0, #UND_STACK_SIZE
msr CPSR_c, #ARM_MODE_USR
main()
function. It is only necessary to transfer some data to RAM and zero out the memory that is in the .BSS segment. This is the memory where global variables are stored. The point is that the C language standard promises that global variables will be reset to zero at the beginning of work, and ARM does not guarantee us that. Therefore, we reset the segment manually:
MOV R0, #0 LDR R1, =__bss_start__ LDR R2, =__bss_end__ LoopZI: CMP R1, R2 STRLO R0, [R1], #4 BLO LoopZI
__bss_end__ & __bss_start__
kindly provided to us by the linker.
int x=42
) from ROM to RAM.
LDR R1, =_etext LDR R2, =_data LDR R3, =_edata LoopRel: CMP R2, R3 LDRLO R0, [R1], #4 STRLO R0, [R2], #4 BLO LoopRel
LDR r0, =__ctors_start__ LDR r1, =__ctors_end__ ctor_loop: CMP r0, r1 BEQ ctor_end LDR r2, [r0], #4 STMFD sp!, {r0-r1} MOV lr, pc BX r2 LDMFD sp!, {r0-r1} B ctor_loop ctor_end:
main()
:
ldr r0,=main bx r0
void main(void)
function. You can do the initialization of the periphery. The fact is that before this we initialized only the software environment. Therefore, the processor now operates at the lowest frequency possible, all peripherals are disabled. It does not clear up :)
Source: https://habr.com/ru/post/87343/