CCS C Software and Maintenance Offers
FAQFAQ   FAQForum Help   FAQOfficial CCS Support   SearchSearch  RegisterRegister 

ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 

CCS does not monitor this forum on a regular basis.

Please do not post bug reports on this forum. Send them to CCS Technical Support

I2C global int8 can be altered by user code in ISR

 
Post new topic   Reply to topic    CCS Forum Index -> General CCS C Discussion
View previous topic :: View next topic  
Author Message
allenhuffman



Joined: 17 Jun 2019
Posts: 552
Location: Des Moines, Iowa, USA

View user's profile Send private message Visit poster's website

I2C global int8 can be altered by user code in ISR
PostPosted: Thu Aug 29, 2019 3:06 pm     Reply with quote

"...or any int8 used by any library code that is accessed in an ISR."

Just a fun warning about something I ran into at work recently. We had an issue where the I2C communications would hang, and finally tracked it down to the use of global int8s in the code.

If you have a global "int8 value;" that gets set to zero, it might generate code like this:

Code:
                         ..............................     value = 0;
0022C EF6800         CLR.B   800            : [800] = 0


But if you have enough globals being used (more than 6K on my PIC24), it might generate multiple "load, modify, store" instructions for the same C code:

Code:
                         ..............................     value = 0;
0022E 817880         MOV     2F10,W0        : W0 = [2F10]
00230 B3C000         MOV.B   #0,W0L         : W0L = 0 
00232 897880         MOV     W0,2F10        : [2F10] = W0


In our situation, there was an int8 called I2C_STATUS (or similar) being used by the CCS I2C code, and it "just happened" to end up sharing a 16-bit word with one of our int8 variables.

Our code "just happened" to be running that multiple step "load, modify, restore" when an IRQ happened, then the ISR would modify the value and store it back, then our code would resume and store back what we had, overwriting what the ISR did to the previous value.

Once we saw this, it was an "oh, sure, of course that can happen" moment. It's very similar to how you always want to protect variable access with semaphores when they could be accessed from other bets of code via threads, ISRs, etc.

In this case, though, the int8 is part of a library, so any int8 we used could have ended up sharing that word and potentially causing an unexpected value change.

So keep in mind, avoid using int8s in the ISR "just in case" you have an int8 in the rest of your code that might cause this to happen. You can't tell where your value is unless you look at the symbol table file.

We can change all our values to int16+ and prevent any of our code from doing something like this moving forward.

Fun stuff!

Side note...

This is just like the classic ISR issue of:

Code:
int g_dataReady = 0;

isr_data()
{
   g_dataReady++;
   // store data in a buffer or whatever...
}

something()
{
   while (processing)
   {
      if (g_dataReady > 0)
      {
         processData(); // A
         g_dataReady = 0; // B
      }
   }
}


For those without the same background, imagine data comes in and the while() code goes to process it. After done processing, it is about to set that counter back to 0. An ISR comes in, and dataReady increments. Then the main code continues, and proceeds to set it to 0.

Now that data seems "lost".

Traditionally, you'd protect accessing the g_dataReady valye with a semaphore or whatever mechanism the OS or environment supports. Pretty basic stuff, but in the case of code generated by the compiler, there wouldn't be a way to know that's happening, or get into that code to lock around the 16-bit value.

Fun stuff!
_________________
Allen C. Huffman, Sub-Etha Software (est. 1990) http://www.subethasoftware.com
Embedded C, Arduino, MSP430, ESP8266/32, BASIC Stamp and PIC24 programmer.
http://www.whywouldyouwanttodothat.com ?
allenhuffman



Joined: 17 Jun 2019
Posts: 552
Location: Des Moines, Iowa, USA

View user's profile Send private message Visit poster's website

PostPosted: Thu Aug 29, 2019 4:03 pm     Reply with quote

Sharing some of my notes. In my environment,, globals start at the 2K mark (0x800). After 8K (0x2000) it doesn't look like there is a MOV op code that works with memory, so the compiler loads a value into a register, modifies the register, then writes the register back.

Code:
                         ..............................                         // Offset   -> Address
                         ..............................     memory[0] = 0;      // 0x0      -> 0x0800
