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

Reading off the end of memory

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



Joined: 14 Apr 2015
Posts: 28
Location: Boulder Creek, CA

View user's profile Send private message

Reading off the end of memory
PostPosted: Mon Jun 01, 2015 6:47 pm     Reply with quote

First of all, this is on an 18LF45K22 - 32K bytes flash, 256 bytes EEPROM.

Some experimentation has shown me that if you read off the end of Flash or EEPROM, no indication of having done something wrong is given. For example, the following code segment, despite having obvious errors, compiles and runs like nothing is wrong:
Code:

    uint16_t prog_mem[10] ;
    uint8_t  eepr_mem[10] ;

    for(z = 0; z < 10; z++)
    {
        prog_mem[z] = 0xDDDD ;
        eepr_mem[z] = 0x22 ;
    }
    z = 0 ;
    prog_mem[z] = read_program_eeprom(0) ;
    eepr_mem[z] = read_eeprom(0) ;
    z++ ;

    // z = 1
    prog_mem[z] = read_program_eeprom(128) ;
    eepr_mem[z] = read_eeprom(128) ;
    z++ ;

    // z = 2
    prog_mem[z] = read_program_eeprom(254) ;
    eepr_mem[z] = read_eeprom(254) ;
    z++ ;

    // z = 3
    prog_mem[z] = read_program_eeprom(255) ;
    eepr_mem[z] = read_eeprom(255) ;
    z++ ;

    // z = 4
    prog_mem[z] = read_program_eeprom(257) ;
    eepr_mem[z] = read_eeprom(257) ;
    z++ ;

    // z = 5
    prog_mem[z] = read_program_eeprom(32766) ;
    eepr_mem[z] = read_eeprom(1025) ;
    z++ ;

    // z = 6
    prog_mem[z] = read_program_eeprom(32767) ;
    eepr_mem[z] = read_eeprom(32767) ;
    z++ ;

    // z = 7
    prog_mem[z] = read_program_eeprom(48*1024) ;
    eepr_mem[z] = read_eeprom(48*1024) ;
    z++ ;

    // z = 8
    prog_mem[z] = read_program_eeprom(48*1024+1) ;
    eepr_mem[z] = read_eeprom(48*1024+1) ;
    z++ ;


In fact, for all values in eepr_mem, except [9], the stored value is 0xFF. The addresses for z= 4, 5, 6, 7, and 8 are clearly out of range.

For the values in prog_mem where z is 7 or 8, I get back 0's. These are also clearly out of range.

Is this what is expected? No errors are indicated in the manual.
_________________
D. Scruggs
PCM programmer



Joined: 06 Sep 2003
Posts: 21708

View user's profile Send private message

PostPosted: Mon Jun 01, 2015 7:51 pm     Reply with quote

This compiler is close to the hardware. You have to put in your own
qualifiers. For example, you can put in the if() statements shown below:
Code:

#include <18F45K22.h>
#fuses INTRC_IO, NOWDT
#use delay(clock=4M)
#use rs232(baud=9600, UART1, ERRORS)

//===========================
void main()
{
int16 flash_addr;
int8 eeprom_addr;

// Display the amount of data eeprom and flash.
// This is done just for informational purposes.
printf("%lu \r", getenv("DATA_EEPROM"));
printf("%lu \r", getenv("PROGRAM_MEMORY"));

flash_addr = 0x2000;

if(flash_addr < getenv("PROGRAM_MEMORY"))
 {
  printf("OK to write to flash \r");

 }

eeprom_addr = 64;
   
if(eeprom_addr < getenv("DATA_EEPROM"))
 {
  printf("OK to write to data eeprom \r");
 
 }


while(TRUE);
}


You can run this program in MPLAB simulator with UART1 output enabled
and see the results in the MPLAB Output window.
Ttelmah



Joined: 11 Mar 2010
Posts: 19552

View user's profile Send private message

