Utilizing GDB with T-Engine

PMC Laboratory


With T-Engine, we have adopted GNU for a development environment reference, and thus we can use gdb as a debugger. In this paper, we will explain the characteristics and methods of using gdb.

What We Call Debugging

Generally, even though we create software, there are cases when it does not run according to expectations. As for the causes that software does not run according to expectations, there are, for example, the following things [1~2].

(1) Hardware disorders
(2) Compiler disorders
(3) Operating system disorders
(4) Application disorders

"Hardware disorders" are things such as a certain computation cannot be executed normally; or, if there are four bytes, they cannot be written, since normally writing into memory is one byte. Furthermore, there are also cases where these are limitations in the design of hardware, and there are occasions when the specification not being expertly transmitted becomes the cause of confusion. Generally, because it is harder to correct hardware than software, in cases where a disorder is found in hardware, there are also instances of modifying the software in a way that avoids the disorder.

"Compiler disorders" are things in which the conversion from C language to machine code is not carried out normally. In today's CPUs, various schemes have been put in place in order to make them high performance, and thus they are made up so that they demonstrate their performance by means of the compiler carrying out optimization processing. In accordance with optimization processing, useless things are removed from the original C language source code, and new source code that does the same work is created, but this processing is very complex. When there are disorders in optimization processing, there are occasions when source code that does not run normally comes into being.

"Operating system disorders" are things such as things such as, for example, the system calls do not run as in the specification, or that errors do not occur in places where errors should occur. As for things similar to this, there are library disorders.

"Application disorders" are cases when there are mistakes in software that you have created. Besides cases where the software itself is not correct, this corresponds to cases where the hardware control procedures are incorrect, or the method for using other software (the operating system or the libraries, etc.) are incorrect.

Generally, when someone mentions debugging, they are indicating the work to ascertain the causes of and take countermeasures in regard to the disorders (bugs) in (1) ~ (4).

Methods of Debugging

A disorder is the difference between the results one ought to be able to obtain and the results that were actually obtained. However, there are also cases in which the cause of a disorder cannot be guessed by just looking at the results that have been obtained. In these cases, it is conceivable that one can execute the software little by little and look for the portion where it comes not to run in accordance with expectations. There are the following methods to this.

(1) Doing it in a manner in which one acquires the present status in the source code component by component
(2) Using operating system functions and running down the execution states of the program
(3) Using a monitor and executing the software little by little
(4) Using a debugger and executing the software little by little

(1) is something where, for example, we display the values of variables by embedding printf in the source code. In a case where display speed becomes a problem, this can be improved by writing out a log in memory and later reading it after putting it in order. Also, in cases where we are using complicated data structures, it is also effective to periodically check whether or not the data structures are broken. As we move forward slowly cornering the places where the disorders occur, we research the causes [3].

(2) is something where we utilize the debugging support functions that are prepared in the operating system. For example, in T-Kernel/DS, functions that call up system calls, call up interrupt handlers, and set the full routines for the respective task switches have been prepared. By outputting a log here, we can investigate how the multiple tasks are actually running. With this method, modification of the software we will execute is not necessary.

(3) is something where we utilize the breakpoint function or reverse assemble function of a low level monitor and execute the software little by little. In the case of T-Engine, a monitor called T-Monitor has been prepared. Even in cases where we are using T-Monitor, it is necessary to modify the software. This will be described later, but caution is required while operating T-Monitor, because T-Engine completely stops.

(4) is something where we utilize the debugging breakpoint function or the single step execution function and execute the software little by little. In (1) ~ (2), only a log was obtained, but when we use a debugger, there is also the advantage that we can interactively reference variables, modify them, and so forth. Furthermore, in using a debugger, it becomes necessary to specify debug mode at the time the software is compiled. Moreover, with the "gdb" debugger we are introducing on this occasion, as is the case when we use T-Monitor, T-Engine completely stops while it is in operation.

In actual debugging work, we come to use the suitable one among these means. In the debugging of the inside of C language functions, we use (1), (3), and (4); in problems with the cooperation of multiple tasks, we use (2). When we compare (1) with (3) and (4), compared to the repetition of source code correction, compiling, downloading, and execution in (1), in the respect that with (3) there is no need to correct, and (4) also is completed by just compiling once, we could say that they are superior. Also, when we compare (3) and (4), (3) becomes debugging at the machine code level, but because with (4) debugging at the C language level is possible, there is the advantage that the work is easy to do. On the other hand, with (3), it is possible to use the original debugging functions of the CPU.

Basic Knowledge of C Language

