What limitation am I hitting?

I have a situation where I'm hitting some limitation in the GCC tool chain.

I'm using the Uno, so I should have about 30 kB of code flash, and about 2 kB of SRAM (for globals + stack), if I read all the documentation correctly.

Yet, I'm running into problems at code sizes that are way smaller than that. Let me explain the symptoms. I have two global instances of some classes. The classes each have a small vtable, and a handful of bytes of member variables. Those instances, in turn, reference a hundred bytes or so of PROGMEM data and a dozen bytes of other global data.

In the smallest case, these instances are commented out:

//Menu menu(lcd, &menuExit);
//Page main(&menu, &mainText, &mainAction);

The binary sketch size of this is 8482 bytes (of a 32256 byte maximum). This runs as expected.

Now, I can add one of the variables:

Menu menu(lcd, &menuExit);
//Page main(&menu, &mainText, &mainAction);

The binary size is 8586 bytes, and it still runs as expected.

Moving on, I can add the second variable, with the first variable still there, and the linker goes bonkers:

Menu menu(lcd, &menuExit);
Page main(&menu, &mainText, &mainAction);

Compiling this results in a binary size of only 4722 bytes, but no compiler or linker errors. The program does not run as expected. However, I don't think this is "running out of SRAM," by looking at the memory consumed. I think this is a bug in the compiler or linker, where it generates an invalid image, without generating an error to the user, and this concerns me a lot, because I don't know how to diagnose this problem.

But, the plot thickens! If I remove the first variable, only keeping the second, I get an unexplained internal linker error:

//Menu menu(lcd, &menuExit);
Page main(0, &mainText, &mainAction);
c:/code/arduino/arduino-0022/hardware/tools/avr/bin/../lib/gcc/avr/4.3.2/../../../../avr/lib/avr5/crtm328p.o:(.init9+0x0): warning: internal error: out of range error
Binary sketch size: 4610 bytes (of a 32256 byte maximum)

This gives me very little to go on. I'd like to start varying the different inputs to this compilation, and examine the object files (say, with objdump or something similar), but I can't find where the Arduino software stores the intermediate build artifacts. It seems to create temporary directories, compile into there, and then wipe them all out before I can get to them.

Is there some way to avoid that deletion? Is there some way to build my sketch from the command line? Perhaps one step at a time -- gather, compile, link, as separate steps? Is there some way to know how the environment is invoking the compiler/linker so I can reproduce the exact same build under conditions I can examine? And is this documented somewhere? The documentation Wiki just seems to talk about the high-level API of the built-in libraries; perhaps there's some reference documentation that I've simply missed?

Any help on this would be appreciated.

How do you know how much RAM you're consuming?
For that matter, how do we know? (Hint)

I am using indirect measurements. Size of struct/class can be had by adding up the members (include vtable!)
Thus, size delta from changes should be understandable. Some classes may bring in new libraries, which use more sram, which i don't know how to get good grips on.
Printing the address of globals is useful -- globals towards the end of the sketch seem to be towards the end of the data section.
If i could somehow convince the linker to spit out a symbol map, or objdump the .o files or bin/hex file, that would be very helpful. Is there a way to do that?
Note the "binary size" values, though -- the problem I'm seeing is likely in codegen/linking, not runtime.

jwatte:
I am using indirect measurements. Size of struct/class can be had by adding up the members (include vtable!)
Thus, size delta from changes should be understandable. Some classes may bring in new libraries, which use more sram, which i don't know how to get good grips on.
Printing the address of globals is useful -- globals towards the end of the sketch seem to be towards the end of the data section.
If i could somehow convince the linker to spit out a symbol map, or objdump the .o files or bin/hex file, that would be very helpful. Is there a way to do that?
Note the "binary size" values, though -- the problem I'm seeing is likely in codegen/linking, not runtime.

I'm not going to be as polite as AWOL:

POST YOUR CODE.

Seriously - how do you really expect us to help you figure this out, if we can't see exactly what you are doing...?

Just a thought - using main as an identifier - isn't that going to cause issues?

cr0sh:
POST YOUR CODE.

The code is spread across three libraries (each with a .h and a .cpp) and the actual sketchbook project. If I saw someone post all of that on the web and then ask me "hey, debug my code for me," I'd think he was crazy. In fact, I'm not asking for fish (the solution to the problem); I'm asking for a fishing pole (the methods to debug the problem).

