SVC Execptions

SVC is a privileged instruction in ARM architecture that generates a software interrupt for requesting system services.

Key Characteristics:

How it Works:

  1. User code executes the SVC instruction with a parameter
  2. Processor automatically:
    • Saves context on stack
    • Switches to Handler mode (privileged)
    • Jumps to the SVC handler function

Common Uses:

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

The stack grows downward in memory

image.png

image.png

💡 ARM GCC calling convention (AAPCS, ARM Architecture Procedure Call Standard):

Argument Passing

Return Values

Register Usage

Stack Alignment

Function Prologue/Epilogue

Special Registers

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.

image.png

if the return size is between 4 bytes and 8 bytes

r2 is moved to r0

and r3 is moves in r1

image.png

💡 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;
  }
}

image.png

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

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