Forums

Wrong return address in ISR with GCC

Started by Erik Larsson September 10, 2007
Hi!

I've found a strange thing when using interrupts with GCC.

When I compile the code and run it GCC tends to change the code
depending in the content of the ISR-routine. When the interrupt occurs
the return address + 0x04 is stored in lr register. This is as it
should be. The first thing that happens in the ISR is a SUBS lr, lr,
#0x04 to get the correct return address. The the ISR will push all
used registers on the stack, including lr.

In the end of the ISR it will read from the stack to restore all
registers. It's now the strange thing happen.

The compiler can generate two different types of code here:
Version 1:
Start of ISR:
1ee8: e24ee004 sub lr, lr, #4 ; 0x4
1eec: e92d500f stmdb sp!, {r0, r1, r2, r3, ip, lr}
1ef0: e24dd004 sub sp, sp, #4 ; 0x4
End of ISR:
2004: e28dd004 add sp, sp, #4 ; 0x4
2008: e8bd500f ldmia sp!, {r0, r1, r2, r3, ip, lr}
200c: e25ef004 subs pc, lr, #4 ; 0x4

When popping register from the stack it will restore lr to it's
original state. Then PC is loaded with lr-0x04. Because we already did
that at the ISR start it will totally removed 8 instead of 4 and this
leads to some funny bugs

Version 2:
Start of ISR:
1ee8: e24ee004 sub lr, lr, #4 ; 0x4
1eec: e92d500f stmdb sp!, {r0, r1, r2, r3, ip, lr}
End of ISR:
1f44: e8fd900f ldmia sp!, {r0, r1, r2, r3, ip, pc}^
The differens here is that it will push all used register including
lr. But when it reads them back it will restore lr to PC instead of lr
and that will lead to the correct return address.


I've attached my C source code. Am I doing something wrong?


Hope someone can help me...

GCC version:
Using built-in specs.
Target: arm-elf
Configured with: ../gcc-4.1.1/configure --target=arm-elf
--prefix=/g/gnuarm-4.1.1 --enable-interwork --enable-multilib
--with-float=soft --with-newlib
--with-headers=../newlib-1.14.0/newlib/libc/include
--enable-languages=c,c++
Thread model: single
gcc version 4.1.1

**********************************
Version 1: (C code)
***********************************

__attribute__((interrupt("IRQ"))) void PF_TIM1_Interrupt(void);

void PF_TIM1_Interrupt(void)
{
uint32 i, a, b, c;
if(Tim1Counter >= 3)
{
g_Timer_Expired = TRUE;
Tim1Counter = 0;
}

Tim1Counter++;


for(i=0;i<1000;i++)
{
a = (123 % 100) *5;
b = (456 % 200) *8;
c = (a+b)*(a+b) % 100;
}

TIM_ClearFlag(TIM1, TIM0_INTERRUPT_FLAG); //Clear the interrupt
flag for the next interrupt
VIC0->VAR = 0x0000; //Acknowledge interrupt
}

**********************************
Version 1: (C and ASM)
***********************************