Specifically, without any code at all, I can re-phrase my questions much more succinctly:

  • How can I capture or re-generate the compiled .o files and run objdump on them?
  • How can I capture or re-generate the linked image and obtain a link map for it?

Regarding using the name "main" -- that's a fantastic observation! Gotta go try renaming that. I'd expect a syntax error from the compiler if that were the case, or worst case a linker multiple symbol definition error, but the cases where I do get the error are totally consistent with using "main" as a non-function symbol.

MarkT:
Just a thought - using main as an identifier - isn't that going to cause issues?

Alright, MarkT wins the prize! Apparently I posted just enough code to help solve the problem :smiley: Thanks for the fish, MarkT; I really appreciate it!

My other questions remain, though: Where do I go to get self-help tools, such as .o files, .map files, and the like?

Where do I go to get self-help tools, such as .o files, .map files, and the like?

Hold the shift key down when you click on "verify" or "download". All will be revealed.

That's good to know! I can now objdump the .elf file it generates, which would hopefully have led me to the solution without MarkT having to force open my eyes :slight_smile: Thank you.

Is there a good pointer to where things like these are documented?

Btw: I first tried it with the shortcut keys (Ctrl-shift-R) and menu item (shift-select) which didn't work. Apparently, it only works for the toolbar buttons.

jwatte:
Regarding using the name "main" -- that's a fantastic observation! Gotta go try renaming that. I'd expect a syntax error from the compiler if that were the case, or worst case a linker multiple symbol definition error, but the cases where I do get the error are totally consistent with using "main" as a non-function symbol.

I think he is probably right about main. You can make your own main function - I tried a while back. You don't get a linker error, probably because if it finds your main it doesn't search the libaries for their main.

But if your main function doesn't do what you expect (like call init and loop) you will get odd runtime behaviour. Or it it has the wrong return type, you are likely to get linker errors that aren't immediately obvious.

