I'm doing a little project to use a Linux standard C library for ARM on bare metal (no OS). I'm using qemu-system-arm as the execution platform and GDB to debug. I have written a little system call handler to handle the SVC calls that the C library makes but am confused by the fact that my unhandled syscall function can't traverse the stack back to the caller even though the SVC handler can. The handler code is:
SVC_Handler:
srsfd sp!, #Mode_SYS // Save LR_svc and SPSR_svc on the sys stack.
cpsid i, #Mode_SYS // Switch to sys mode.
push {r4-r12, lr} // Save registers.
// In a system call.
// r7 is the call number.
__in_syscall: // The stack frame is valid here.
cmp r7, #512
blhs Unhandled_SVC // Jump if too big for a syscall.
adr r8, SVC_Table // Get the system call table.
str r7, SysCall // Save call number for error reporting.
ldr r7, [r8, r7, lsl #2]// Get the stystem call entry.
blx r7 // Dispatch. Return value is in r0/r1
goback:
pop {r4-r12, lr} // Restore registers.
rfeia sp! // And return.
SysCall:
.word 0
// Unhandled system calls.
Unhandled_SVC:
stmfd sp!, {r12, lr}
push {r2-r5} // Push extra arguments.
mov r3, r1
mov r2, r0
ldr r1, SysCall // And the system call number.
ldr r0, stringPtr // Get the format string.
bl printf
add sp, #16 // clean up the stack.
mov r0, #-ENOSYS
ldmfd sp!, {r12, pc}
If I set a breakpoint at __in_syscall, I can see the stack frame just fine. If I enter Unhandled_SVC either via the branch or indirectly via a pointer in SVC_Table, GDB gets confused displaying the stack frame, even though the program executes correctly.
What am I missing?
This is part of my ELLCC embedded compiler project and the complete source is here.
tl;dr - probably you can not do what you want for your use case of a system call.
However, the following is useful for tracing ARM assembler that doesn't involve mode switching.
There are several gnu assembler or gas pseudo-ops which are used for stack tracing in assembler. That said, you can always create assembler that is beyond what a typical routine would be; for instance, a scheduler with context switching, etc.
.fnstart
- start of a function (text range).fnend
- end of a function (text range).setfp
- a location of a APCS stack frame..save
- a list of saved registers on stack..pad
- other reserved space on stack.movsp
- incremented the stack..cantunwind
- don't attempt to unwind this function; when you went beyond normal.Your current routine omits the
pc
register (for theSVC_Handler
) routine and doesn't update thefp
. This is fine, but you need to tell the debugger not to look at the arguments that could be in non-preserved registers. Specifically useful is the gas unwinding tutorial.Your user
sp
and the systemsp
are different. So when you trace, it will only be in one stack. GDB won't know to jump modes and/or stack. One mechanism is to zero the frame pointer in the syscall entry so that it is a termination of the frame trace. You then need to setup a real stack frame inUnhandled_SVC
. You would need to write a GDB macro to extract the kernel SVC call info and transition to the excepted stack, if you wanted to continue tracing.See: ARM Link and frame pointer for some info on the ARM stack frame.