Let's write a simple program for Linux. How hard can it be? Well, simple is the opposite of complex, not of hard, and it is surprisingly hard to create something simple. What is left when we get rid of the complexity from the standard library, all the modern security features, debugging information, and error handling mechanisms?
Still looks pretty simple, right? Wrong! While this might be familiar territory and easy to comprehend, the program is far from simple. Let's take a look behind the curtain.
That's a lot of symbols! Actually, as far as symbol tables go, this one is quite modest. Any non-trivial program will have many more symbols, but still, what are they all for? We're just printing a string!
It turns out that for simple cases, where there is no formatting work required by printf, GCC optimizes the code and replaces it with the simpler puts@GLIBC_2.2.5 from libc. The address is all zeros since the symbol is undefined (*UND*). It will be resolved when the program is loaded together with the dynamic libc.so library as we run it.
Let's keep digging. What sections are there in the program? The only data we have is the hardcoded string and its length. Surely we only need a .text section? Let's see what we got: