Blocking External Table Reads in PIC18 with the MPLAB XC8 Compiler

Last modified by Microchip on 2023/11/10 11:05

EBTRx config pragma

In addition to the code protection configuration bits, which restrict device programmers from reading back the content of program memory, some PIC18 devices implement external block table read configuration bits that offer an additional level of data protection at runtime. Enabling this feature prevents data within program memory blocks from being read by table-read instructions that are located in other blocks.

To apply this protection, use the EBTRx config pragma setting, where x is a number that represents the program memory block or is the letter B, represents the boot block. There is also the BBSIZ0 configuration bit, which controls the size of the boot block and which may also affect the range of block 0. For example to protect block 0 and use a 2k byte boot block use:

#pragma config EBTR0 = ON
#pragma config BBSIZ0 = BB2K

Your device datasheet will indicate how many blocks your device has and the address ranges these blocks cover. For example, enabling the EBTR0 setting on an 18Fx6K22 device will protect the program memory block from 0x1000 to 0x3FFF if the BBSIZ0 bits are set to BB2K. Once protected, any table read instruction inside block 0 can read bytes located within this same memory block, but those same bytes will not be accessible to any table read instruction located in another block.

One important consideration when using this feature is that the MPLAB® XC8 compiler stores const data and strings in program memory, and that this data has to be accessed using table read instructions. The compiler will not be aware that blocks have been made read-protected by the configuration bit settings, and if the sections holding these data end up in a protected block, your code will almost certainly fail.

If you do want to use this feature, you must ensure that the protected blocks are used for the special data that you want to protect and the routines that read them and that the protected blocks are not used for any other data. The easiest way to accomplish this is to reserve the memory taken up by the entire block or blocks you wish to protect so that no other code or data will be placed in this region by the linker. Then you can explicitly place your protected data and read routines somewhere in this reserved area of memory.

Here’s how you might achieve this: place your protected data into a section with a name of your choosing, for example:

const unsigned char __section("ebtr0_data") protectedData[] = {
   0, 2, 4, 6, 8, 10, 12, 14
} ;

Do the same for any routine that has to read this data or that needs to be located in the protected block.

unsigned char __section("ebtr0_code")
readProtectedData(unsigned char offset)
   unsigned short long tblptrSaved;
   unsigned char       data;

    tblptrSaved = TBLPTR;
    data = protectedData[offset];
    TBLPTR = tblptrSaved;

   return data;

Note that the routine above saves the table pointer registers (TBLPTR is comprised of 3-byte registers) before reading the data and restores them before returning. This is because the compiler might have preloaded some of the table pointer registers in the runtime startup code and assumes that they will not change during the execution of the program. This assumption improves the performance of the program, but for situations when accessing const data that are located in special locations, as is the case when accessing data using the __section() specifier, the table pointer registers must be preserved to ensure that this assumption remains valid.

To reserve memory, use the —ROM compiler option, or the ROM ranges field in the project properties when using the MPLAB® X IDE. Use the following ROM ranges argument to reserve block 0 on an 18F87K22 device with BBSIZ0 set to BB2K, so that the address range 0x1000 to 0x3FFF is removed from the linker’s classes and no code or data will be allocated to that region.

XC8 Linker > Memory model > ROM ranges: -1000-3FFF

Next, define a linker class to represent the protected block or blocks, then place the code and data sections you have created into that class. This is performed using a process similar to that described in this article, so you might use the following options.

XC8 Linker > Additional options > Extra Linker Options:

-L-APROT_BLK0=1000-3fff  -L-Pebtr0_data=PROT_BLK0  -L-Pebtr0_code=PROT_BLK0

Alternatively, you could also link the sections without the use of a class, as in the following example which explicitly places the data at the beginning of the block and places the code immediately following.

-L-Pebtr0_data=01000h -L-Pebtr0_code=ebtr0_data

Check the map file (found in the <project>/dist/default/production folder for production builds) after you build to ensure that the data and code are linked where you intended and that other areas of the protected block have not been populated.