Subroutines and Stacks (Problem with Return address)

Go To Last Post
12 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi, i am running this code in order to add the data from the sram addreses 0x060 to 0x063 by using two subroutines (the first one adds the data of sram addresses into R1,R0 and the second performs the sign extension in the register R3). Although, in the second return(RET) from the ADDEXT subroutine the program counter returns to the first line of code instead in the point of where the first subroutine is rcalled.Can anyone help with debugging i think there is a mistake in the return address of the first RET in the subroutine ADDEXT, Thank you in Advance.

 

.include"m32def.inc"

.equ ram=0x060
.equ datalen=4
.def metritis=r16

ldi r19,low(RAMEND)
OUT SPL,R19
ldi r19,high(RAMEND)
OUT SPH,R19
ldi r18,0x0A
ldi metritis,datalen
ldi XL,low(ram)
ldi XH,high(ram)
loop:
    st X+,r18
    INC R18
    dec metritis
    brne loop
ldi XL,low(ram)
ldi XH,high(ram)
LD R22,-X

RCALL ADDEXT
ADDEXT:

    ADIW XH:XL,1
    RCALL SignExt
    ADD R0,R2
    ADC R1,R3
    RET

SignExt://Sign Extension
    ld R2,X
    CLR R3
    TST R2
    BRPL synex
    DEC R3
    synex:
    ret
telos:
    rjmp telos

Last Edited: Tue. Dec 4, 2018 - 09:00 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0
 

.include"m32def.inc"

.equ ram=0x060
.equ datalen=4
.def metritis=r16

ldi r19,low(RAMEND)
OUT SPL,R19
ldi r19,high(RAMEND)
OUT SPH,R19
ldi r18,0x0A
ldi metritis,datalen
ldi XL,low(ram)
ldi XH,high(ram)
loop:
    st X+,r18
    INC R18
    dec metritis
    brne loop
ldi XL,low(ram)
ldi XH,high(ram)
LD R22,-X

RCALL ADDEXT     <----- what do you expect will happen upon return from this call???
ADDEXT:

    ADIW XH:XL,1
    RCALL SignExt
    ADD R0,R2
    ADC R1,R3
    RET

SignExt://Sign Extension
    ld R2,X
    CLR R3
    TST R2
    BRPL synex
    DEC R3
    synex:
    ret
telos:
    rjmp telos

 

Click Link: Get Free Stock: Retire early! PM for strategy

share.robinhood.com/jamesc3274

 

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

In the first RET from the first subroutine the program counter will move to the ADIW XH:XL,1 although in the second RET of the same subroutine the program counter will go at the top of the code as i have already mentioned maybe because at the first return(RET) the program counter had to go to the //RCALL ADDEXT and not //ADIW XH:XL,1.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

MrSpikeHawk wrote:
although in the second RET of the same subroutine the program counter will go at the top of the code

 

How do you figure it will "return" to the top of code?

 

Only one return address was pushed on the stack by the RCALL,

the second time the RET is hit, there is no valid address on the stack to return too!

 

Try single stepping this in the simulator to see what happens after the second return.

 

Jim

 

Click Link: Get Free Stock: Retire early! PM for strategy

