Search code examples
assemblyarmgdbgnu-arm

GDB Continues Execution Unexpectedly After Linked Branch (ARM Assembly)


I have a simple ARM Assembly program (below) with a linked branch to a function, which pushes a register onto the stack and branches back to the return address in lr.

main.S:

.global _start

.section .text
_start:
    mov     r0, #2      // garbage instructions to show that single step works properly with mov
    mov     r1, #2
    mov     r2, #2
    bl      subr

    mov     r7, #0x1
    mov     r0, #0
    swi     0

subr:
    mov     r0, #1
    push    {r0}
    bx      lr

When I debug the program with GDB and use n to step over the bl subr instruction, it continues to the next breakpoint instead of single stepping (GIF 1 below). When I step into subr and then single step over bx lr, it single steps to the next instruction normally (GIF 2 below). I realized the problem is because of the push {r0} instruction in subr. If it is removed, the linked branch instruction behaves as normal.

The address in lr is 0x10064, which matches the address of the next instruction after the branch (line 10):

(gdb) i r lr
lr             0x10064  65636
(gdb) i line 10
Line 10 of "main.S" starts at address 0x10064 <_start+16> and ends at 0x10068 <_start+20>.

Step Over GIF of GDB (not working as expected):

Step Over GIF Link

Step Into (correct):

Step Into GIF Link

I am using GDB 7.7.1 on Raspbian Jessie 2017 (QEMU Emulation, stablest OS I could get working) on architecture ARMv6l.

The question: In a real project (I am currently working on one), how can I avoid having to single step through a subroutine while still placing the return value on the stack somehow (If I am doing things completely wrong, please tell me)? Why is this happening with the push instruction?


Solution

  • You're unbalancing the stack, so it seems that GDB assumes that the function is recursive or otherwise still active, and so doesn't stop upon return to main (since the stack is unbalanced).  GDB is waiting for the stack to return to normal (for _start) and the pc to return to that "next" instruction location, both together, in order to break at the next logical line (methinks, at least).

    Unbalancing the stack is not good and could very well upset not just the debugger, but other functions, say, if such function made any use of the stack before & then after making a call to a function that unbalances the stack, such as saving its own return address on the stack to use later.

    For example, try writing that same code with _start calling a main, then that main calling subr.  That just won't work b/c main will have to push its own return address (i.e. to _start) before calling subr and then pop it after in order to return back to _start, but it won't get it right since the stack has been unbalanced by subr.