SVC Execptions
SVC is a privileged instruction in ARM architecture that generates a software interrupt for requesting system services.
Key Characteristics:
- Purpose: Creates a controlled pathway from unprivileged to privileged mode
- Instruction:
SVC #immediate(where immediate is typically a service number) - Exception Type: Exception number 11 in ARM Cortex-M processors
How it Works:
- User code executes the SVC instruction with a parameter
- Processor automatically:
- Saves context on stack
- Switches to Handler mode (privileged)
- Jumps to the SVC handler function
Common Uses:
- Operating Systems: Implementing system calls
- RTOS: Task switching and privilege management
- Security: Creating boundaries between privileged and unprivileged code
- Resource Access: Controlled access to protected hardware resources
Creating SVC Services in arm gcc (cubeide)
💡 The stack is a region of memory used for temporary storage during program execution. It operates in a "last in, first out" (LIFO) manner. The stack is used to store function parameters, local variables, return addresses, and to save CPU registers during function calls and interrupts. Each function call pushes data onto the stack, and data is popped off when the function returns.

The stack grows downward in memory


💡 ARM GCC calling convention (AAPCS, ARM Architecture Procedure Call Standard):
Argument Passing
- First four arguments: Passed in registers
r0,r1,r2,r3. - Additional arguments: Passed on the stack (right to left).
- Floating-point arguments: Passed in
s0–s15(if FPU is enabled and used).
Return Values
- Integer/pointer return: In
r0(andr1for 64-bit values). - Floating-point return: In
s0(ord0for double).
Register Usage
- Caller-saved (volatile):
r0–r3,r12(can be changed by called function). - Callee-saved (non-volatile):
r4–r11,sp,lr(must be preserved by called function).
Stack Alignment
- Stack pointer (
sp): Must be 8-byte aligned at function entry.
Function Prologue/Epilogue
- Prologue: Callee saves non-volatile registers it uses, sets up stack frame.
- Epilogue: Callee restores non-volatile registers, stack pointer, and returns.
Special Registers
r12: Intra-procedure-call scratch register (used by linker, not preserved).lr: Link register (return address).sp: Stack pointer.
Example
int foo(int a, int b, int c, int d, int e);
// a → r0, b → r1, c → r2, d → r3, e → stack
References:
Let me know if you want details for a specific ARM variant or ABI!
💡 When a function is declared to return a type but does not provide a return statement, the compiler must still produce code that returns a value in r0 (the standard return register for integers in ARM calling convention).
If the function ends without a return, the compiler may move a value from another register (like r3 if the return size is 4 bytes or less) into r0 to ensure r0 contains something, even if it's meaningless. This is often just leftover code from the function's last computation or a default action to satisfy the calling convention.
Summary:
r0 must hold the return value.
Without a return, the compiler may move an arbitrary value (e.g., from r3 if the return size is 4 bytes or less) to r0 before exiting, to fulfill ABI requirements.
The returned value is undefined and should not be relied upon.

if the return size is between 4 bytes and 8 bytes
r2 is moved to r0
and r3 is moves in r1

💡 The attribute((naked)) is a GCC compiler attribute that instructs the compiler to not generate any function prologue or epilogue code for a function.
#include <stdint.h>
#include "stm32f4xx.h"
void SVC_Handler(void);
void SVC_Handler_c(uint32_t *sp);
static inline int32_t svc_add(register int32_t a, register int32_t b)
{
__asm volatile("svc #0 \n");
// Note when you don't provide a return
// the arm gcc complier moves the r3 to r0
// and the calling convention states that
// value of r0 is the return value
}
int main(void)
{
int x, y, z;
x = 8;
y = 9;
z = svc_add(x, y);
while (1) {
}
}
__attribute__((naked)) void SVC_Handler(void)
{
// if the fucntion is not naked, an other stack frame
// is created for the SVC_Handler function.
// And the address for this stack is moved into
// MSP register, which leads to the wrong stack to be
// passed to the SVC_Handler_c
// The register value of r0 is the first **argument in**
// SVC_Handler_c function, which called by '**B SVC_Handler_c'**
__asm volatile (
"TST lr, #4 \n"
"ITE EQ \n"
"MRSEQ r0, MSP \n"
"MRSNE r0, PSP \n"
"B SVC_Handler_c \n"
);
}
void SVC_Handler_c(uint32_t *sp)
{
// to better understand the sp value refer to the image below
// the sp points to the top of the stack which mean the r0 register
// of the svc_add function (aka it first argument 'a')
uint8_t *pc = (uint8_t *)sp[6]; // stacked PC
uint8_t svc_number = pc[-2];
// why the pc value contain the svc number?
// The PC (Program Counter) is a CPU register that holds the
// address of the next instruction to execute
// and this address points to the epilogue of the
// **svc_add** function (image below) which is a **NOP** in our case
****
switch (svc_number)
{
case 0:
sp[3] = sp[0] + sp[1];
break;
// case 1:
// sp[3] = sp[0] - sp[1];
// break;
// case 2:
// sp[3] = sp[0] * sp[1];
// break;
// case 3:
// sp[3] = sp[0] / sp[1];
// break;
default:
break;
}
}


I changed the SVC code to 0xEE (238 decimal) to differentiate it from the NOP instruction.