00228 EF6800         CLR.B   800            : [800] = 0
                         ..............................     
                         ..............................     memory [6143] = 0;  // 0x17ff   -> 0x1fff
0022A EF7FFF         CLR.B   1FFF           : [1FFF] = 0
                         ..............................     
                         ..............................     memory [6144] = 0;  // 0x1800   -> 0x2000
0022C 810000         MOV     2000,W0        : W0 = [2000]
0022E B3C000         MOV.B   #0,W0L         : W0L = 0 
00230 890000         MOV     W0,2000        : [2000] = W0
                         ..............................     
                         ..............................     
                         ..............................     memory[0] = 1;      // 0x0      -> 0x0800
00232 B3C010         MOV.B   #1,W0L         : W0L = 1 
00234 B7E800         MOV.B   W0L,800        : [800] = W0L
                         ..............................     
                         ..............................     memory [6143] = 1;  // 0x17ff   -> 0x1fff
00236 B3C010         MOV.B   #1,W0L         : W0L = 1 
00238 B7FFFF         MOV.B   W0L,1FFF       : [1FFF] = W0L
                         ..............................     
                         ..............................     memory [6144] = 1;  // 0x1800   -> 0x2000
0023A 810000         MOV     2000,W0        : W0 = [2000]
0023C B3C010         MOV.B   #1,W0L         : W0L = 1 
0023E 890000         MOV     W0,2000        : [2000] = W0

_________________
Allen C. Huffman, Sub-Etha Software (est. 1990) http://www.subethasoftware.com
Embedded C, Arduino, MSP430, ESP8266/32, BASIC Stamp and PIC24 programmer.
http://www.whywouldyouwanttodothat.com ?
jeremiah



Joined: 20 Jul 2010
Posts: 1346

View user's profile Send private message

PostPosted: Thu Aug 29, 2019 8:23 pm     Reply with quote

Make sure you report it to CCS support. I just submitted a similar type bug (though caused by something else) and they fixed it for the next upcoming release.
Ttelmah



Joined: 11 Mar 2010
Posts: 19506

View user's profile Send private message

PostPosted: Fri Aug 30, 2019 12:05 am     Reply with quote

As a comment, it used to be standard practice on quite a few compilers
to always word align any 8bit variables accessed in IRQ's. It sounds as if the
same really needs to be done with CCS.

As a comment to the original poster, he says 'int16+'. Remember that anything
over an int16, itself needs protection when accessed inside an IRQ, since you then automatically have the potential for two accesses to be involved....

Only the 'native' processor size (so int8 on PIC16/18, and int16 on PIC24/33)
has inherent protection against this problem.
PCM programmer



Joined: 06 Sep 2003
Posts: 21708

View user's profile Send private message

Re: I2C global int8 can be altered by user code in ISR
PostPosted: Fri Aug 30, 2019 2:44 am     Reply with quote

allenhuffman wrote:

But if you have enough globals being used (more than 6K on my PIC24).

6000 individual named global variables ? Why ? I was taught to use only
a few, and only if necessary. Maybe you mean a global buffer.
temtronic



Joined: 01 Jul 2010
Posts: 9225
Location: Greensville,Ontario

View user's profile Send private message

PostPosted: Fri Aug 30, 2019 4:43 am     Reply with quote

I won't live long enough to type up 6000 individually named variables, sigh...
That's a LOT of typing ( and typos for me.......)
I'm thinking some kind of buffer as well....
Jay
allenhuffman



Joined: 17 Jun 2019
Posts: 552
Location: Des Moines, Iowa, USA

View user's profile Send private message Visit poster's website

Re: I2C global int8 can be altered by user code in ISR
PostPosted: Fri Aug 30, 2019 7:39 am     Reply with quote

PCM programmer wrote:
allenhuffman wrote:

But if you have enough globals being used (more than 6K on my PIC24).

6000 individual named global variables ? Why ? I was taught to use only
a few, and only if necessary. Maybe you mean a global buffer.


There are, indeed, a lot of variables (including buffers).

Code:
               ROM used:   38608 bytes (22%)
                           Largest free fragment is 44544
               RAM used:   16969 (52%) at main() level
                           17195 (53%) worst case