Normally, when the compiler converts the C language into machine code, it tries as much as possible to remove useless things. For this reason, although the C language and the machine code are created so as to perform the same work, the actual execution process becomes something different. The debugger will display the C language source code, but because what is actually being executed is machine code, there are occasions where discrepancies arise.

As for the place where C language variables are stored, in actual machine code, there are cases when it becomes the registers, and there are cases when it becomes the memory. Registers are high speed, but because the number is limited, it has come about that the variables with the highest frequency of use are assigned to the registers. Furthermore, in cases where two variables are not used simultaneously, there are also occasions when two variables are assigned to the same register or memory. Although these kinds of variables are within the scope, there are cases where they are not displayed by the debugger.

When we convert C language into machine code, there are occasions when the compiler changes the places of the lines. This is carried out in cases of the type where there is a speed boost when the compiler changes the places of the execution order, and, moreover, where the results do not change. As a result of this, there is the possibility that program code will be displayed in the debugger in a manner in which it is being executed in a different order from that of the C language source code.

Finally, we will explain about stack frames. Function calls in C language are carried out in machine code through the creation of stack frames. For example, in a case where we call function A from function B, the argument is first stored in a stack frame. Furthermore, while function A is being executed, the automatic variable of function B is remembered in a stack frame. In a case supposing function B being called by function C, the automatic variable and argument of function C are remembered in a separate stack frame. When the computer returns from the function, the stack frame is automatically released. Accordingly, if we look at the stack frames, we come to understand the process of the function calls. Because the debugger possesses a function for displaying stack frames, by means of this, we learn how each function is called by which type of argument.

Limits of Debugging

On the other hand, there are also limits to debugging. First, when information has been lost as a result of optimization in the above mentioned manner, there are cases where the debugger does not operate in the manner expected.

Also, in a case where the debugger has ended up damaged as a result of a software disorder, the debugger cannot display information. As a classic example, when there is a buffer overrun for an automatic variable in a stack frame (for example, writing 20 bytes into a region where only 10 bytes has been reserved), the stack frame ends up damaged and a lot of clues are completely lost [4].

When using gdb with T-Engine, while gdb is in operation, T-Engine completely stops, and it enters a state in which interrupts and the like absolutely cannot be accepted. Accordingly, debugging such things as interrupt handlers, which must be executed in real time, is difficult with gdb and T-Monitor. Also, in the case of systems that carry out the control of devices, if we too easily use breakpoints while they are in operation, there is the possibility that control will end up being lost, and it will come about that the device does not operate normally.

Terminology 

Breakpoint This is a function that by means of setting a breakpoint in a program line stops the program immediately prior to where that line is executed. After the program stops, it is possible to display variables and memory, rewrite, and so on. It is also possible to continue execution without doing anything.
Software breakpoint This is a breakpoint that is realized by rewriting a program in memory and embedding an exception instruction in a particular line. When the computer executes that line, an exception is generated, and the program stops. This cannot be used in programs loaded into non-rewriteable memory, but it is possible to set many breakpoints.
Hardware breakpoint This is a breakpoint that is realized by utilizing a CPU-dependent function that generates an exception when the computer tries to execute a particular address. This both depends on the CPU, and it is not possible to set more than one or two breakpoints. The watchpoint that breaks when reading and writing a particular address is also realized through the same method.
Single step execution This is a function that stops the program after only one line is executed. Two types, "step into" and "step over," have been prepared, dependent on its operation in cases where there is a function call in that line.
Step into This is a function in single step execution that stops at the first line after calling up a function in a case where there is a function call in that line. If there is not a function call, step into and step over become the same operation. In gdb, this is the s command.
Step over This is a function in single step execution that stops after proceeding to the next line following return from a function without stopping inside the called function in a case where there is a function call in that line. (If there are multiple function calls, it stops after executing them all and proceeding to the next line.) If there is not a function call, step into and step over become the same operation. In gdb, this is the n command.
Monitor This operates outside the management of the operating system. It is the lowest level CPU control environment. In T-Engine, T-Monitor has been prepared, and it has become possible to control memory and registers, start up the operating system, update the flash ROM, and so on. Outside of starting up the operating system immediately after reset, it possesses a function that accepts commands when a CPU exception or an undefined interrupt is generated. Because the monitor runs in an interrupt prohibited status, the operating system and tasks, and handler types completely stop, and it is not possible to access device drivers and the like.

Basic Method of Use

From here, we will explain about the procedures when actually using gdb. As for the hardware, we have used µT-Engine/M32104, but no matter what the model, the basic operations are the same.