share.robinhood.com/jamesc3274

 

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I runned the program with the Step into(F11) in order to see if both RET are working correctly but as i mentioned when the first RET (ADDEXT subroutine) is executed for the second time then the program counter returns in the first line of code instead of where it is called (RCALL (ADDEXT). As you correctly said in the second return of the first subroutine there is no valid address to return to however I can't find any possible solution to that problem.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

try rjmp telos after the rcall.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Why are you heading into addext without using a call?

 

You call it, ok fine.  As soon as it is finished , you walk right back into it.  Put your subs somewhere else, like at the bottom of your code where you won't accidentally enter them.

 

you may like this

http://www.acorn-kernel.net/kernelunleashed/readarticle?ClassKey=ahJzfmFjb3JuLWtlcm5lbC1ocmRyDwsSB0FydGljbGUYybUQDA

 

When in the dark remember-the future looks brighter than ever.   I look forward to being able to predict the future!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I tried it and it doesn't work for some reason in the second return of the first subroutine the stack pointer loses the correct return adrress.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I tried it and it doesn't work for some reason in the second return of the first subroutine the stack pointer loses the correct return address.

 

both of your subs are fine in terms of you will call them and they WILL return, no stack problem

 

What command do you want to happen after RCALL ADDEXT ???!?   you will enter the sub without a call!!!!!  Obviously you can't do that.

After the rcall put in whatever commands you want to run thereafter.

 

You must provide an updated listing of all your changes, or it is not possible to diagnose further. 

 

 

 

When in the dark remember-the future looks brighter than ever.   I look forward to being able to predict the future!

Last Edited: Wed. Dec 5, 2018 - 12:51 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

You can observe the operation on the simulator. Note that the stack pointer ‘points’ to the stack which is in ram. The return address is stored on the stack, not the stack pointer.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thanks guys problem fixed the code operates correctly i had to RCALL the ADDEXT subroutine from another routine and as Raving mentioned i should have put commands after the RCALL ADDEXT in order to return at the right spot because previous after the RCALL ADDEXT there where no commands for the program counter to move on so that's why it went at the top of the code.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Sure, you are calling a loop, just one RCALL with two RET... without the first return address at the stack, it will return to any value at the stack, that for coincidence is the begging of your code.

 

For example:

START:
   RCALL GO

GO:
   LD R19, 5
   RCALL DEMO

DEMO:
   DEC R19
   BRNE PC-1
   RET

Stack Empty

 

The RCALL on START label pushes its return address into the stack, the return address is exactly at the GO label, and then it jumps to GO label.

 

STACK $3FF   GO-ADDRESS-LOW

STACK $3FE   GO-ADDRESS-HIGH

 

On the GO label, it loads R19 with 5 and RCALL DEMO.

The GO label return address stays in the stack, and right below it writes the return address after the RCALL DEMO, that is exactly the DEMO label, and jumps to DEMO.

 

STACK $3FF   GO-ADDRESS-LOW

STACK $3FE   GO-ADDRESS-HIGH

STACK $3FD   DEMO-ADDRESS-LOW

STACK $3FC   DEMO-ADDRESS-HIGH

 

On DEMO, it decrements R19 (5 steps) until zero and RETURN to the address saved on 3FC:3FD

 

But Returns where?  To the address pointed by the Stack Pointer 3FC:3FD, it is exactly the last above, the DEMO routine address.

So it "returns" to DEMO, Stack Pointer increments two address, and jumps and runs DEMO again, decrements R19 (now 256 steps) to zero, and RETURN

 

STACK $3FF   GO-ADDRESS-LOW

STACK $3FE   GO-ADDRESS-HIGH

 

But Returns where? Now the Stack Pointer is pointing to the return address pointed to 3FE:3FF to GO.

So it "returns" to GO, Stack Pointer again increments two address, and jumps to GO

 

STACK EMPTY

 

On GO, it loads R19 with 5 and RCALL DEMO, so the return address of the next instruction of RCALL DEMO is also the DEMO label address, this address is stored into the Stack and Stack Pointer is decremented by 2.

 

STACK $3FF   DEMO-ADDRESS-LOW

STACK $3FE   DEMO-ADDRESS-HIGH

 

On DEMO, it decrements R19 (5 steps) until zero and RETURN to the address saved on 3FE:3FF that is the beginning of the DEMO routine.  This is the first time it runs DEMO on this call.

At the DEMO return instruction, it will return to the beginning of DEMO routine, that address in on 3FE:3FF at stack, stack pointer is incremented by 2, and it executes the DEMO by the second time in this call.

 

STACK EMPTY

 

At the end of this second run on DEMO, it will return, to where? nobody knows, since stack is empty, there is no valid address to jump to.

No one knows what is in the memory position $400:401, may be some data pushed before, but uncertain.

 

The above, and also your routine lacks the stack to have something to return after the second embedded RET.

 

There are situations where you use this feature in your favor.  

For example, having a 100us timer, and needing to have also a 200us timer, you don't need to have a second routine.

T200US: 
          RCALL T100US
T100US:
          CLR R19
          DEC R19
          BRNE PC-1
          RET

 

The T100US is a trivial loop of decrementing R19 from 256 to zero and return, in a AVR core running at 8MHz will give you aproximately 100us.

But, if you call it twice, will have 2 x 100us, so if you use a RCALL for the next address that happens to be the T100us, that is exactly what happens.

 

Routine X calls the T100US and after 100us the RET instructions comes back to the next instruction of what called T100US.

Routine Y calls the T200US it calls the T100US, the RET send PC back to the next instructions of what called T100US, that is exactly the T100US label, so T100US runs again, its second return will jumps back to the next instruction after what called T200US, 200us after the call. 

 

T400US:
          RCALL T200US
T200US: 
          RCALL T100US
T100US:
          CLR R19
          DEC R19
          BRNE PC-1
          RET

 

The above created another time delay of 400us just making a second loop, calling T200 that calls T100, ending running 200us and calling it again ending in 400us delay.

 

T500US:
          RCALL T100US
T400US:
          RCALL T200US
T200US: 
          RCALL T100US
T100US:
          CLR R19
          DEC R19
          BRNE PC-1
          RET

 

The above uses T500US label on top, that calls first the 100us, then the 400us, totaling 500us.

But always need to respect an EVEN count of RCALL and RET, if not you will have a ROGUE return, computer crazy.

 

There is a pretty and nice way to create a program, where the Stack never received the return address from a CALL or RCALL, instead you put manually the return address by pushing registers into Stack with the return stack.

That, of course, you will need to know the addresses of the labels you are jumping to.

Suppose some routine defined that it needs to jump to label TUMB1, and TUMB1 is documented to be exactly at address $1A40 (in the code).

That routine can prepare the jump doing this:

 

    LDI  R16, 0x20
    PUSH R16
    LDI  R16, 0x0D
    PUSH R16
    RET

Both PUSH R16 will put the value 0x0D20 into the stack (big-endian scheme).

The RET instruction will jump to this address, that is in fact 0x0D20 x 2 = 0x1A40 in bytes.

Remember, instructions use 2 bytes (word), so the stack also use words addressing, the real bytes address / 2.

In sumary, RCALL always store into the STACK the number of the next INSTRUCTION to be executed upon RETURN, not addresses exactly.

So, the instruction located at address 0x1A40 is exactly the possible instruction # 0x0D20 in the program.

 

Can you imagine if the value of R16 above is the result of some crazy decription calculation to jump to an address that is created on fly (but valid routine start address), and not literally written in the code?

A hacker making reverse engineering will become crazy trying to understand where the values of R16 came from to the jump, and where this routine goes now...

 

This is somehow equivalent to the IJMP instruction, that jumps to the address value contained in the Z register, R31:R30 registers.

ICALL also CALLS a routine with address stored in the Z register.

The IJMP and ICALL is not available in all devices.

Wagner Lipnharski
Orlando Florida USA