Generating a stack trace from an ELF.
As fantastic as asserts are, sometimes a vanilla assert leaves a programmer wanting more. Wanting for what, you ask? A stack trace. Preferably a human-readable one. And it’s surprisingly easy to do.
Step 1: Get yourself some symbols. A stack trace won’t be much use unless it’s readable. If they aren’t already there, you’ll have to add the symbol table and string table to your elf file by adding *(.symtab) and *(.symstr) to your loader.*.x files. For CS452, I only added them to loader.app.x, since I don’t support stack traces in the kernel.
Step 2: Access those symbols. You’ll have to parse the elf’s section header table to find the symbol table. You’re looking for a section of type SHT_SYMTAB. Once you find the section, you can rip some useful data out of it. The sh_link member is the section index of the string table, which you’ll need to print out the symbols. You can get the offset and size of the real data in the symbol & string tables with pretty much the same techniques you used to parse the elf’s program header table. Each entry in your symbol table has a name, which is actually an index into your string table that points to the string. I made sure I had things working at this point by just printing out all of the symbols in an elf when it was loaded.
Step 3: Perform a stack trace. This is much, much easier than I thought it would be. There are two things on the stack that are of particular interest to you: The return address to the previous function call, and the frame pointer from the previous function pointer. You want to keep loading the previous function call’s frame pointer until you get to the end of the world, which is probably a frame pointer of 0.
Step 4: Print yourself a fancy new stack trace. Remember the return address to the previous function call, that other interesting bit of the stack? You want to find a symbol that matches that return address as closely as possible. Your symbol table contains symbols that are associated with values, which are function entry points. You’re not going to be returning to the entry point, you’ll be returning to somewhere past it, so you need to find the function that’s closest to, but not over, your return address. Make sure that you’re only looking for symbols that are actually function names. This is (again) surprisingly easy to do: ELF32_ST_TYPE(symbolTableEntry.st_info) == STT_FUNC.
Now, you just printed some pretty ugly function names. That’s because they’re mangled. My first attempt at name demangling took around 3 hours (much longer than researching and writing the stack trace), and that was all spent hunting down gcc/gdb name demangling code and making it compile for my assignment. Long story short, it’s not worth it. I recommend that you stick to mangled names, and learn to love them.
And you’re done. I wish I’d known how little code this would involve three months ago.
1 comment1 Comment so far
Leave a reply
Glad to have you back to the online world!