Bare-metal ARM, gcc, position-independent code -- possible? - Page 1
Forum section: electronics / general

author: eutectique
author_rank: Frequent Contributor
timestamp: March 01, 2022, 06:11:14 pm
content: Hi,I would very much appreciate an advice regarding building position-independent applications with gcc.BackgroundA device has a bootloader and two slots for applications, say A and B. The bootloader starts, chooses which app to run, and jumps into it.An application should have position-independent code, it is not known in advance which slot it will occupy -- the firmware update process can place it in either A or B. Data should have absolute addresses. So, the whole thing should be read-only position-independent, ROPI.Slot size is 400 kB. (For the record, current app size is about 230 kB)Compiler is arm-none-eabi-gcc v9.3.1, the app runs in FreeRTOS. The project contains about 100 source files, some of them are vendor's HAL and BSP, but the majority are ours.The buildCFLAGS += -fPIC -mno-pic-data-is-text-relative -msingle-pic-base -mpic-register=r9LFLAGS += -fPICrebuild Newlib from source withCFLAGSfix FreeRTOS to not trash R9 registerbuild the app for slot Ain start-up code:copy GOT from flash to RAM and relocate code addresses (don't touch data addresses)setup R9 to point to GOTcopy vector table from flash to RAM, relocate addresses, setup VTORcopy .data section to RAM, clean .bss sectioncallmain()Place the app into slot A and enjoy.The problemThe app fails when placed into slot B. The source of the problem is data structures initialised with function addresses like this:QuotehalBoardis initialised as follows:QuoteSo,absoluteaddresses are placed into .data section (first three words) and then used to initialise the structure.The pages suggested by google include:https://community.arm.com/support-forums/f/compilers-and-libraries-forum/44805/cannot-build-position-independent-code-that-works-- Yes, but the resulting shared object is bloated 3 times, from original 230kB to 650kB. It will not fit the slot.https://mcuoneclipse.com/2021/06/05/position-independent-code-with-gcc-for-arm-cortex-m-- Yes, but the manual creation of PLT or other Linuxy stuff is out of question.And lots of others, without a clear answer.The questionHas anyone successfully built a ROPI application:with gcc and relevant command-line options (which?),without manual veneers or other crutches,with a reasonable size?How?gcc is not that strict requirement. If it's a wrong tool, would Clang do the job?Thank you for hints, pointers, etc.

author: SiliconWizard
author_rank: Super Contributor
timestamp: March 01, 2022, 06:36:00 pm
content: You may want to refer to this thread:https://www.eevblog.com/forum/microcontrollers/how-to-create-elf-file-which-contains-the-normal-prog-plus-a-relocatable-block/

author: ataradov
author_rank: Super Contributor
timestamp: March 01, 2022, 07:17:41 pm
content: In the past I tried to create reasonable PIC code using GCC, and I could not make it work. You very quickly run into needing GOT and all associated overhead.I was looking at it for OTA firmware updates where the image would be located in one part of the flash or the other. And a more reasonable solution I found is to build two images linked at different addresses. OTA image includes both, and the device selects which one to program based on the side that is currently running (pick the opposite of the running). The other half is discarded or is not downloaded in a first place.

author: eutectique
author_rank: Frequent Contributor
timestamp: March 01, 2022, 09:32:58 pm
content: Quote from: SiliconWizardIt's a bit away from what I am after, but thanks anyway!(4 pages of discussion of the problem which can be solved with one line of the linker script?Ok, two lines.And the second line to the source code.)

author: eutectique
author_rank: Frequent Contributor
timestamp: March 01, 2022, 09:47:05 pm
content: Quote from: ataradovYes, that's exactly why I want PIC.Looks like two images would be the viable solution. Thank you!

author: abyrvalg
author_rank: Frequent Contributor
timestamp: March 01, 2022, 10:06:51 pm
content: You can do it a different way:- save OTA to slot B (always)- reboot to bootloader- validate slot B image- copy it to slot A (if valid)- invalidate slot B- jump to slot A

