0

Can anyone explain the arm based assembly code for the stack point of view ; specifically the stack view for "reset_handler" right before calling "main", "save_context" and "resume" part? (Note that I know what the code is doing but I can't comprehend or imagine how exactly the stack look like or behave while the code is running).

   */ asm.s */

  .global main, process, process_size
  .global reset_handler, context_switch, running

reset_handler:
   ldr r0, =process
   ldr r1, =process_size 
   ldr r2, [r1, #0] 
   add r0, r0, r2 
   mov sp, r0 
   bl main 
   
context_switch:
 save_context:
  stmfd sp!, {r0-r12, lr}
  ldr r0, =running 
  ldr r1, [r0, #0] 
  str sp, [r1, #4] 

 resume:
  ldr r0, =running
  ldr r1, [r0, #0] 
  ldr sp, [r1, #4] 
  ldmfd sp!, {r0-r12, lr} 
  mov pc, lr

*/ cfile.c */

#define SIZE 2048 
typedef struct process
{
   struct process *next; 
   int *saved_stack;
   int running_stack[SIZE]; 
}PROC;

int process_size = sizeof(PROC);

PROC process, *running; 

main() 
{
  running = &process; 
  context_switch();
}
Ken White
  • 123,280
  • 14
  • 225
  • 444
user156427
  • 15
  • 3
  • It is not necessary to repeat the tag information in the title, and doing so is just creating noise and clutter. The tagging system works extremely well here, and doesn't need any assistance. – Ken White Jan 05 '22 at 20:52

2 Answers2

1

Pseudocode:

reset_handler:
  stack_pointer = &process+process_size;
  call main

This means that the stack pointer is pointing to &process.running_stack[SIZE]. When a stack pointer points to the very end of the stack buffer like this, it means the stack is completely empty (ARM uses a descending stack).

Tom V
  • 4,827
  • 2
  • 5
  • 22
1

As a background -- the processor's registers pretty much define what it is doing. They are often referred to as a context. The most important is the program counter pc which contains the memory address of the next instruction; however they are all important. So lets look at how to save a context:

save_context:
  stmfd sp!, {r0-r12, lr}
     -- that instruction saved to processor context to the stack
     -- it could be broken down as follows:
     -- sp = sp - 14*4    4, because each register is 4 bytes, and there are 14 specfied
     -- for (i=0; i < 13; i++)   sp[i] = r(i);
     -- sp[i] = lr      `lr` is special, it holds the return address of the instruction that called us.
  ldr r0, =running 
     -- put the address of the variable `running` into r0
  ldr r1, [r0, #0] 
     -- load r1 with the memory address from r0.  So r1 = running.
  str sp, [r1, #4] 
     -- store the stack pointer (sp) in the `saved_sp` field of running.
     -- so these three instructions perform:  running->saved_stack = sp;
     -- now we "fall through" to load, or `resume` a context.
 resume:
  ldr r0, =running
  ldr r1, [r0, #0] 
  ldr sp, [r1, #4]
      -- the inverse of the above, these three instructions effectively perform:
      -- sp = running->saved_stack 
  ldmfd sp!, {r0-r12, lr} 
      -- this is the complimentary operation to the complicated save one above; but this time it is:
      -- for (i=0; i < 13; i++) r(i) = sp[i];
      -- lr = sp[i];
      -- sp += 14*4;
  mov pc, lr
      -- this is a return instruction, where the program counter is loaded with the contents of the link register `lr`.
      -- so, with this, it will return to main just after the call to context_switch

There are a few fuzzy bits in the above: sp[i] would have to scale i by the sizeof a register (4); but earlier sp is reduced by 14*4. Since the pseudo-C isn't real, it seems ok.

mevets
  • 10,070
  • 1
  • 21
  • 33
  • Here is my understanding: For the context_switch part; after the first line the stack looks like : ```|LR|R12|R11|......|R0|``` after the line ```str sp, [r1,#4]``` the stack still looks like below ```|LR|R12|R11|......|R0| 'and sp points to r0 at the moment.``` and at this point, "saved_stack" holds where sp pointing to ? Also why do we allocate too much (2048) running_stack[]? just ```14*4 + (whatever sp holds) ``` should be enough to allocate, right? – user156427 Jan 05 '22 at 23:28
  • Yes, 14*4 is enough to hold the context; but presumably the next step is to have these threads do something useful, like compute the size of the universe, and for that they are going to need some stack. Most function calls require at least some stack. To clarify, the stack in that process structure will be the execution stack of the thread. – mevets Jan 05 '22 at 23:36
  • One last question about reset_handler is that: why don't we set a stack frame as we do for subroutines? How does sp dont lose its way around since we dont set any fp when we are trying to reach out different part of sp? (I hope I am clear with my question). What would the stack look like during the reset_handler (as I describe the internals of the stack above)? What does stack look like right before calling ```main``` the moment – user156427 Jan 05 '22 at 23:43
  • @tom-v answered the reset handler well; the reset_handler is where the CPU starts executing from. At that point, the stack is undefined so it sets it to the end of the `process` structure, which is the top of the stack. You don't really need a frame pointer unless you are either doing very clever stack things (like variable allocations) or you want a debugger to be able to show all the nested calls and local variables. Right before calling main; sp = &process + 1; that is, at the word beyond `process`. – mevets Jan 06 '22 at 03:08