330  yosemite_early_serial_map();

331

332  /* Identify the system */

333  printk('AMCC PowerPC ' BOARDNAME ' Platform ');

334 }

335 

To summarize the previous discussion:

• We entered a breakpoint in gdb at yosemite_setup_arch().

• When the breakpoint was hit, we found ourselves at line 116 of the source file, which was far removed from the function where we defined the breakpoint.

• We produced a disassembly listing of the code at yosemite_setup_arch() and discovered the labels to which this sequence of code was branching.

• Comparing the labels back to our source code, we discovered that the compiler had placed the yosemite_set_emacdata() subroutine inline with the function where we entered a breakpoint, causing potential confusion.

This explains the line numbers reported by gdb when the original breakpoint in yosemite_setup_arch() was hit.

Compilers employ many different kinds of optimization algorithms. This example presented but one: function inlining. Each can confuse a debugger (the human and the machine) in a different way. The challenge is to understand what is happening at the machine level and translate that into what we as developers had intended. You can see now the benefits of using the minimum possible optimization level for debugging.

14.3.3. gdb User-Defined Commands

You might already realize that gdb looks for an initialization file on startup, called .gdbinit. When first invoked, gdb loads this initialization file (usually found in the user's home directory) and acts on the commands within it. One of my favorite combinations is to connect to the target system and set initial breakpoints. In this case, the contents of .gdbinit would look like Listing 14-10.

Listing 14-10. Simple gdb Initialization File

$ cat ~/.gdbinit

set history save on

set history filename ~/.gdb_history

set output-radix 16

define connect

#   target remote bdi:2001

    target remote /dev/ttyS0

    b panic

    b sys_sync

end

This simple .gdbinit file enables the storing of command history in a user-specified file and sets the default output radix for printing of values. Then it defines a gdb user-defined command called connect. (User-defined commands are also often called macros.) When issued at the gdb command prompt, gdb connects to the target system via the desired method and sets the system breakpoints at panic() and sys_sync(). One method is commented out; we discuss this method shortly in Section 14.4.

There is no end to the creative use of gdb user-defined commands. When debugging in the kernel, it is often useful to examine global data structures such as task lists and memory maps. Here we present several useful gdb user-defined commands capable of displaying specific kernel data that you might need to access during your kernel debugging.

14.3.4. Useful Kernel gdb Macros

During kernel debugging, it is often useful to view the processes that are running on the system, as well as some common attributes of those processes. The kernel maintains a linked list of tasks described by struct task_struct. The address of the first task in the list is contained in the kernel global variable init_task, which represents the initial task spawned by the kernel during startup. Each task contains a struct list_head, which links the tasks in a circular linked list. These two ubiquitous kernel structures are described in the following header files:

struct task_struct .../include/linux/sched.h

struct list_head .../include/linux/list.h

Using gdb macros, we can traverse the task list and display useful information about the tasks. It is easy to modify the macros to extract the data you might be interested in. It is also a very useful tool for learning the details of kernel internals.

The first macro we examine (in Listing 14-11) is a simple one that searches the kernel's linked list of task_struct structures until it finds the given task. If it is found, it displays the name of the task.

Listing 14-11. gdb find_task Macro

1 # Helper function to find a task given a PID or the

2 # address of a task_struct.

3 # The result is set into $t

4 define find_task

5   # Addresses greater than _end: kernel data...

6   # ...user passed in an address

7   if ((unsigned)$arg0 > (unsigned)&_end)

8     set $t=(struct task_struct *)$arg0

9   else

10     # User entered a numeric PID

11     # Walk the task list to find it

12     set $t=&init_task

13     if (init_task.pid != (unsigned)$arg0)

14       find_next_task $t

15       while (&init_task!=$t && $t->pid != (unsigned)$arg0)

16         find_next_task $t

17       end

18       if ($t == &init_task)

19         printf 'Couldn't find task; using init_task '

20       end

21     end

22   end

23   printf 'Task '%s': ', $t->comm

24 end

Place this text into your .gdbinit file and restart gdb, or source[95] it using gdb's source command. (We explain the find_next_task macro later in Listing 14-15.) Invoke it as follows:

Добавить отзыв
ВСЕ ОТЗЫВЫ О КНИГЕ В ИЗБРАННОЕ

0

Вы можете отметить интересные вам фрагменты текста, которые будут доступны по уникальной ссылке в адресной строке браузера.

Отметить Добавить цитату