The strings utility examines ASCII string data in binary files. This is especially useful for examining memory dumps when source code or debug symbols might not be available. You might often discover that you can narrow the cause of a crash by tracing the strings back to the offending binary. Although strings does have a few command line options, it is easy to learn and use. See the man page for further details.
Although not strictly a binary utility, the ldd script is another useful tool for the embedded developer. It is part of the C library package and exists on virtually every Linux distribution. ldd lists the shared object library dependencies for a given object file or files. We introduced ldd in Chapter 11, "BusyBox." See Listing 11-2 for an example usage. The ldd script is particularly useful during development of ramdisk images. One of the most common failures asked about on the various embedded Linux mailing lists is a kernel panic after mounting root:
VFS: Mounted root (nfs filesystem).
Freeing unused kernel memory: 96k init
Kernel panic - not syncing: No init found. Try passing init=option to kernel.
One of the most common causes is that the root file system image (be it ramdisk, Flash, or NFS root file system) does not have the supporting libraries for the binaries that the kernel is trying to execute. Using ldd, you can determine which libraries each of your binaries requires and make sure that you include them in your ramdisk or other root file system image. In the previous example kernel panic, init was indeed on the file system, but the Linux dynamic loader, ld.so.1, was missing. Using ldd is quite straightforward:
$ xscale_be-ldd init
libc.so.6 => /opt/mvl/.../lib/libc.so.6 (0xdead1000)
ld-linux.so.3 => /opt/mvl/.../lib/ld-linux.so.3 (0xdead2000)
This simple example demonstrates that the init binary requires two dynamic library objects: libc and ld-linux. Both must be on your target and must be accessible to your init binary that is, they must be readable and executable.
The nm utility displays symbols from an object file. This can be useful for a variety of tasks. For example, when cross-compiling a large application, you encounter unresolved symbols. You can use nm to find which object module contains those symbols and then modify your build environment to include it.
The nm utility provides attributes for each symbol. For example, you can discover whether this symbol is local or global, or whether it is defined or referenced only in a particular object module. Listing 13-18 reproduces several lines from the output of nm run on the U-Boot ELF image u-boot.
Listing 13-18. Displaying Symbols Using nm
$ ppc_85xx-nm u-boot
...
fff23140 b base_address
fff24c98 B BootFile
fff06d64 T BootpRequest
fff00118 t boot_warm
fff21010 d border
fff23000 A __bss_start
...
Notice the link addresses of these U-Boot symbols. They were linked for a Flash device that lives in the highest portion of the memory map on this particular board. This listing contains only a few example symbols, for discussion purposes. The middle column is the symbol type. A capitalized letter indicates a global symbol, and lower case indicates a local symbol. B indicates that the symbol is located in the .bss section. T indicates that the symbol is located in the .text section. D indicates that the symbol is located in the .data section. A indicates that this address is absolute and is not subject to modification by an additional link stage. This absolute symbol indicates the start of the .bss section and is used by the code that clears the .bss on startup, as required for a C execution environment.
The prelink utility is often used in systems in which startup time is important. A dynamically linked ELF executable must be linked at runtime when the program is first loaded. This can take significant time in a large application. prelink prepares the shared libraries and the object files that depend on them to provide a-priori knowledge of the unresolved library references. In effect, this can reduce the startup time of a given application. The man page has complete details on the use of this handy utility.
• The GNU Debugger (GDB) is a complex and powerful debugger with many capabilities. We presented the basics to get you started.
• The DDD graphical front end for GDB integrates source code and data display with the power of GDB command line interface capabilities.
• cbrowser is a useful aid for understanding large projects. It uses the cscope database to rapidly find and display symbols and other elements of C source code.
• Linux is supported by many profiling and trace tools. We presented several, including strace, ltrace, top, and ps, and the memory profilers mtrace and dmalloc.
• Embedded developers often need to build custom images such as those required for bootloaders and firmware images. For these tasks, knowledge of binutils is indispensable. We presented many of the utilities found in binutils, including readelf, objdump, objcopy, and several others.
13.7.1. Suggestions for Additional Reading
GDB: The GNU Project Debugger:
www.gnu.org/software/gdb/gdb.html
GDB Pocket Reference
Arnold Robbins
O'Reilly Media, 2005
Data Display Debugger:
www.gnu.org/software/ddd/
cbrowser home page:
http://cbrowser.sourceforge.net/
cscope home page:
http://cscope.sourceforge.net/index.html
dmallocDebug Malloc Library:
http://dmalloc.com/
Tool Interface Standard (TIS) Executable and Linking Format (ELF) Specification
Version 1.2
TIS Committee, May 1995
Tool interface standards:
DWARF Debugging Information Format Specification
Version 2.0
TIS Committee, May 1995
Chapter 14. Kernel Debugging Techniques
Often the pivotal factor in achieving development timetables comes down to one's efficiency in finding and fixing bugs. Debugging inside the Linux kernel can be quite challenging. No matter how you approach it, kernel debugging will always be complex. This chapter examines some of the complexities and presents ideas and methods to improve your debugging skills inside the kernel and device drivers.
14.1. Challenges to Kernel Debugging
Debugging a modern operating system involves many challenges. Virtual memory operating systems present their own unique challenges. Gone are the days when we could replace a processor with an in-circuit emulator. Processors have become far too fast and complex. Moreover, pipeline architectures hide important code-execution details, partly because memory accesses on the bus can be ordered differently from code execution, and particularly because of internal caching of instruction streams. It is not always possible to correlate external bus activity to internal processor instruction execution, except at a rather coarse level.
Some of the challenges you will encounter while debugging Linux kernel code are:
• Linux kernel code is highly optimized for speed of execution in many areas.
Читать дальше