First, when we compile, we specify the debug option. Normally, when we do gmake in a directory to which we have attached '.debug', as in the manner of m32104.debug, the program is automatically compiled with the debug option attached. Furthermore, by revising Makefile, we make it so that we specify a a fixed address format, and not relocatable format [5]. By means of this, gdb comes to catch the correspondence between the C language source code and the machine code. Also, when this program is executed, it is necessary at some point in time to transfer control to T-Monitor. For example, we make it so that we call tm_monitor() at the beginning of the main function.

When we download this program to T-Engine and execute it, control moves to T-Monitor [6]. In this state, T-Engine stops (in a case where you are not using gdb, it is possible to continue execution with the 'g' command).

Here, we terminate gterm at the host side, and we start up gdb. In using gdb, it is necessary to create and place aside beforehand a settings file. As a gdb startup option, if we specify the execution file at the host side, in addition to reading the necessary information from there, we carry out communication with T-Engine. If this is successful, T-Engine comes to wait for command input.

When we use gdb on T-Engine, while T-Engine is in execution status, it is impossible to control gdb, and after T-Engine breaks and changes to suspended status, it is necessary to enter a command. For this reason, when we restart execution, it is necessary to without fail make and put in place a setting so that we can break at any moment. As a typical work procedure, executing after first setting breakpoints at the beginning of dubious functions, and from there by executing the functions with single step to check if they are executed according to expectation is a method that can be taken.

When the gdb work ends, we terminate gdb and start up gterm. Because we are in T-Monitor command acceptance status, we input the 'g' command, and recommence execution.

Main Commands

In gdb, commands for such things as displaying the source program, managing the breakpoints, controlling execution, displaying variables and evaluating expressions, and displaying memory contents have been prepared. By evaluating an assignment expression, it is also possible to rewrite a variable. In that gdb is a command line debugger, it has very many functions, and thus those who have taken an interest in it after reading this article, should please by all means consult reference documents and learn about it.

First, please pay attention to the point that, as a general rule, when we input a blank line in gdb, it results in the repetition of the immediately preceding command. At the time of source code display or single step execution, because things are arranged in a way to proceed to the next step just by pressing the return key, if we meaninglessly press the return key, the command will end up re-executing.

To display the source code, we use the 'l' command. With 'l line number' or 'l function name', we can display the source to the front and back. When we input return, the continuation is automatically displayed. Also, as for the front and back of the break, etc., we can display forward of and behind the broken location with just 'l'.

To display stack frames, we use the 'bt' command. Using the functions currently being executed as a starting point, this displays the called functions by following their order. The execution location of the called functions, the arguments at the time they are called, and so on are also displayed. In a case where it has come about that you do not know the location where a function is currently executing, it would be good for you to look at the line number that is displayed here.

We set the breakpoint with 'b function name' or 'b line number'. Gdb breaks immediately prior to the line we have specified here being executed (in a case where we have specified a function name, it stops immediately after it has entered the function). It is possible to specify multiple breakpoints, and the numbers are assigned in order. We can delete a breakpoint with 'd number'.

In continuing execution up to where gdb breaks, we use 'c'. When we arrive at the breakpoint, information concerning the break is displayed, and gdb changes to command input status.

In a case where we want to immediately break after executing just one line, we use single step execution. 'n' executes the present line, but if it is also a case in which we have called a function, gdb does not break inside the function. 's' executes one line, and then in a case where we have called a function, gdb breaks at the beginning of the function. In order to continue executing the single step, we press the return key.

In displaying variables and the like, 'p' can be used. When we make it 'p i', we display the contents of variable i with a decimal number. We can also specify an option, and thus when we make it 'p/x i', we display the contents of variable i with a hexadecimal number. In a case where we have specified a structure, we display in detail the contents of the structure. In the case of a pointer, the address is displayed, and when we make it '*pointer', the contents are displayed.

When we terminate gdb, we use 'q'.

Execution Example

In Fig. 1, we are in the process of debugging µT-Engine/M32104 using gdb. Let's now look through this in order.

bash-2.00$ ln -s ../../../tool/SunOS-sparc/binm32r-unknown-tmonitor-gdb gdb [1]
bash-2.00$ cat .gdbinit [2]
set remotebaud 38400
target tmon /dev/ttyb
directory .:../src
bash-2.00$ gterm -3 [3]
<< Gterm ver 2.10 : 030307 >>

PMC T-Kernel/M32104 Version 1.0.00
Copyright (C) 2002 by Personal Media Corporation

