Symbol tables are always generated when compiling code into object files. Because symbols are needed when linking different object files into one executable. Or into a library.
You're completely right. In addition to just the names, further information about how the functions allocate their local variables on the stack space can be used to rewind into the complete context of any function higher up in the call chain. Also, a map between the relative virtual address space of a module and all its corresponding file names / line numbers enables a debug runtime, like msvcrtd, to automatically resolve the exact location of crashes as well as the content of the variables in the context and the complete call chain at that point in time. So far, the only thing I've found are the file/line combinations for failed asserts, but as far as I remember, they've been disabled and just the text references remain.
This stuff can be done either directly on the user's computer whenever it crashes, however this requires the debug information to not be stripped away. Otherwise, a minidump can be created and be used by the developer in combination with the correct debug symbols for the modules that have been present in memory when the minidump has been created. The latter also requires some mechanism for catching, collecting and transmitting the minidumps. Firefox does this, for example.
What compiler flags have been used can sometimes be determined from the debug symbols, however, it is usually sufficient to just look at the disassembled code and anolyze which things the compiler would've optimized away. This works quite easily once one has debugged on asm level a few times - the flags all have characteristic effects. For example, setting the flag to optimize for small binaries produces other instruction encodings and even chooses different instructions in the same code situation, also proper alignment isn't enforced and so on.
For example, TESV has a huge amount of functions looking exactly like, or very similar to:
void* get_this(void* this) { return *this; }or
void* get_this_offset_8(void* this) { return *(this + 8); }Here, the "this" variable is actually passed through ecx, so it's using the __thiscall convention, meaning it is a C++ getter method that hasn't been inlined, even though the whole method can be optimized away so just a single instruction remains. In conservative inlining mode, the compiler calculates how much code would have to be generated when inlining it and if that's under a certain or configurable limit, it will automatically be inlined even if the function doesn't bear the inline keyword. Varying degrees of inlining aggressiveness vs. additional binary size can be configured in all the major compilers.
In Skyrim, the function I described here as get_this() has three copies and is being called in about 30000 different places in the game code. When profiling the original executable, it is also shown as one of the functions the CPU spends the most time in, simply due to the fact that is called tens of thousands of times per second and the unoptimized function body is quite large in assembler code. This is how and why the whole hard-crafted optimization was a viable route to go for Skyrim. Typically, this doesn't work well because optimizing compilers tend to distribute the time the CPU spends very evenly across the whole code, so that only algorithms with bad runtime behavior will stand out instead of unoptimized code constructs.