I have a pretty big program which shows some very weird behavior during compiling.
Depending on the order in which I included two files, the program either compiled fine or gave a bunch of "L15 Multiple Call to Function" warnings. Each warning had some random function called from main as "NAME" and always "INTERRUPTVECTORS_INT0/MAIN" (see below) as "CALLER1" and "?C_C51STARTUP" as "CALLER2".
The two included files only defined a bunch of functions, none of which did anything noteworthy. Their respective declarations are all in a single file included much earlier. And those functions don't refer to each other.
I merged the two files together and started moving functions around. I narrowed it down to two functions which, when placed in one order, compiled fine, but produced the same warnings when placed in the opposite order. This is one of those two functions:
bit GetSomeBitOfSomeState(unsigned int State, unsigned char BitIndex) { // return (State & (1 << BitIndex)) != 0; return 1 << State; }
Through some trial and error I found that the code, as above, compiled fine (in one order), but replacing "State" with "BitIndex" (which seems like a very mundane thing to do) caused the same warnings to show up again.
The INTERRUPTVECTORS_INT0 mentioned above is this:
void InterruptVectors_Int0() interrupt 0 { if (IsRunning) { ZeroPassCount++; } }
IsRunning is defined in a much earlier file as
bit IsRunning = false;
(I might have forgotten a
volatile
here, but its presence does not have any effect on the problem) and ZeroPassCount is defined as
volatile unsigned int ZeroPassCount = 0;
in that same file. If I comment out just the
ZeroPassCount++;
, the program compiles fine (assuming the return statement from above still contains "BitIndex"), but commenting out the if-statement (i.e.
executes always) or having nothing commented out at all, causes the warnings to show up again.
In the "OVERLAY MAP OF MODULE: ..." section of the .MAP file there is indeed a link from INTERRUPTVECTORS_INT0/MAIN to ?PR?MAIN:
FUNCTION/MODULE BIT_GROUP DATA_GROUP XDATA_GROUP --> CALLED FUNCTION/MODULE START STOP START STOP START STOP =================================================================================== INTERRUPTVECTORS_INT0/MAIN ----- ----- ----- ----- ----- ----- +--> ?PR?MAIN MAIN ----- ----- ----- ----- ----- ----- +--> ?CO?MAIN ?CO?MAIN ----- ----- ----- ----- ----- ----- +--> ...
But I don't understand how this is possible, because InterruptVectors_Int0 does not call any functions. It merely updates a global variable.
Any insights into how I can systematically find out what's causing this and how I can fix it? Since some changes I made were completely unrelated to any of the warnings, any change I try might randomly make the problem disappear or reappear, so systematic trial and error is very difficult to do.
specifically on an 8051, that is.
It's probably one of those, "Use an 8051 or functions pointers - choose one" questions ... ?
I mean... once you know how the linker relates different functions when creating function pointers, it doesn't seem all too crazy. But getting there is the hard part. And I don't just mean understanding the rules of the linker. The problem is when you don't know that you are doing something dangerous.
The original symptom (before the L13 warnings) was that a single character on the display wasn't updated from an underscore to a hyphen when power was turned on. Because the function which draws to the display still got data to print an underscore. Because the function which changes that backing data was never called. Because the initialization function calling that function was never called. Because the loop in another function, which calls a bunch of initialization functions by function pointer, only ever ran one iteration. Because the loop counter got overwritten with a high value after the first function pointer call. Because the first called initialization function trashed the local variables of that calling function. Because the linker thought those two functions could be overlapped. Because the link to the initialization function came from whichever code was generated to initialize the table.
It all makes sense when you know it. But getting from the symptom to the root cause is a mind-bending journey of "WTF?", "WHY?" and "HOW EVEN?".
But when everything is working smoothly, doing low-level programming is usually a lot of fun :D
Indeed.
And there is the ongoing maintenance worry that you might change something in the code, but forget one of the details to keep the linker happy ...
But when everything is working smoothly, doing low-level programming is usually a lot of fun
you may not be familiar with the common standard non-maintainable code is worthless
I have, on occasion, when tasked with maintenance ended up rewriting the code to a maintainable shape