When I first started working on GLIBC development, one of the challenges was to understand how to debug GLIBC. All the usual attempts to compile GLIBC without optimizations ended up with GLIBC grumpily complaining that it cannot be compiled that way. The development FAQ says
In the early startup of the dynamic loader (_dl_start), before relocation of the PLT, you cannot make function calls. You must inline the functions you will use during early startup, or call compiler
builtins (__builtin_*).Without optimizations enabled GNU CC will not inline functions. The early startup of the dynamic loader will make function calls via an unrelocated PLT and crash.
OK, fair enough. Can the optimized code be perhaps then debugged, since after all GCC generates adequate debug information even when optimizations are enabled? Well, not really. GLIBC is a carefully written piece of software and people who wrote it (a) know quite a lot about compilers and (b) use that knowledge to squeeze every drop of optimizations out of the code. So though the resulting debug information is usually enough for users to get a sensible backtrace and report bugs to the developers, it is almost useless for single-stepping in a debugger.
The two main problems with single-stepping optimized GLIBC are (1) line numbers and (2) optimized out variables. The line numbers at -O2 get screwed up beyond repair. For example, when you try to single step the runtime linker while it is loading dependencies and resolving symbols, the debugger will be thrown between source files and functions at every other line. So to make the situation more bearable you need to add -fno-schedule-insns -fno-schedule-insns2 to the build flags.
I don’t really know how to properly fix problem (2) with variables being optimized out. What I usually do is just recompile the only file I’m interested in without optimizations and relink GLIBC. Normal GLIBC development rarely requires single-stepping the runtime linker before it self-relocates, so try, it might work for you too!
My recipe for debuggable GLIBC is the following:
Build GLIBC, save the log.
Find the command line in the log that compiles the file you’re interested in. Note: most files are compiled into file.o and file.os; the first one goes into the static library, and the second — into the shared. If you’re not sure which one you need, it’s probably the .os version.
Go to the source directory in which the file is located. It is the subdirectory in the source tree which stands right before the file.os in the output destination. It took me several builds to understand that after you run make in the build tree the first command is cd <src_dir> — funny, eh?
Add -E -dD to the end of the compile line and change the output destination to a directory outside both build and source trees. This way you will get the preprocessed source. Take a look at it. Most likely you’d want to remove the original line information from it (sed “/#\ .*/d” < file.i > file.c) and reformat it (indent < file.c > file2.c) to unravel all the hidden sorcery in GLIBC macros.
Remove the -include libc-symbols.h from the command line (it’s the header through which that “glibc cannot be compiled without optimization” error gets in!) and remove -O2 from the optimization flags.
Recompile file.
Rerun make to relink the libraries, the runtime linker and the utilities. Check the log to make sure only things that were intended for update were updated.
If the make process didn’t notice that file.o was changed, force rebuild by removing the top-level binary, e.g., remove libc-pic.a to force rebuild of libc.so.
Voilà! Debug at will!
联系客服