Skip to content

Instantly share code, notes, and snippets.

@Tropix126
Last active February 14, 2024 00:26
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Tropix126/bf90b92379c78060ec9039f9013d80db to your computer and use it in GitHub Desktop.
Save Tropix126/bf90b92379c78060ec9039f9013d80db to your computer and use it in GitHub Desktop.

Hot/Cold Linking

Hot/cold linking is a strategy used by PROS for splitting a program into two binary packages to reduce upload size.

  • The cold image (cold.bin) contains libraries (libpros, liblvgl, libstdc++, etc...). The cold image is uploaded once, or whenever it is modified.
  • The hot image (hot.bin) contains user functions and links against the cold image. The hot image is uploaded every time the program is built, and thus must be optimized for size.

Memory

Each image has its own address offset in memory:

hot_table and .hot_init

The hot_table is a C struct stored in the cold image. It stores function pointers to user functions in the hot image. It looks roughly like this:

struct hot_table {
	// Symbols containing compiler metadata.
	char const* compile_timestamp;
	char const* compile_directory;

	// Symbols used by GCC for stack unwinding.
	void* __exidx_start;
	void* __exidx_end;

	// Here's the important part:
	struct {
		// These just call their _cpp counterparts.
		void (*autonomous)();
		void (*initialize)();
		void (*opcontrol)();
		void (*disabled)();
		void (*competition_initialize)();

		// These are called by the system daemon after everything is initialized if a hot_table is available.
		// https://github.com/purduesigbots/pros/blob/1e7513d4f110d2eac625b6300dbbb8c086ab6c0c/src/system/user_functions.c#L38
		void (*cpp_autonomous)();
		void (*cpp_initialize)();
		void (*cpp_opcontrol)();
		void (*cpp_disabled)();
		void (*cpp_competition_initialize)();
	} functions;
};

You can think of hot_table as the "glue" between the hot and cold packages. Since the cold package contains kernel code and the kernel needs to call user functions as an entrypoint to actual user code, it needs hot_table to access and call user functions from from the hot package.

Initializing the Hot Table

Great. We have a struct that's supposed to store some function pointers to functions stored in the hot image's address space. How do we access these functions stored in the hot image to fill the struct on the cold image?

In the .hot_init section of the hot package is a function called install_hot_table. This function is what actually creates an instance of the hot_table struct on program startup. The function signature looks like this:

__attribute__((section(".hot_init"))) void install_hot_table(struct hot_table* const tbl);

Removing all the macro garbage reveals a system roughly like this:

// Symbols added by the compiler
extern char const* _PROS_COMPILE_TIMESTAMP;
extern char const* _PROS_COMPILE_DIRECTORY;
extern const int   _PROS_COMPILE_TIMESTAMP_INT;

extern unsigned __exidx_start;
extern unsigned __exidx_end;

// Forward declarations to the user functions in the acutal hot package
extern void autonomous();
extern void initialize();
extern void opcontrol();
extern void disabled();
extern void competition_initialize();
extern void cpp_autonomous();
extern void cpp_initialize();
extern void cpp_opcontrol();
extern void cpp_disabled();
extern void cpp_competition_initialize();
...

// Let's make a struct!
__attribute__((section(".hot_init"))) void install_hot_table(struct hot_table* const tbl) {
	// Boring compiler stuff
	tbl->compile_timestamp = _PROS_COMPILE_TIMESTAMP;
	tbl->compile_directory = _PROS_COMPILE_DIRECTORY;
	tbl->__exidx_start = &__exidx_start;
	tbl->__exidx_end = &__exidx_end;

	// There's our functions...
	tbl->functions.autonomous = autonomous;
	tbl->functions.initialize = initialize;
	tbl->functions.opcontrol = opcontrol;
	tbl->functions.disabled = disabled;
	tbl->functions.competition_initialize = competition_initialize;
	tbl->functions.cpp_autonomous = cpp_autonomous;
	tbl->functions.cpp_initialize = cpp_initialize;
	tbl->functions.cpp_opcontrol = cpp_opcontrol;
	tbl->functions.cpp_disabled = cpp_disabled;
	tbl->functions.cpp_competition_initialize = cpp_competition_initialize;
}

However, this function is only invoked by invoke_install_hot_table under the following condition:

void invoke_install_hot_table() {
	// Check for.. something?
	if (vexSystemLinkAddrGet() == (uint32_t)0x03800000 && MAGIC_ADDR[0] == MAGIC0 && MAGIC_ADDR[1] == MAGIC1) {
		install_hot_table(HOT_TABLE);
	} else {
		// Can't install the hot table, zero it out instead.
		memset(HOT_TABLE, 0, sizeof(*HOT_TABLE));
	}
}

https://github.com/purduesigbots/pros/blob/1e7513d4f110d2eac625b6300dbbb8c086ab6c0c/src/system/hot.c#L79C1-L88C2

So, the requirements for initializing the hot table are:

  • vexSystemLinkAddrGet() must be 0x03800000 (whatever this entails)
  • MAGIC_ADDR[0] must be MAGIC0 and MAGIC_ADDR[1] must be MAGIC1

MAGIC0 and MAGIC1

Looking the top of the file reveals these two definitions for the magic numbers we saw previously:

#define MAGIC0 0x52616368
#define MAGIC1 0x8CEF7310

These two numbers are essentially "sanity checks" to ensure that the hot image exists in the address space of the current user program. They're set in the hot image's .hot_magic section (located at 0x078) like this:

__attribute__((section(".hot_magic"))) uint32_t MAGIC[] = {MAGIC0, MAGIC1};

So PROS creates this value pair containing the magic numbers in hot memory. In the cold image, it then checks that these numbers exist at a pointer to the address that MAGIC is expected to be at.

uint32_t const volatile* const MAGIC_ADDR = MAGIC;

Assuming that the hot image exists, MAGIC_ADDR[0] will point to MAGIC0 and MAGIC_ADDR[1] will point to MAGIC1

What is vexSystemLinkAddrGet()?

This SDK call is pretty weird. PROS checks that its value is 0x038, which is the start of a typical user program and cold memory. The leading theory is that is returns the starting address of whatever binary it's called from. It's essentially a check that invoke_install_hot_table is being called from the cold package and nowhere else.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment