I am using STM32F4(ARM-Cortex M4). I want to share a function between main application and bootloader without redefine that function, for example imagine that i wrote a print function in main application and now i want to use this in bootloader too. First way is copy that function into bootloader and then use it, but i don't want this. I want to define this function 1 time in memory and then when i needed to it, invoke it.
edit : I have an idea and i wrote this code for jump between two program(app and bootloader). I define this function in my bootloader:
void Shared_Func(void)
{
while(1)
{
HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
HAL_Delay(700);
}
}
typedef void (*ptr_shared_func_t)(void);
ptr_shared_func_t ptr_shared_func;
and in main:
ptr_shared_func = Shared_Func;
ptr_shared_func();
if i run this code, it works and there is no problem. Then on debug mode i find address of Shared_Func and instead of use name of function i write:
unsigned int *add_shared_fun = (unsigned int *)0x08001055;// correct address is 0x08001054 ->for thumb2 change to 0x08001055
ptr_shared_func = (void(*)(void))(add_shared_fun);//Shared_Func;
in this case code not work! My Final goal to use this solution was that after verify this type of jump to function, i will define a pointer in special address and then save address of Shared_Func() into it, then in app program use this address that stored in ponter, jump to Shared_Func()!
Now my question is whats the best solution and whats the problem in my code?
-
\$\begingroup\$ The same way you share any function. With libraries and header files. \$\endgroup\$DKNguyen– DKNguyen2021年07月17日 06:44:45 +00:00Commented Jul 17, 2021 at 6:44
-
6\$\begingroup\$ A typical approach would be to define an interface to query the structure that holds pointers to the functions. But normally it's a bootloader that exposes functions to be shared with (possibly multiple) application that was launched by that bootloader, since bootloader does not change as often as the applications. \$\endgroup\$Vlad– Vlad2021年07月17日 07:54:50 +00:00Commented Jul 17, 2021 at 7:54
-
2\$\begingroup\$ @HwSwDesigner A code that provides functions also hosts the structure with pointers to those function. In simplest case a pointer to that structure is stored at a predefined location, and that location is made known to apps by defining it in some .h file, along with structure definition and functions prototypes. \$\endgroup\$Vlad– Vlad2021年07月17日 09:37:09 +00:00Commented Jul 17, 2021 at 9:37
-
4\$\begingroup\$ It's probably not a good idea to call main program functions from the bootloader. A error in the main program could render the bootloader inoperable. \$\endgroup\$Unimportant– Unimportant2021年07月17日 10:24:00 +00:00Commented Jul 17, 2021 at 10:24
-
2\$\begingroup\$ @HwSwDesigner no, including a header twice does by no means use any memory in the end binary; that's not how any of that works! The header should only tell the compiler what the functions used in your source code look like. \$\endgroup\$Marcus Müller– Marcus Müller2021年07月17日 12:57:00 +00:00Commented Jul 17, 2021 at 12:57
3 Answers 3
This is happening constantly in your computer. .dll and .so files are basically code that is not compiled into your program, but contains functions that your program calls. Whichever direction you want to go does not really matter, bootloader calling app, app calling bootloader, you can decide about the sanity and dangers of that, but it is very much possible either way.
What you do not see when you
#include <stdio.h>
int main ( void ) { printf("Hello World!\n"); return 0; }
gcc hello.c -o hello
Is, well tons of stuff, but in this context. The C library most likely being linked does not contain the printf function nor the thousands of other functions that printf relies on. What it does contain is something that is built into the design and implementation of the library that goes and finds the C library and patches up the function pointers such that this works.
You are apparently on a baremetal system so you have to do all of this yourself, just like the folks that create C libraries do, except they know they are sitting on top of a known operating system and implement their solution based on that.
There are linker games you can play such that you build both of the binaries at the same time and the linker does all the work, basically you are building one binary but breaking it into two parts, and it will ONLY work if you have the binaries from the same link combined, mix and match, and you get massive failure. A feature of this approach is for example you can build multiple binaries, you could have one bootloader and several applications that could be loaded by that bootloader and have the applications call functions in the bootloader seemlessly, all the work is in the linker script and the extraction of the several binaries, no code needs to be written in the bootloader or applications themselves. This is very specialized solution and generally going to fail.
The right answer is you use function pointers where one binary is calling the other. How and when you do this is determined by your design/solution.
You could have
int hello ( int );
int fun ( int x )
{
return(hello(x+1));
}
so that you write that code like normal but
int *_hello ( int x );
int hello ( int x )
{
if(_hello==NULL)
{
_hello=((volatile int *)0x00001100));
}
return(_hello(x));
}
Or somewhere you have a
void init_remotes ( void )
{
_hello=point at fixed/known address for hello;
_there=point at fixed/known address for there;
_world=point at fixed/known address for world;
}
I have no use for function pointers so my syntax is broken, but that should not matter you may already know it or spend the minute googling it.
Structs are a (lazy) solution too, but understand you should never use structs across compile domains, that leads to pain and failure and regular maintenance.
Structs that do not cross are often used for things like this, not always a remote/shared library but in general a way to abstract something. Say I have a storage device and I want to have a read and write file function, I could implement that with structs of functions storage->read(something). storage->write(something). And depending on whether it is flash or a usb stick or a hard drive, the read/write functions can point to flash_read() usb_read(), hd_read(). With at some point before using any of the functions the items in the storage structure are pointed to the media specific function if usb then storage->read = usb_read kind of a thing with the right syntax.
Now for what you are actually asking. YOU have to do the work here, YOU have to design the connection, whichever direction you are going. If for example the bootloader contains the common/shared functions, two simple approaches.
One is that the bootloader is often located in a known address space, lets say 0x00000000. So you could design it so that the function addresses are at a known offset in the bootloader binary. You can go so far as to put markers and versions
[0x00001000] 0x12345678 some marker to help prevent crashing in case the pointers are not here
[0x00001004] 0x00000104 a version number so that your application may know what functions are there or what version of them
[0x00001008] 0x0000200C address to the function hello();
[0x0000100C] 0x00003120 address to the function world();
And the application at some point after it launches and before it calls these remote/shared functions, needs to point the application's function pointer to the remote/shared function pointer. Then it just works.
It is not difficult on the bootloader side in this case for the tools to do the work of filling in the addresses for you.
You could in the application for example have
.globl hello
hello:
ldr r3,=0x00001008
ldr r3,[r3]
bx r3
This is a crude solution (not the asm, but hardcoding addresses) that will work. An easier solution is to pass something to the application by the bootloader (this is basically how linux gets its parameters a register points to a linked list or a device tree of info that linux parses through). So, pick a register, r2 may be defined by you since you own the design of all of this, the address (within the bootloader of course) to the function pointer table in some designed by you form.
And as ugly and undesirable as it is, you are often going to find something like this, excuse the bugs in the syntax.
typedef struct
{
unsigned int marker;
unsigned int version;
int *_hello (); //LOL I cant remember how to do this, it is probably another typedef
int *_world;
} my_library;
my_library ml =
{
0x12345678,
0x0104,
hello,
world
};
int hello ( int x )
{
return(5+x);
}
int world ( int x )
{
return(7*x);
}
//loader calling app after loading into ram
branch_to_application(LOAD_ADDRESS,something,ml);
.globl branch_to_application
branch_to_application:
bx r0
then you use the matching struct in the application connect r2 to the struct in the application in some way (pass r2 into your C entry point, do it in the bootstrap, etc)
and then
something=ml.hello(3);
Another similar solution is to pass the struct to the bootloader, for example an address passed in a register to the application may be the function that you pass your struct to, then the bootloader fills that struct in. Or you could do it in an ascii fashion, that address could be a function in the bootloader and you call it once for each function you want the address to...whatever.
Note I am sure you can figure out how to do it without structs and reduce the risk if not eliminate that risk, but get the same results.
I will let you sort the syntax out as it is not relevant to this answer. It magically happens on an operating system with C or other languages when calling those libraries because the tools, library, operating system, are all designed to solve this problem of shared libraries (one binary calling functions in another binary). For bare metal you have to do the work yourself, the tools do not know your "operating system" so you in some way have to make them know. Which as with a C library, someone sits down and designs a solution to connect one binary to the functions in another. And it ultimately comes down to at some point before you use the remote/shared function you have to point at it, and/or in the stub version of the function in your code it goes and finds the address to the function then branches to it.
Now to your question, explain how the bootloader is going to run something in an application? Normally you would put the print in the bootloader and the application once run calls it. If you want the bootloader to call code in the application that will work but is just very strange. That means the bootloader cannot print until it loads the application, does the work to find the pointer to the function then call it. The other way around makes for a larger bootloader yes but smaller applications and less flash space consumed as you only need one print function in one place (the bootloader) and only need stubs in the applications or a structure or whatever.
In general this has pros and cons, the cons are that you might have a product that has evolved the bootloader over time and customers may have a version of the product with a 5 year old bootloader, and you have a new application for that product and it needs to somehow work with the 5 year old bootloader, the 4 year old bootloader, the 3.5 year old bootloader, the 3 year old...you get the picture. Is saving flash and ram that important, how much are you saving. After two or three years you realized you should have had more shared functions now all the new binaries have to carry the new functions burning ram and flash. Do you field update the bootloader, do you make that a customer choice (and get stuck back with having to support all prior released versions for every new application). How do you insure you do not brick the product when you do the field upgrade...Maybe if you write cleaner code and optimize better you can save hundreds to thousands of bytes, and maybe that is enough across all the binaries that they dont have to share libraries. Maybe the shared library is itself a loadable application, the bootloader has built in only what it needs but you load the shared library off the flash to somewhere, then you load the application to somewhere and give the application a pointer to the shared library on launch. And when you field update/install the new set of applications you update the shared library.
All of this is only really relevant IMO if you have multiple possible applications per bootloader. If you only have one bootloader and one application and the bootloaders is there so that the user can field update the application, then it only needs enough to do that and the application carries everything it needs, yes you might have some wasted overlap.
If this is a case of primary and redundant bootloader along with primary and redundant application so that you can do field updates of either with less risk of bricking the product. You are already burning a lot of flash, how much are you really saving?
bottom line, because this is (assumed to be) bare metal on an mcu you have to design the mechanism yourself. Discover the function addresses in the other binary and point at them in the currently executing binary. You either do that in a stub/wrapper for each function (very wasteful) or you create an ideally one time initialization, before you use any remote function, that has a table of addresses on the remote side that you populate the table/list of function pointers on the near side. Then you just use these functions. Your level of paranoia/fear/experience will determine how simple or complicated you want to make this. Obviously start simple with a single function as an experiment, complicate from there.
-
1\$\begingroup\$ Another point to mention is that one should permanently allocate separate regions of static storage for the bootloader and main program (or else design the boot loader so none of the shared functions require any static storage). \$\endgroup\$supercat– supercat2021年07月17日 19:34:11 +00:00Commented Jul 17, 2021 at 19:34
-
\$\begingroup\$ Thanks for Answer my question friends. \$\endgroup\$HwSwDesigner– HwSwDesigner2021年07月17日 21:57:47 +00:00Commented Jul 17, 2021 at 21:57
I had to do this once in order to save flash/ROM space. There was some crc and hardware driver functions that could be shared between the bootloader and application. The bare-bones answer is: create a function pointer, assign the function pointer to the appropriate address of the function you wish to call, call the function via the function pointer. You can get creative on how you pass the function address information to each program. The quick and dirty way: Hard code the addresses by looking at the function address from a map/list file generated by your compiler. A more sophisticated way: Create a "function address look-up table" that's always placed at a specific address in memory (need to use linker file directives to make this happen). In this way, you can update the bootloader or application (meaning function addresses may change), and still have both remain sync'd. You'll probably need to access some compiler-specific syntax for generating the lookup table of function addresses.
-
\$\begingroup\$ can you use an example code? \$\endgroup\$HwSwDesigner– HwSwDesigner2021年07月18日 05:24:58 +00:00Commented Jul 18, 2021 at 5:24
I write this code to solve the problem and I tested it. IDE: keil uvision v5.22
In Bootloader Program:
//Shared_Func is a sample function for sharing between two app
void Shared_Func(void)
{
while(1)
{
//do somethings
}
}
typedef void (*ptr_shared_func_t)(void);//define new function pointer type
const ptr_shared_func_t ptr_const_shared_fun __attribute__((at(USER_APP_BASE_ADDRESS -4))) = Shared_Func;
//save address of Shared_Func in special address. To avoid non-interference between application code and address of shared function ,i defined that at (USER_APP_BASE_ADDRESS -4).
In Application Program:
typedef void (*ptr_shared_fun_t)(void);
ptr_shared_fun_t ptr_shared_fun;
in the main function or anywhere we want to call Shared_Func:
unsigned int *add_shared_fun = (unsigned int *)(USER_APP_BASE_ADDRESS-4);
ptr_shared_fun = (void(*)(void))(*add_shared_fun);
ptr_shared_fun();//call SharedFunc with function pointer