objdump
shows the disassembled code because that's its job. It knows the format of the executable file. Executables are not just a straight sequence of instructions: they have structure. Executables typically start with a header containing various metadata and are organized in sections. Dynamically linked executables necessarily contain enough information for the dynamic linker, so they must indicate what symbols the executable needs and an indication of where those symbols will be loaded so that the program can find them when it runs.
For example, most Unix systems use ELF (other formats exist). If objdump
detects an ELF binary (by checking the magic number at the beginning of the file), it parses the file header, which lets it know where the program header and the section tables are located. Each section contains an indication of what type of content it contains. objdump
further parses each section according to its type. When it sees a section that's supposed to contain code, it runs a disassembler on it.
Disassembly is fairly simple in principle: code is a list of instructions, and disassembly is just translating from a binary representation of instructions to a textual one. The disassembler just takes the instructions in order. In practice, things can be more complicated on architectures where instructions have a variable size, and because data can be embedded in code sections. objdump
generally works on “normal” executables, but may not produce sensible output on binaries that have been deliberately obfuscated.
objdump
does not perform any static analysis. It just makes a direct parsing and translation, it doesn't do any analysis of what the instructions mean.
gcc
wouldn't generate a .o file in the invocation you gave. As for assembly you might considergcc -S
. I realise maybe you're only afterobjdump
but if not...