Why coders do what they do remains one of the mysteries of the universe. I am more on the "clean code" side and try to localize variables and functions only where they need to be, to avoid polluting the rest of the project. Oh, the stories I could tell from various jobs...
_________________
Allen C. Huffman, Sub-Etha Software (est. 1990) http://www.subethasoftware.com
Embedded C, Arduino, MSP430, ESP8266/32, BASIC Stamp and PIC24 programmer.
http://www.whywouldyouwanttodothat.com ?


Last edited by allenhuffman on Fri Aug 30, 2019 7:59 am; edited 2 times in total
allenhuffman



Joined: 17 Jun 2019
Posts: 552
Location: Des Moines, Iowa, USA

View user's profile Send private message Visit poster's website

PostPosted: Fri Aug 30, 2019 7:39 am     Reply with quote

jeremiah wrote:
Make sure you report it to CCS support. I just submitted a similar type bug (though caused by something else) and they fixed it for the next upcoming release.


I did not consider it a bug, per se, but I see another comment here about compilers protecting variables used in ISRs which seems like a smart thing to do. I'll write something up for them.
_________________
Allen C. Huffman, Sub-Etha Software (est. 1990) http://www.subethasoftware.com
Embedded C, Arduino, MSP430, ESP8266/32, BASIC Stamp and PIC24 programmer.
http://www.whywouldyouwanttodothat.com ?
allenhuffman



Joined: 17 Jun 2019
Posts: 552
Location: Des Moines, Iowa, USA

View user's profile Send private message Visit poster's website

PostPosted: Fri Aug 30, 2019 7:41 am     Reply with quote

Ttelmah wrote:
As a comment, it used to be standard practice on quite a few compilers
to always word align any 8bit variables accessed in IRQ's. It sounds as if the
same really needs to be done with CCS.

As a comment to the original poster, he says 'int16+'. Remember that anything
over an int16, itself needs protection when accessed inside an IRQ, since you then automatically have the potential for two accesses to be involved....

Only the 'native' processor size (so int8 on PIC16/18, and int16 on PIC24/33)
has inherent protection against this problem.


Good point - I hadn't thought about the implication of using 32-bit/64-bit variables that are "faked" on 16-bit architectures.
_________________
Allen C. Huffman, Sub-Etha Software (est. 1990) http://www.subethasoftware.com
Embedded C, Arduino, MSP430, ESP8266/32, BASIC Stamp and PIC24 programmer.
http://www.whywouldyouwanttodothat.com ?
allenhuffman



Joined: 17 Jun 2019
Posts: 552
Location: Des Moines, Iowa, USA

View user's profile Send private message Visit poster's website

PostPosted: Fri Aug 30, 2019 9:42 am     Reply with quote

I need to rename this thread or start a new one. It gets better.

We have multiple boards, and are finding similar issues in other boards. The next one was us having this in our code:

rtc_time_t realTimeClock;

That is a 9-byte structure (9 int8s).

In memory, it happened to be lined up right before @I2C_STATUS (internal variable used by i2c library code).

We then saw corruption of the last byte of the realTimeClock structure.

We can control where we declare "realTimeClock", or add a bogus int8 after it in our program (since the compiler doesn't seem to be optimizing unused things away with our settings).

But … Whac-A-Mole! Smile

https://en.wikipedia.org/wiki/Whac-A-Mole
_________________
Allen C. Huffman, Sub-Etha Software (est. 1990) http://www.subethasoftware.com
Embedded C, Arduino, MSP430, ESP8266/32, BASIC Stamp and PIC24 programmer.
http://www.whywouldyouwanttodothat.com ?
allenhuffman



Joined: 17 Jun 2019
Posts: 552
Location: Des Moines, Iowa, USA

View user's profile Send private message Visit poster's website

PostPosted: Fri Aug 30, 2019 9:50 am     Reply with quote

More compiler Whac-a-Mole... I have a tiny test program with these globals:

Code:
int8 value1;

rtc_time_t  realTimeClock;

int8 value2;
int8 value3;
int8 value4;


The compiler is smart enough to use available int8 bytes, so the actual layout of variables in memory is:

Code:
800     value1
801     value2
802-80A realTimeClock
80B     value3
80C     value4


It places value2 after value1 since there is a byte free. Thus, that @I2C_STATE global variable can be placed in a 16-bit value that is shared with another int8. Nothing we can do about that with library code, unless there is a pragma or something that helps.

There is the __attribute_x thing which can force variables to be aligned. I chose a 2-byte boundry (even bytes):

Code:
int8 value1 __attribute__((aligned(0x2)));

rtc_time_t  realTimeClock;

int8 value2 __attribute__((aligned(0x2)));
int8 value3 __attribute__((aligned(0x2)));
int8 value4 __attribute__((aligned(0x2)));


Now we get everything starting on an even byte boundry, in the order I declared them:

Code:
800     value1
802-80A realTimeClock
80C     value2
80E     value3
810     value4


That's kind of silly, since if we control the code, why not switch to just using native ints? Then we only have to worry about two int8s in library code conflicting -- not sure we have any control over that.

Fun with memory day continues...
_________________
Allen C. Huffman, Sub-Etha Software (est. 1990) http://www.subethasoftware.com
Embedded C, Arduino, MSP430, ESP8266/32, BASIC Stamp and PIC24 programmer.
http://www.whywouldyouwanttodothat.com ?
allenhuffman



Joined: 17 Jun 2019
Posts: 552
Location: Des Moines, Iowa, USA

View user's profile Send private message Visit poster's website

PostPosted: Fri Aug 30, 2019 9:54 am     Reply with quote

For fun … a quick #define macro that will add or not add the attribute as needed. Make the define there, but empty, and you get default behavior. Define the macro to the attribute, and you get alignment:

Code:
//#define ALIGN __attribute__((aligned(0x2)))
#define ALIGN

int8 value1 ALIGN;

rtc_time_t  realTimeClock;

int8 value2 ALIGN;
int8 value3 ALIGN;
int8 value4 ALIGN;


Now I can easily switch it on and off to look at what the compiler is doing. Fun stuff.
_________________
Allen C. Huffman, Sub-Etha Software (est. 1990) http://www.subethasoftware.com
Embedded C, Arduino, MSP430, ESP8266/32, BASIC Stamp and PIC24 programmer.
http://www.whywouldyouwanttodothat.com ?
allenhuffman



Joined: 17 Jun 2019
Posts: 552
Location: Des Moines, Iowa, USA

View user's profile Send private message Visit poster's website

PostPosted: Fri Aug 30, 2019 10:02 am     Reply with quote

_attribute_x help file entry:

Quote:
By default each element in a struct or union are padded to be evenly spaced by the size of 'int'. This is to prevent an address fault when accessing an element of struct. See the following example:

struct
{
int8 a;
int16 b;
} test;

On architectures where 'int' is 16bit (such as dsPIC or PIC24 microcontrollers), 'test' would take 4 bytes even though it is comprised of3 bytes. ...


While this example is true, the wording may be a bit misleading. It will not pad int8s to be on even byte alignments unless an int8 is followed by a non-int8 in the structure.

That's the behavior I would expect. When I read the phrase "By default each element in a struct or union are padded to be evenly spaced by the size of 'int'." I wondered if they were padding int8s.
_________________
Allen C. Huffman, Sub-Etha Software (est. 1990) http://www.subethasoftware.com
Embedded C, Arduino, MSP430, ESP8266/32, BASIC Stamp and PIC24 programmer.
http://www.whywouldyouwanttodothat.com ?
Ttelmah



Joined: 11 Mar 2010
Posts: 19506

View user's profile Send private message

PostPosted: Fri Aug 30, 2019 10:43 am     Reply with quote

No, they are talking about all elements. Remember there are lots of
things that can use an odd number of bytes. A structure, an int8, an
int1.
Their example there is for the situation where you want to pack things
for something like a structure that is then passed to another device.
The opposite to what is involved here.

The advantage of word aligning bytes, rather than using an int16, is that
some things can be slightly faster done on a byte, especially if this is then
going to be used for something like I/O.
Display posts from previous:   
Post new topic   Reply to topic    CCS Forum Index -> General CCS C Discussion All times are GMT - 6 Hours
Page 1 of 1

 
Jump to:  
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum


Powered by phpBB © 2001, 2005 phpBB Group