static void PF_TIM1_Interrupt(void)
{
1ee8: e24ee004 sub lr, lr, #4 ; 0x4
1eec: e92d500f stmdb sp!, {r0, r1, r2, r3, ip, lr}
1ef0: e24dd004 sub sp, sp, #4 ; 0x4
static uint32 i, a, b, c;
if(Tim1Counter >= 3)
1ef4: e59f3114 ldr r3, [pc, #276] ; 2010 <.text+0x2010>
1ef8: e5933000 ldr r3, [r3]
1efc: e3530002 cmp r3, #2 ; 0x2
1f00: 9a000005 bls 1f1c <PF_TIM1_Interrupt+0x34>
{
g_Timer_Expired = TRUE;
1f04: e59f2108 ldr r2, [pc, #264] ; 2014 <.text+0x2014>
1f08: e3a03001 mov r3, #1 ; 0x1
1f0c: e5c23000 strb r3, [r2]
Tim1Counter = 0;
1f10: e59f20f8 ldr r2, [pc, #248] ; 2010 <.text+0x2010>
1f14: e3a03000 mov r3, #0 ; 0x0
1f18: e5823000 str r3, [r2]
}

Tim1Counter++;
1f1c: e59f30ec ldr r3, [pc, #236] ; 2010 <.text+0x2010>
1f20: e5933000 ldr r3, [r3]
1f24: e2832001 add r2, r3, #1 ; 0x1
1f28: e59f30e0 ldr r3, [pc, #224] ; 2010 <.text+0x2010>
1f2c: e5832000 str r2, [r3]


for(i=0;i<1000;i++)
1f30: e59f20e0 ldr r2, [pc, #224] ; 2018 <.text+0x2018>
1f34: e3a03000 mov r3, #0 ; 0x0
1f38: e5823000 str r3, [r2]
1f3c: ea000025 b 1fd8 <PF_TIM1_Interrupt+0xf0>
{
a = (123 % 100) *5;
1f40: e59f20d4 ldr r2, [pc, #212] ; 201c <.text+0x201c>
1f44: e3a03073 mov r3, #115 ; 0x73
1f48: e5823000 str r3, [r2]
b = (456 % 200) *8;
1f4c: e59f20cc ldr r2, [pc, #204] ; 2020 <.text+0x2020>
1f50: e3a03d07 mov r3, #448 ; 0x1c0
1f54: e5823000 str r3, [r2]
c = (a+b)*(a+b) % 100;
1f58: e59f30bc ldr r3, [pc, #188] ; 201c <.text+0x201c>
1f5c: e5932000 ldr r2, [r3]
1f60: e59f30b8 ldr r3, [pc, #184] ; 2020 <.text+0x2020>
1f64: e5933000 ldr r3, [r3]
1f68: e0821003 add r1, r2, r3
1f6c: e59f30a8 ldr r3, [pc, #168] ; 201c <.text+0x201c>
1f70: e5932000 ldr r2, [r3]
1f74: e59f30a4 ldr r3, [pc, #164] ; 2020 <.text+0x2020>
1f78: e5933000 ldr r3, [r3]
1f7c: e0823003 add r3, r2, r3
1f80: e0010193 mul r1, r3, r1
1f84: e59f3098 ldr r3, [pc, #152] ; 2024 <.text+0x2024>
1f88: e0832391 umull r2, r3, r1, r3
1f8c: e1a032a3 mov r3, r3, lsr #5
1f90: e58d3000 str r3, [sp]
1f94: e59d3000 ldr r3, [sp]
1f98: e1a03103 mov r3, r3, lsl #2
1f9c: e59d2000 ldr r2, [sp]
1fa0: e0833002 add r3, r3, r2
1fa4: e1a02103 mov r2, r3, lsl #2
1fa8: e0833002 add r3, r3, r2
1fac: e1a03103 mov r3, r3, lsl #2
1fb0: e0631001 rsb r1, r3, r1
1fb4: e58d1000 str r1, [sp]
1fb8: e59f3068 ldr r3, [pc, #104] ; 2028 <.text+0x2028>
1fbc: e59d2000 ldr r2, [sp]
1fc0: e5832000 str r2, [r3]
1fc4: e59f304c ldr r3, [pc, #76] ; 2018 <.text+0x2018>
1fc8: e5933000 ldr r3, [r3]
1fcc: e2832001 add r2, r3, #1 ; 0x1
1fd0: e59f3040 ldr r3, [pc, #64] ; 2018 <.text+0x2018>
1fd4: e5832000 str r2, [r3]
1fd8: e59f3038 ldr r3, [pc, #56] ; 2018 <.text+0x2018>
1fdc: e5932000 ldr r2, [r3]
1fe0: e59f3044 ldr r3, [pc, #68] ; 202c <.text+0x202c>
1fe4: e1520003 cmp r2, r3
1fe8: 9affffd4 bls 1f40 <PF_TIM1_Interrupt+0x58>
}

TIM_ClearFlag(TIM1, TIM0_INTERRUPT_FLAG); //Clear the interrupt
flag for the next interrupt
1fec: e59f003c ldr r0, [pc, #60] ; 2030 <.text+0x2030>
1ff0: e3a01b1a mov r1, #26624 ; 0x6800
1ff4: eb001429 bl 70a0 <TIM_ClearFlag>
VIC0->VAR = 0x0000; //Acknowledge interrupt
1ff8: e59f2034 ldr r2, [pc, #52] ; 2034 <.text+0x2034>
1ffc: e3a03000 mov r3, #0 ; 0x0
2000: e5823030 str r3, [r2, #48]
}
2004: e28dd004 add sp, sp, #4 ; 0x4
2008: e8bd500f ldmia sp!, {r0, r1, r2, r3, ip, lr}
200c: e25ef004 subs pc, lr, #4 ; 0x4



**********************************
Version 2: (C code)
***********************************
__attribute__((interrupt("IRQ"))) void PF_TIM1_Interrupt(void);

void PF_TIM1_Interrupt(void)
{
//uint32 i, a, b, c;
if(Tim1Counter >= 3)
{
g_Timer_Expired = TRUE;
Tim1Counter = 0;
}

Tim1Counter++;

/*
for(i=0;i<1000;i++)
{
a = (123 % 100) *5;
b = (456 % 200) *8;
c = (a+b)*(a+b) % 100;
}
*/
TIM_ClearFlag(TIM1, TIM0_INTERRUPT_FLAG); //Clear the interrupt
flag for the next interrupt
VIC0->VAR = 0x0000; //Acknowledge interrupt
}

**********************************
Version 2: (C and ASM)
***********************************
void PF_TIM1_Interrupt(void)
{
1ee8: e24ee004 sub lr, lr, #4 ; 0x4
1eec: e92d500f stmdb sp!, {r0, r1, r2, r3, ip, lr}
//uint32 i, a, b, c;
if(Tim1Counter >= 3)
1ef0: e59f3050 ldr r3, [pc, #80] ; 1f48 <.text+0x1f48>
1ef4: e5933000 ldr r3, [r3]
1ef8: e3530002 cmp r3, #2 ; 0x2
1efc: 9a000005 bls 1f18 <PF_TIM1_Interrupt+0x30>
{
g_Timer_Expired = TRUE;
1f00: e59f2044 ldr r2, [pc, #68] ; 1f4c <.text+0x1f4c>
1f04: e3a03001 mov r3, #1 ; 0x1
1f08: e5c23000 strb r3, [r2]
Tim1Counter = 0;
1f0c: e59f2034 ldr r2, [pc, #52] ; 1f48 <.text+0x1f48>
1f10: e3a03000 mov r3, #0 ; 0x0
1f14: e5823000 str r3, [r2]
}

Tim1Counter++;
1f18: e59f3028 ldr r3, [pc, #40] ; 1f48 <.text+0x1f48>
1f1c: e5933000 ldr r3, [r3]
1f20: e2832001 add r2, r3, #1 ; 0x1
1f24: e59f301c ldr r3, [pc, #28] ; 1f48 <.text+0x1f48>
1f28: e5832000 str r2, [r3]

/*
for(i=0;i<1000;i++)
{
a = (123 % 100) *5;
b = (456 % 200) *8;
c = (a+b)*(a+b) % 100;
}
*/
TIM_ClearFlag(TIM1, TIM0_INTERRUPT_FLAG); //Clear the interrupt
flag for the next interrupt
1f2c: e59f001c ldr r0, [pc, #28] ; 1f50 <.text+0x1f50>
1f30: e3a01b1a mov r1, #26624 ; 0x6800
1f34: eb001421 bl 6fc0 <TIM_ClearFlag>
VIC0->VAR = 0x0000; //Acknowledge interrupt
1f38: e59f2014 ldr r2, [pc, #20] ; 1f54 <.text+0x1f54>
1f3c: e3a03000 mov r3, #0 ; 0x0
1f40: e5823030 str r3, [r2, #48]
}
1f44: e8fd900f ldmia sp!, {r0, r1, r2, r3, ip, pc}^

On Sep 10, 10:18 am, Erik Larsson <karl.erik.lars...@gmail.com> wrote:
> Hi! > > I've found a strange thing when using interrupts with GCC. > > When I compile the code and run it GCC tends to change the code > depending in the content of the ISR-routine. When the interrupt occurs > the return address + 0x04 is stored in lr register. This is as it > should be. The first thing that happens in the ISR is a SUBS lr, lr, > #0x04 to get the correct return address. The the ISR will push all > used registers on the stack, including lr. > > In the end of the ISR it will read from the stack to restore all > registers. It's now the strange thing happen. >
<snip>
> I've attached my C source code. Am I doing something wrong?
Using __attribute__((interrupt("IRQ"))), Use an assembly interrupt stub instead. I'm mostly serious here. I've made it a policy to avoid architecture specific compiler extensions where possible, they tend to be the buggiest part of the compilers (and most of the issues I've run into have been in commercial compilers by the by). Given a) how easy, and b) how educational an interrupt stub is to write it's not worth avoiding the task. Heck the simplest is to borrow one from someone else and verify/modify it to suit. Robert
On Mon, 10 Sep 2007 07:18:35 -0700, Erik Larsson
<karl.erik.larsson@gmail.com> wrote:

>Hi! > >I've found a strange thing when using interrupts with GCC. > >When I compile the code and run it GCC tends to change the code >depending in the content of the ISR-routine. When the interrupt occurs >the return address + 0x04 is stored in lr register. This is as it >should be. The first thing that happens in the ISR is a SUBS lr, lr, >#0x04 to get the correct return address. The the ISR will push all >used registers on the stack, including lr. > >In the end of the ISR it will read from the stack to restore all >registers. It's now the strange thing happen. >
[Code snipped] Go to http://www.hitex.co.uk/download.html There are nice guides for both ARM7 and ARM9 which explains the ARM interrupt structure in detail including example snippets for gcc. I am using the interrupt attribute with gcc 4.2 for arm without any problems. Regards Anton Erasmus
Robert Adsett wrote:
...
> Using __attribute__((interrupt("IRQ"))), Use an assembly interrupt > stub instead. I'm mostly serious here.
... The same advice from me. Application stared cgeashing after change of the newlib version (?!) After a day of debugging I found problem in the ISR entry/exit code generated by GCC. After switch to assembly all is working ok. Regards, -- Artur Lipowski
"Artur Lipowski" <LAL@pro.onet.pl> wrote in message 
news:fc3q0l$9cl$1@news.onet.pl...
> Robert Adsett wrote: > ... >> Using __attribute__((interrupt("IRQ"))), Use an assembly interrupt >> stub instead. I'm mostly serious here. > ... > > The same advice from me. > Application stared cgeashing after change of the newlib version (?!) > After a day of debugging I found problem in the ISR entry/exit code > generated by GCC. After switch to assembly all is working ok. > > > Regards, > --
In which version did you find a problem? I have not seen anything suspect in the since older V3.x versions. -- Regards, Richard. + http://www.FreeRTOS.org 13 official architecture ports, 1000 downloads per week. + http://www.SafeRTOS.com Certified by T&#2013265948;V as meeting the requirements for safety related systems.
FreeRTOS.org wrote:
> In which version did you find a problem? I have not seen anything suspect > in the since older V3.x versions.
GCC 4.1.2 Regards, -- Artur Lipowski