(Hint: don't make your own init function either).

jwatte:

MarkT:
Just a thought - using main as an identifier - isn't that going to cause issues?

Alright, MarkT wins the prize! Apparently I posted just enough code to help solve the problem :smiley: Thanks for the fish, MarkT; I really appreciate it!

My other questions remain, though: Where do I go to get self-help tools, such as .o files, .map files, and the like?

:wink:

If you are interested you can look at the runtime source code for the Arduino in hardware/arduino/cores/arduino/ in your Arduino installation. You'll find the main() routine there, it calls setup() and when loops calling loop().

Looks like I was half right. :wink:

main calls init and setup, and then loop:

int main(void)
{
	init();

	setup();
    
	for (;;)
		loop();
        
	return 0;
}

This concerns me. A program is only allowed to have one definition of a particular symbol, and you should get a linker error if you define a symbol more than once. Similarly, you should get a compile error if the runtime code simply includes all the data, and you try to define the symbol "main" twice in the same compilation unit.

Thanks for all your help, though!

Regular GCC does this right:

jwatte@svn:/tmp> cat > foo.cpp

class Foo {
};

Foo main;

int main(int argc, char const *argv[]) {
        return 0;
}
jwatte@svn:/tmp> g++ -c foo.cpp
foo.cpp: In function âint main(int, const char**)â:
foo.cpp:7: error: âint main(int, const char**)â redeclared as different kind of symbol
foo.cpp:5: error: previous declaration of âFoo mainâ
jwatte@svn:/tmp>

jwatte:
This concerns me. A program is only allowed to have one definition of a particular symbol, and you should get a linker error if you define a symbol more than once.

The trigger for the linker searching dozens of library files is the desire to resolve a symbol for which it doesn't yet have code for. It isn't designed to merely search every possibly library for duplicates.

Actually, the linker searches exactly the libraries you specify, and includes the transitive closure of all symbols that are referenced. In fact, in the UNIX (and thus generally GCC) world, including any symbol from a library will include all of that particular translation unit, even if the other functions are not used. It seems to me as if the Arduino/AVR version of the compiler fixes that particular mis-feature, though.

And, when a linker searches libraries that have been defined, if it finds multiple definitions of the same name, that is generally a linker error. Here is the error GCC/GLD spits out in that case:

[jwatte@svn:/tmp]> cat foo.cpp

int main() {
    return 0;
}
[jwatte@svn:/tmp]> cat bar.cpp

int main() {
    return 1;
}
[jwatte@svn:/tmp]> g++ -c foo.cpp
[jwatte@svn:/tmp]> g++ -c bar.cpp
[jwatte@svn:/tmp]> g++ -o foobar foo.o bar.o
bar.o: In function `main':
bar.cpp:(.text+0x0): multiple definition of `main'
foo.o:foo.cpp:(.text+0x0): first defined here
collect2: ld returned 1 exit status
[jwatte@svn:/tmp]>

And, what I posted was the compile error I would have expected the compiler to generate, assuming that my sketch gets included into some greater file that defines main(). If instead main() lives in a referenced .o file or library, then I would expect a linker error.

Thus, either the Arduino sketch compilation model is separate compilation with linking, in which case I should get a linker error. Or it's a single compilation unit with inclusion, in which case I should get a compiler error. In either case, the AVR toolchain's behavior is mysterious, and hopefully (?) not actually intended. Which reminds me: assuming that the failure to point out my dumb mistake is a toolchain bug, where would I file it? Is there an Arduino or AVR-specific GCC project, or is it just gnu.org?

Yes, I can reproduce that, but that isn't linking in libraries. First to prove that:

$ cat foo.cpp

#include <iostream>
int main() {
    std::cout << "foo" << std::endl;
    return 0;
}

$ cat bar.cpp

#include <iostream>

int main() {
    std::cout << "bar" << std::endl;
    return 1;
}


$ g++ -c foo.cpp

$ g++ -c bar.cpp

$ g++ -o foobar foo.o bar.o
ld: duplicate symbol _main in bar.o and foo.o for architecture x86_64
collect2: ld returned 1 exit status
$

But the Arduino IDE links in libraries, not compilation units. There is a difference. Here it is:

$ g++ -c foo.cpp   # compile foo

$ ar rs foo.a foo.o  # turn foo into a library

$ g++ foo.a bar.cpp -o bar  # compile bar, linking in foo.a

$ ./bar   # run resulting program
bar

So the difference is the way libraries are handled, compared to compilation units. Libraries are places you search if you don't have all the functions you need. I don't agree it is a bug. See here:

From inside that:

The linker handles an archive file by scanning through it for members which define symbols that have so far been referenced but not defined.

Note the key words "referenced but not yet defined".

Ah, it seems my "same translation unit" thesis was incorrect, then.

The reason I had that thesis was that LINE doesn't evaluate to the correct line number in the file, so "something" is being done to the source file in my sketch other than just compiling it.

To be super picky, the order of libraries matters, too! Specifically, if you already pick up "main" from a library, then trying to re-define it in a .o file will generate a multiple symbol definition error:

jwatte@svn:/tmp> ar cr libfoo.a foo.o
jwatte@svn:/tmp> ar cr libbar.a bar.o
jwatte@svn:/tmp> g++ -o foobar foo.o -L. -lbar
jwatte@svn:/tmp> g++ -o foobar -L. -lbar foo.o
foo.o: In function `main':
foo.cpp:(.text+0x0): multiple definition of `main'
./libbar.a(bar.o):bar.cpp:(.text+0x0): first defined here
collect2: ld returned 1 exit status
jwatte@svn:/tmp>

So, if there were a single Wiki page with three paragraphs about how my sketch turns into a file to be compiled, and what specifically is done to compile and link libraries (it seems to do something magic when it sees a #include, for example, to re-build my local libraries!), that would help a lot. Reason being that I come at this with 30 years of software development baggage and kind-of want to understand exactly what is being done where, to avoid biting myself with assumptions... (when I assume -- I make an ass of u and me !)

The 'something' you refer to is the IDE massaging your sketch into a compilable program - adding main, producing prototypes too IIRC. It accounts for the fact that the error message line numbers don't match with your source too. It also has some bizarre side effects - sometimes what look like viable code won't compile until you put an unneeded variable declaration before it. I've had trouble with typedef struct too.

Right. I guess my point is that, if that process were actually clearly and stringently documented somewhere, that would probably be helpful :slight_smile:

Redirecting ??