On 1/16/2024 5:19 AM, pozz wrote:
> In one project I have many quasi-fixed strings that I'd like to keep in non
> volatile memory (Flash) to avoid losing precious RAM space.
>
> static const char s1[] = "/my/very/long/string/of/01020304";
> static const char s2[] = "/another/string/01020304";
> ...
>
> Substring "01020304" is a serial number that changes during production with
> specific device. It has the same length in bytes (it's a simple hex
> representation of a 32-bits integer).
>
> Of course it's too difficult and slow to rebuild the firmware during production
> passing to the compiler the real serial number. I think a better solution is to
> patch the .hex file generated by the compiler.
>
> I'm wondering how to detect the exact positions (addresses) of serial numbers
> to fix.
Don't "detect" (i.e., "find"); rather, *place* it/them in a specific location
that your code already knows about. How else would you force vector tables
to reside at specific locations, jump tables, etc.?
You will also code this "hole" into any checksum routine that your
code executes at POST and cover it with a check of its own (that you
will have to ensure is satisfied by whatever tool you use to "patch"
the binary image).
> The build system is gcc, so I could search for s1 in the elf file. Do you know
> of a tool that returns the address of a symbol in the elf or map file?
>
> Could you suggest a better approach?
When faced with *small* memory regions (e.g., 100 bytes) of a particular
resource (e.g., NVRAM), I prefer a tagged format that allows the available
space to be dynamically traded among uses -- much like cramming a variable
number of parameters in a BOOTP packet.
The downside is that you have to parse the region to extract any specific
parameter. But, it eliminates the need to define static structures
that might change from instance to instance:
STRING1, "/my/very/long/string/of/01020304",
STRING8, "Some Guy's Really long name or address",
STRING9, "/another/string/01020304",
etc.
Note that the tag can be designed to act as the delimiter of a field.
E.g., if all tags (STRING1, STRING8, etc.) have values outside the valid
range of the data being stored (e.g., > 0x7F for ASCII), then the
parse can know that a string terminates when any value > 0x7F is
encountered. (you know that the first value in the region is a tag)
A more versatile approach is to have each tag invoke it's own parse
algorithm:
CITY, "Cañon City\0", ZIPCODE, (long) 81212, AREACODE, ...
Note that 'ñ' is outside the ASCII code points but the CITY parse
routine could rely on some other mechanism ('\0') to detect the end
of that field; similarly, ZIPCODE can expect a 4-byte integer to
immediately follow it; AREA code can expect three BSD digits, etc.
One can insist that tags appear in some fixed order (like TIFF
files) so encountering anything that violates that rule where a
tag is expected can act as a terminator for the field. Or, you
can add a tag that is ENDOFDATA, etc.
This makes the task of replacing any *individual* datum a bit
harder as the fields aren't rigidly defined -- just the start
of the region and its TOTAL length.
OTOH, it's a win when the design evolves to require yet another
parameter without altering the space available to that COLLECTION
of parameters (like a network protocol trying to cram more functionality
into a single packet)
Protecting the integrity of this section can be accomplished with
an error *correcting* code instead of just an error DETECTING
checksum. E.g., I often create nonvolatile instances of the
state of a pseudo-random number generator (because you don't
want a user to be able to "reinitialize" it just by cycling
power) with it's own ECC -- as *it* is often far more valuable than
any other "settings" in the device.