DocumentationDevice AppExplanationMemory constraintsPosition-independant code

Position-Independent Code

PIC and Model Implications

PIC stands for Position-Independent Code. The Ledger OS toolchain produces PIC to allow the code Link address to be different from the code Execution address. For example, the main function is linked in the generated application at address 0xC0D00000. However, the slot used when loaded into the Secure Element could be 0x10E40400. Therefore, if the code makes a reference to 0xC0D00000, even with an offset, it would be denied access as the application is locked by the Memory Protection Unit (not to mention, this is not the correct address of the main function at runtime).

The PIC assembly generator makes sure every dereference is relative to the Program Counter, and never to an arbitrary address resolved during the link stage. This behaviour is supported by clang versions 4.0.0 and later.

Traditionally, PIC code implies the BSS segment (RAM variables) is at a constant offset of the code. For example, if code is at 0xC0D00000, then global vars may be at 0xC2D00000, so if loaded at 0x10E00000 then global vars would be at 0x12E00000. However, Ledger OS uses a fixed address for global vars. The global variables start address and length are defined in the link script. Only the code is meant to be placed at different addresses (in flash memory, rather than RAM).

The model we choose has limitations, which are related to the way const data and code is referenced in other const data. Here is a simple example:

const char array1[] = {1, 2, 3, 4};
const char array2[] = {1, 2, 3, 4};
const char *array_2d[] = {array1, array2};
 
void main() {
    int sum, i, j;
    sum = 0;
    for (i = 0; i < 2; i++) {
        for (j = 0; j < 4; j++) {
            sum += array_2d[i][j]; // Segmentation Fault!
        }
    }
}

In the example above, when dereferencing array_2d, the compiler uses a link-time address (in the 0xC0D00000 space, following the previous examples). This is not where the program is loaded in memory at runtime. Therefore, when the dereference is executed, it causes a segmentation fault that effectively stalls the SE. Luckily, the solution is pretty simple, thanks to a small piece of assembly provided with the SDKs which is invoked with the PIC(...) macro. PIC(...) uses the current load address to adjust the link-time address in order to acquire the correct runtime address of const data and code. The above examples can be corrected by modifying the line where array_2d is dereferenced to do the following:

sum += ((const char*) PIC(array_2d[i]))[j];

The same mechanism must be applied when storing function pointers in const data. The PIC call cast is just different. Additionally, if a non-link-time address is passed to PIC(...), it will be preserved. This is possible due to the wisely chosen link-time address which is beyond both real RAM and loadable addresses. For example, PIC(...) is used during a call to io_seproxyhal_display_default(...), all display elements can hold a reference to a string to be displayed with the element, and the string could be in RAM or code, and therefore PIC(...) is applied to acquire the correct runtime address of the string, even if it’s in RAM.

Ledger
Copyright © Ledger SAS. All rights reserved. Ledger, Ledger Nano S, Ledger Vault, Ledger OS are registered trademarks of Ledger SAS