** MemoryMgr OK
** ClockMgr OK
** CardMgr OK
** ClockDrv OK
** SysDiskDrv OK
** RsDrv OK

<<<< IMS >>>>
[IMS]%
[IMS]%
[IMS]% #
TM> .load motor.mot
.. LO XS [motor.mot: 52638 bytes]
....*....*....*....*....*! (3509.2 bytes/sec)

Loaded: 04800000 -> 04805227 [4]
TM> g
[IMS]%
[IMS]% lodspg @0x04800000
TM> .q [5]

<< exit Gterm >>
bash-2.00$ gdb motor [6]
GNU gdb 011001
Copyright 1998 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions. This version of GDB is supported
for customers of Cygnus Solutions. Type "show warranty" for details.
This GDB was configured as "--host=sparc-sun-solaris2.5.1 --target=m32r-unknown-tmonitor"...
0x4802256 in tm_monitor () [7]
(gdb) b task00 [8]
Breakpoint 1 at 0x48002e4: file motor.c, line 115.
(gdb) c [9]
Continuing. 

Breakpoint 1, task00 () at motor.c:115 [10]
115             if ((tskid1 = CreateTask(task_motorl, DEFAULT_PRI)) < 0) {
(gdb) bt [11]
#0  task00 () at motor.c:115
(gdb) l  [12]
110
111
112     static  void   task00()
113     {
114
115             if ((tskid1 = CreateTask(task_motorl, DEFAULT_PRI)) < 0) {
116                     DERROR("motorl. CreateTask", tskid1);
117             }
118             if ((tskid2 = CreateTask(task_motorr, DEFAULT_PRI)) < 0) {
119                     DERROR("motorl. CreateTask", tskid1);
(gdb) [13]
120             }
121
122             for (;;) {
123                     tk_dly_tsk(1000);
124                     tk_sus_tsk(tskid1);
125                     tk_dly_tsk(1000);
126                     tk_rsm_tsk(tskid1);
127                     tk_dly_tsk(1000);
128                     tk_sus_tsk(tskid2);
129                     tk_dly_tsk(1000);
(gdb) l task_motorl  [14]
62              return;
63      }
64
65
66      static  void    task_motorl()
67      {
68              for (;;) {
69                      ctrl_motor(0xf0, 1);
70                      tk_dly_tsk(DELAY_MOTOR);
71                      ctrl_motor(0xf0, 5);
(gdb) l ctrl_motor
52              tk_exd_tsk();
53      }
54
55
56      static  void    ctrl_motor(UB and, UB or)
57      {
58              tk_dis_dsp();
59              status_motor = (status_motor & and)| or;
60              *PORT_MOTOR = status_motor;
61              tk_ena_dsp();
(gdb) b 59  [15]
Breakpoint 2 at 0x480015c: file motor.c, line 59.
(gdb) c
Continuing. 

Breakpoint 2, ctrl_motor (and=240 '', or=1 '\001') at motor.c:59  [16]
59              status_motor = (status_motor & and)| or;
(gdb) p/x and  [17]
$1 = 0xf0
(gdb) c
Continuing.

Breakpoint 2, ctrl_motor (and=15 '\017', or=16 '\020') at motor.c:59
59              status_motor = (status_motor & and)| or;
(gdb)  [18]
Continuing.

Breakpoint 2, ctrl_motor (and=240 '', or=5 '\005') at motor.c:59
59              status_motor = (status_motor & and)| or;
(gdb)
Continuing.

Breakpoint 2, ctrl_motor (and=15 '\017', or=80 'P') at motor.c:59
59              status_motor = (status_motor & and)| or;
(gdb)
Continuing.

Breakpoint 2, ctrl_motor (and=240 '', or=4 '\004') at motor.c:59
59              status_motor = (status_motor & and)| or;
(gdb) bt  [19]
#0  ctrl_motor (and=240 '', or=4 '\004') at motor.c:59
#1  0x48001d4 in task_motorl () at motor.c:73
(gdb) c
Continuing.

Breakpoint 2, ctrl_motor (and=15 '\017', or=64 '@') at motor.c:59
59              status_motor = (status_motor & and)| or;
(gdb) bt  [20]
#0  ctrl_motor (and=15 '\017', or=64 '@') at motor.c:59
#1  0x480026c in task_motorr () at motor.c:96
(gdb) f  [21]
#0  ctrl_motor (and=15 '\017', or=64 '@') at motor.c:59
59              status_motor = (status_motor & and)| or;
(gdb) info f  [22]
Stack level 0, frame at 0x4030ff4:
pc = 0x480015c in ctrl_motor (motor.c:59); saved pc 0x480026c
called by frame at 0x4031000
source language c.
Arglist at 0x4030ff4, args: and=15 '\017', or=64 '@'
Locals at 0x4030ff4, Previous frame's sp is 0x0
Saved registers:
fp at 0x4030ffc, lr at 0x4030ff8
(gdb) q  [23]
The program is running.  Exit anyway? (y or n) y
bash-2.00$
bash-2.00$ gterm -3
<< Gterm ver 2.10 : 030307 >>

TM> g  [24]
SYSPRG @04800000 [0]
[IMS]%

Figure 1. Appearance of gdb in operation

[1] In gdb, because the path is divided into a host side model and a target side model, here we create a symbolic link to m32r-unknown-tmonitor-gdb.

[2] In the execution of gdb, .gdbinit is also necessary. We create .gdbinit with these contents.

[3] We start up gterm.

[4] This is the point at which we have loaded at a fixed address the debugging target program. On this occasion, we have used a T-Kernel application, but it is also possible to debug even a process application with gdb.

[5] When we execute the program, control immediately moves to T-Monitor. Here, we terminate gterm by entering '.q'.

[6] We start up gdb,. As an argument, we specify the debugging target program, which is at the host side.

[7] Information on the location where it has stopped at present is displayed.

[8] We set the breakpoint to the function we want to debug.

[9] We resume execution.

[10] When it breaks, that line is displayed.

[11] We display stack frames. Because they are not being called by anything else, only one step is displayed. In addition, the present line numbers are also displayed.

[12] We display the front and back of the break location.

[13] When we enter return, the continuation is displayed.

[14] It is also possible to display by specifying the function name.

[15] By specifying a line number, we set a new breakpoint. At this time, the breakpoint in [8] is valid as it stands.

[16] Because this function accompanies an argument, information on the argument is also displayed at the time of the break.

[17] We try displaying the variable 'and' with a hexadecimal number.

[18] When we input enter here, the command 'c' immediately preceding it is re-executed.

[19] With 'bt', we display the original information of the function call. We learn that it is called from 'task_motorl()'.

[20] We learn that this is called from 'task_motorr()'.

[21] It is possible to display information on the present stack frame with the 'f' command.

[22] It is possible to obtain more detailed stack frame information with the 'info f' command.

[23] When the debugging work is finished, we terminate gdb with 'q' and then start up gterm.

[24] Because T-Engine is in a state in which there has been a break and control has moved to T-Monitor, we resume execution with the 'g' command.

References

GDB debaggingu nyuumon [A GDB debugging primer], ASCII

debagga-no riron to jissoo [The theory and implementation of a debugger], ASCII

____________________

Notes

[1] We also consider insufficiencies in processing capability, such as the speed being slow or the capacity being low, as a type of disorder here.

[2] Besides these, there are ""disorders of conditions and the like" in which software is created based on incorrect conditions. They are different from the disorders (1) ~ (4), and although they run according to design, they are unable to demonstrate merit. For example, a case corresponding to this is one in which because the condition that 10-place computation is required was not transmitted, you designed software that cannot compute up to more than eight places, and eight-place computation can be carried out normally. Conversely, there is also the case in which software was created on the condition that "it's fine if it can be used up to 1999," but that condition was consequently insufficient.

[3] In a case where there is a disorder in the compiler, there are occasions on which the symptoms come not to appear when we add code for the purpose of debugging. At this time, it is necessary to look at the machine code the compiler has output and investigate whether it is arranged so as carry out the same operation as the C language. In a case where a compiler disorder is the cause, the method in which we block optimization with a volatile declaration, etc., is effective.

[4] In a case where T-Monitor is used, it is possible to utilize a watchpoint, depending on the CPU. A watchpoint is a function that stops when the computer reads or writes a certain point. In the case of a disorder of the type where specific memory is being overwritten, there are cases in which the cause can be caught if we utilize a watchpoint.

[5] In the case of µT-Engine/M32104, there is no relocate function, and thus they are always made up as fixed addresses.

[6] Because gdb obtains information from the host side program, it's fine if there is an address match for the download program, and the debugging information itself is not necessary. Accordingly, it is possible to reduce the size of the download program when we delete the debugging information by carrying out strip processing.


The above article on T-Engine programming appeared on pages 25-31 in Vol. 81 of TRONWARE . It was translated and loaded onto this Web page with the permission of Personal Media Corporation.

Copyright © 2003 Personal Media Corporation

Copyright © 2003 Sakamura Laboratory, University Museum, University of Tokyo