PostPosted: Tue Jun 02, 2015 1:46 am     Reply with quote

If you think about the read_eeprom function, this is written to accept an int8. As such if you send it 1024 (for example), it'll never receive this value. It'll receive '0' (the int8 part of 1024), and will merrily read this address. This is inherent in C itself, where there is no actual type checking on numbers passed to functions (though it would be possible to add this using the overload abilities). This is a 'power' of C (allowing you to switch value types easily), but is also a 'weakness' (since the function itself cannot even 'know' that an invalid value has been passed).

On the bounds for the main memory, on more sophisticated processors with hardware memory management, you can have the hardware signal an error if you attempt to read or write an invalid address. However to do this in software for every single operation would slow the functions down massively, which is not after all wanted if you (as the user) make sure that your 'end' addresses are both legal, then you want the read/write operations on every address between to be done as fast as possible (without bounds checking). So it becomes your responsibility to do all checking (as PCM_Programmer shows).

If you look (for instance) at the bootloader example, it tests if the data it is being asked to write (for a low memory bootloader), is above the end of the bootloader, and below the end of the memory.

You have to understand that C, is closer to assembler, than a 'high level' language. It allows you to directly control and access the chip hardware at the lowest level. If you want bounds checking, then you have to add this yourself.
RF_Developer



Joined: 07 Feb 2011
Posts: 839

View user's profile Send private message

PostPosted: Tue Jun 02, 2015 2:33 am     Reply with quote

Another way of looking at it, is that C doesn't even try to protect programmers from their own mistakes. That way it can produce efficient, fast running code.

There are many, many ways of shooting yourself, and the hardware, in the foot in C than there are in many other languages, including later, higher-level C derivatives, such as C++ and C#. Possibly the most common way is by abuse of pointers, but there are many others. There are no bounds checks and any variable can be easily converted into anything else, and often is without the programmer necessarily being aware of it, e.g. automatic promotion of variables in expressions. In the hands of an experienced programmer, this gives great power and tight, fast running code, but gives the inexperienced, reckless and clueless (in fact, everyone!) no protection whatsoever. Is that a "weakness" or a "strength"? That's for individuals to decide and argue about, and they most certainly do, but I feel that this is at the core of C's enduring popularity and usefulness. That and its simple, direct memory model (i.e. not relying on dynamic memory and data stacks) which is well-suited to small scale processors with limited memory such as PICs.

Put yet another way - you run off the end of memory deliberately. "No errors are indicated in the manual" suggests that no out of range checks are made, in fact that no checks of any type are made. So, what did you expect to happen?
bcfd36



Joined: 14 Apr 2015
Posts: 28
Location: Boulder Creek, CA

View user's profile Send private message

PostPosted: Tue Jun 02, 2015 10:57 am     Reply with quote

I appreciate the answers. You have confirmed what I suspected, that there is no error checking in the system calls, even fewer than in a Unix-like system. There ARE error checks in some standard calls, but they may be turned off in the CCS compiler.

RF_Developer, you asked what I expected to happen. What happened is exactly what I expected. I was hoping different, but that is why I asked.

I am well aware that C will happily let you shoot your foot completely off, will let you increment a character and assign that value to a pointer to float that is really holding a series of integers one of which is an index to a structure array.

I was just verifying with those with more experience that no error checking of any type is done for these two system calls. Or even most system calls, including printf. Standard printf returns an integer. Under CCS, it returns "undefined".

Thanks for your help.
_________________
D. Scruggs
RF_Developer



Joined: 07 Feb 2011
Posts: 839

View user's profile Send private message

PostPosted: Wed Jun 03, 2015 2:17 am     Reply with quote

What you got was what you expected, therefore I'm struggling to see where the problem is...

