我正在做一个小项目,在裸机上使用Linux标准C库(无操作系统)。我使用qemu-system-arm作为执行平台,使用GDB进行调试。我已经编写了一个小的系统调用处理程序来处理C库所做的SVC调用,但是由于我的未处理的syscall函数无法将堆栈遍历回调用者,即使SVC处理程序可以,也很困惑。 处理程序代码是:
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}
如果我在__in_syscall设置断点,我可以看到堆栈帧就好了。如果我通过分支输入Unhandled_SVC或通过SVC_Table中的指针间接输入,GDB会显示堆栈帧,即使程序正确执行也会感到困惑。
我错过了什么?
这是我的ELLCC embedded compiler项目和完整source is here的一部分。
答案 0 :(得分:1)
tl; dr - 可能你不能为你的系统调用用例做你想做的事。
但是,以下内容对于跟踪不涉及模式切换的ARM汇编程序非常有用。
有几个gnu汇编程序或 gas 伪操作,用于汇编程序中的堆栈跟踪。也就是说,您总是可以创建超出典型例程的汇编程序;例如,具有上下文切换的调度程序等
.fnstart
- 函数的开头(文本范围).fnend
- 功能结束(文字范围).setfp
- APCS堆栈框架的位置。.save
- 堆栈中保存的寄存器列表。.pad
- 堆叠上的其他保留空间.movsp
- 增加堆栈。.cantunwind
- 不要尝试解除此功能;当你超越正常时。您当前的例程省略了pc
注册表(针对SVC_Handler
)例程,并且没有更新fp
。这很好,但您需要告诉调试器不要查看可能存在于非保留寄存器中的参数。
特别有用的是 gas unwinding tutorial.
您的用户sp
和系统sp
不同。所以当你追踪时,它只会在一个堆栈中。 GDB不知道跳模和/或堆栈。一种机制是将系统调用条目中的帧指针归零,以使其成为帧跟踪的终止。然后,您需要在Unhandled_SVC
中设置一个真正的堆栈帧。如果要继续跟踪,则需要编写GDB宏来提取内核SVC调用信息并转换到例外堆栈。
有关ARM堆栈帧的一些信息,请参阅:ARM Link and frame pointer。