author: ataradov
author_rank: Super Contributor
timestamp: March 01, 2022, 10:15:15 pm
content: Having two images allows rollback and some applications may need this. It is also faster to switch images if you don't have to switch them, just boot the right one.But on the other hand managing two images complicates the release a bit.

author: eutectique
author_rank: Frequent Contributor
timestamp: March 01, 2022, 10:26:48 pm
content: Quote from: abyrvalg on March 01, 2022, 10:06:51 pmYep, that's how we do it now.

author: bson
author_rank: Unknown
timestamp: March 01, 2022, 10:37:11 pm
content: Looks like a compiler or possibly linker bug. Maybe try gcc 10 for good measure?Maybe it's mistakenly assuming a function pointer (which refers to a pure section) stored in an impure section is impure. Only pure data and code is relative to the PIC base register; the SRAM is in the same location regardless of which bank you use. If so, perhaps you can declare the vector table 'const' to make it pure? This will of course add another address calculation, but it might work at least.Also, what code is generated for the calls?

author: abyrvalg
author_rank: Frequent Contributor
timestamp: March 03, 2022, 12:21:26 pm
content: Another option is to wrap all code ptrs with some adjustment func/macro that will add a precalculated displacement.But I'm sure I've seen many ARM binaries full of constructs like this:Code:LDR R0, =PcRelativeOffsetADD R0, PCused instead of hardcoded addresses, the question is which compiler produces them.

author: peter-h
author_rank: Super Contributor
timestamp: March 03, 2022, 09:26:09 pm
content: QuoteYou must be extremely clever, because (that was "my" thread) I never found an elegant solution, and AFAICT nobody had ever used the GCC relocatable code option.

author: bson
author_rank: Unknown
timestamp: March 04, 2022, 04:01:42 pm
content: Thinking about this some more, the vector table as declared can't be correctly made PIC. The pointers you initialize it with are only that -initializers. When emitting code for the call, the compiler has no idea if the pointer actually found is a PIC offset or absolute. Either is a perfectly reasonable use case; the latter for copying all sorts of performance-critical code to an SRAM bank and storing function pointers somewhere in a table to access it, in which case adding the base register to the addresses would be completely wrong. So the first step absolutely should be to make the vector table itself const, and then make sure it's in flash (don't put anything immutable in SRAM unless it's performance critical).I think the bug here is the compiler or linker doesn't issue a warning, that an impure function pointer initialized with a pointer to a pure section is inherently incompatible with PIC.

author: eutectique
author_rank: Frequent Contributor
timestamp: March 04, 2022, 07:09:53 pm
content: Solution:Initialise function pointers at run time. Instead ofCode:halBoard_t halBoard = {.bspGetMcuUid = bspR1BoardGetMcuUid,.bspGetBoardType = bspR1BoardGetBoardType,.bspGetSerialNumber = bspR1BoardGetSerialNumber,};do the following:Code:halBoard_t halBoard = {0};halBoard.bspGetMcuUid = bspR1BoardGetMcuUid;halBoard.bspGetBoardType = bspR1BoardGetBoardType;halBoard.bspGetSerialNumber = bspR1BoardGetSerialNumber;In the first case the compiler generates code to load addresses from .data section. In the second case it generates code to load addresses from GOT, exactly what I want.Tested with gcc v{9,10}, and Clang v13. The results are the same.

author: eutectique
author_rank: Frequent Contributor
timestamp: March 04, 2022, 07:15:06 pm
content: Quote from: bsonTrue. It containsabsoluteaddresses. That's why I copy it to RAM and adjust.

author: eutectique
author_rank: Frequent Contributor
timestamp: March 04, 2022, 07:43:52 pm
content: Quote from: peter-hWon't argue with this, thank you. I've added my two pence to that discussion, hope it's helpful.

author: eutectique
author_rank: Frequent Contributor
timestamp: March 04, 2022, 07:47:05 pm
content: Quote from: abyrvalgYes, also a possibility, but I wanted to avoid it at all costs.