"System calls" is not the right way to think of compiler provided functions. In CCS C there are no "system calls" as there is NO "system", or more specifically an operating system. There are built-in functions, and calls to standard (and in some cases not so standard) C library functions. Printf is not a system call, its a C standard library call, and the way such calls are implemented varies from compiler to compiler. I've only once seen printf implemented in full as C, and for the record that was a C compiler for a TI DSP processor.

As far as I'm aware, printf doesn't have any input parameter checking defined, and I don't know of any implementation that does. If you give it garbage, it'll happily try its best to process it, but don't be surprised if it give unexpected results. Things like unterminated strings will happily cause most printfs to "print" most of the contents of memory, at least until it encounters a null.

Printf is defined to return an integer with the number of characters printed. With CCS C that gives rise to two potential issues as I see it, one the overhead of counting characters is significant, and considering the very, very few times its ever used - I have never used it, nor do I know anyone who has, or come to that even remembers that printf actually returns anything - its possible that its was regarded as an extravagance. The second is that ints in CCS C are unsigned eight bits. While 255 characters is a fair bit for many printf operations, it could fairly easily not be enough for all reasonable practical purposes, so the type would have to be different. As such its not at all unreasonable for printf in CCS C to not return the value it does in other implementations.

In any case, undefined is not a type. Printf probably still returns an int, or something that can be interpreted as an int (even void return values, which most people think of as "nothing is returned" can be interpreted as int, or as any other type). Its just that the value is undefined. Its probably going to be 0, but I wouldn't bet on it.

Your original statement, "...despite having obvious errors, compiles and runs like nothing is wrong" Is really where I'm at here, the point is that there may be obvious symantic errors, such as you trying to access beyond available memory, but compilers, even today, can only deal with syntactic errors. Your code was syntactically correct, therefore it compiled. On some processors it would even run "correctly", such as where there was sufficient memory, so the symantic problems were related to hardware context, at least in this instance. It is possible to deal with such errors if the accesses exclusively use constants for the address, and for the compiler to pick up out addressing errors. However, as soon as they are allowed to be variable, then its not possible for the compiler to know what will happen. The only way to solve that would indeed be runtime address checking with all the overheads that entails.

PICs are not large processors running operating systems with complex memory management and operating systems managing system resources and providing task protection. PICs are NOT a Unix-like system. CCS C provided none of the support that a UNIX-like system is expected to provide. Sure there may be some, limited "error checks" in some, a few, built-in and library functions. To be honest, I do not know of any way of turning them on or off.

Its best to assume that CCS C will not, in any way, hold your hand or protect you from anything. We all need to ensure that we program defensively and debug carefully to make sure our code really does what we want it to, because CCS C simply ain't going to tell us otherwise....
Ttelmah



Joined: 11 Mar 2010
Posts: 19552

View user's profile Send private message

PostPosted: Wed Jun 03, 2015 3:22 am     Reply with quote

I think RF_Developer has put his finger on the problem.

Most people writing in 'conventional' C, are coding applications to run under an OS. As such they code inside the OS, with the C functions linking to OS based calls with their own error protection.

In fact though Microsoft C, and the C inside Unix, can be used to write code for outside the OS (you can code a PC BIOS in C). However as soon as you write at this level, the language behaves very much as CCS C does. I wrote sections of a BIOS for an embedded PC in Microsoft C, and the only memory protection functions given, were those I had already written and added to the language. Even putc and getc, offered no functionality, till these were coded. There was no variable list handed to the main, no file I/O etc.. The 'expectations' that come from writing inside an OS, are _not_ inherent in embedded C.
bcfd36



Joined: 14 Apr 2015
Posts: 28
Location: Boulder Creek, CA

View user's profile Send private message

PostPosted: Wed Jun 03, 2015 10:24 am     Reply with quote

Quote:
There was no variable list handed to the main, no file I/O etc.. The 'expectations' that come from writing inside an OS, are _not_ inherent in embedded C.


So I am finding out. My expectations are rapidly changing. It is going to be fun.
_________________
D. Scruggs
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