Master the GDB debugging tool and easily troubleshoot bugs!

1. What is GDB

gdb is the abbreviation of GNU debugger, which is a programming debugging tool.

  • GDB official website: https://www.gnu.org/software/gdb/
  • Applicable programming languages ​​for GDB: Ada / C / C++ / objective-c / Pascal etc.
  • How GDB works: local debugging and remote debugging.

The latest version of the current release is 8.0, and GDB can run on Linux and Windows operating systems.

1.1 Install and start GDB

  1. gdb -v checks whether the installation is successful, if not, install it (you must ensure that the compiler is installed, such as gcc).

  2. start gdb

    1. gdb test_file.exe to start gdb debugging, that is, directly specify the name of the executable file to be debugged

    2. Enter gdb directly to start, and use the command file test_file.exe to specify the file name after entering gdb

    3. If the target execution file requires input and output parameters (such as argv[] receiving parameters), the parameters can be specified in three ways:

      1. When starting gdb, gdb --args text_file.exe
      2. After entering gdb, run set args param_1
      3. After entering gdb debugging, run param_1 or start para_1

1.2 Functions of gdb

  • Start the program, you can run the program as you like according to the user-defined requirements.
  • Allows the program being debugged to stop at the debugging breakpoint specified by the user (the breakpoint can be a conditional expression).
  • When the program stops, you can check what happened in the program at that time. For example, you can print the value of a variable.
  • Dynamically change the execution environment of the variable program.

1.3 The use of gdb

run the program

run(r)运行程序,如果要加参数,则是run arg1 arg2 ... 

view source code

list(l):查看最近十行源码
list fun:查看fun函数源代码
list file:fun:查看flie文件中的fun函数源代码

Setting Breakpoints and Watching Breakpoints

break 行号/fun设置断点。
break file:行号/fun设置断点。
break if<condition>:条件成立时程序停住。
info break(缩写:i b):查看断点。
watch expr:一旦expr值发生改变,程序停住。
delete n:删除断点。

single step debugging

continue(c):运行至下一个断点。
step(s):单步跟踪,进入函数,类似于VC中的step in。
next(n):单步跟踪,不进入函数,类似于VC中的step out。
finish:运行程序,知道当前函数完成返回,并打印函数返回时的堆栈地址和返回值及参数值等信息。
until:当厌倦了在一个循环体内单步跟踪时,这个命令可以运行程序知道退出循环体。

View runtime data

print(p):查看运行时的变量以及表达式。
ptype:查看类型。
print array:打印数组所有元素。
print *array@len:查看动态内存。len是查看数组array的元素个数。
print x=5:改变运行时数据。

1.4 Program errors

  • Compilation error: When writing the program, the language specification is not complied with, resulting in a compilation error. For example: grammatical errors.
  • Runtime error: The compiler cannot detect this kind of error, but it may cause the program to crash at runtime. For example: memory address illegal access.
  • Logical errors: Compiling and running is fine, but the program doesn't do what we expect it to do.

1.5gdb debug segment fault

What is a segfault? Segmentation fault is an error caused by accessing an illegal address.

  • Access the system data area, especially write data to the memory address protected by the system. For example: access the address whose address is 0.
  • Memory out of bounds (array out of bounds, variable type inconsistency, etc.) accesses a memory area that does not belong to the current program.

You can run the program directly when debugging a segment fault with gdb. When the program crashes, gdb will print the running information, for example: after receiving the SIGSEGV signal, you can use the command to print the stack backtrace information, and then modify the program according to the error code of the program bt.

1.6.core file debugging

6.1 core file

When a program crashes, a file is generally generated called a corefile. The core file records the memory image when the program crashes, and adds debugging information. The core file generation process is called core dump(核心已转储). The system does not generate this file by default.

6.2 Set to generate core file

  • ulimit -c: Check the core-dump status.
  • ulimit -c xxxx: Set the size of the core file.
  • ulimit -c unlimited: Unlimited size of the core file.

6.3 gdb debugging core file

After ulimit -c xxxxsetting , run the program again and a segfault occurs. At this time, a corefile will be generated, use gdb corethe debug core file, and use btthe command to print the stack traceback information.

Two, GDB common commands

  • The following uses test_file.c as the name of the source program example, test_file.exe as the name of the executable file example, and param_1 as the name of the parameter example.
  • (gdb) means running in gdb debug mode
  • Generally, there are two commonly used methods, that is, breakpoint debugging and single-step debugging .
  • list(l): list source code
  • quit(q): exit gdb debugging mode
  • After entering gdb, enter help to view the instructions for all commands

2.1 View source code

list [function name] [number of lines]

2.2 Breakpoint debugging

(1) Set a breakpoint:

  • a. break + [source code line number][source code function name][memory address]
  • b. break ... if condition ... can be any of the above parameters, and condition is a condition. For example, in the loop body, you can set break ... if i = 100 to set the number of loops

delete breakpoint

(gdb) clear location: The parameter location is usually the line number of a certain line of code or a specific function name. When the location parameter is the function name of a function, it means to delete all the breakpoints at the entry of the function.

(gdb) delete [breakpoints] [num] : The breakpoints parameter is optional, and the num parameter is the number of the specified breakpoint, which can be delete to delete a certain breakpoint, not all.

disable breakpoint

**disable [breakpoints] [num…]: **breakpoints parameter is optional; num… means that there can be multiple parameters, and each parameter is the number of the breakpoint to be disabled. If num... is specified, the disable command will disable the breakpoint with the specified number; otherwise, if num... is not set, disable will disable all the breakpoints in the current program.

activate breakpoint

  1. enable [breakpoints] [num…] activate multiple breakpoints specified with num… parameters, if num… is not set, it means activate all disabled breakpoints
  2. enable [breakpoints] once num… Temporarily activate multiple breakpoints numbered by num…, but the breakpoint can only be used once, and then it will automatically return to the disabled state
  3. enable [breakpoints] count num… Temporarily activate multiple breakpoints numbered by num…, breakpoints can be used count times, and then enter the disabled state
  4. enable [breakpoints] delete num... Activate multiple breakpoints numbered num..., but the breakpoints can only be used once, and then they will be permanently deleted.

break(b): It is a common breakpoint, and there are two forms of breakpoints

(gdb) break location // b location, location represents the location of the break point

img

(gdb) break … if cond // b … if cond, which means that if the condition of cond is true, the break point will be at “…”

By using the condition command to set conditional expressions for different types of breakpoints, only when the conditional expressions are true (the value is True), the corresponding breakpoint will be triggered and the program will be suspended.

tbreak : The tbreak command can be seen as another version of the break command. The usage and functions of the tbreak and break commands are very similar. The only difference is that the breakpoint set by the tbreak command will only be used once, even after the program is suspended, the breakpoint will disappear automatically.

rbreak : Different from the break and tbreak commands, the rbreak command acts on functions in C and C++ programs, and it will break at the beginning of the specified function.

  • (gdb) tbreak regex

    • regex represents a regular expression that breaks at the beginning of the matched function
  • The breakpoint set by the tbreak command has the same effect as that of the break command, it will always exist and will not disappear automatically.

watch : This command hits an observation breakpoint, which can monitor the value of a variable or expression. Only when the value of the monitored variable (expression) changes, the program will stop running.

  • (gdb) watch cond

    • cond represents the variable or expression to be monitored

rwatch command: As long as there is an operation to read the value of the target variable (expression) in the program, the program will stop running;

awatch command: As long as there is an operation to read the value of the target variable (expression) or change the value in the program, the program will stop running.

catch : The function of catching a breakpoint is to monitor the occurrence of a certain event in the program, such as when a certain exception occurs in the program, when a dynamic library is loaded, etc. Once the target time occurs, the program stops executing.

(2) Observe the breakpoint:

  • a. watch + [variable] [expression] Stop the program when the value of the variable or expression changes.
  • b. rwatch + [variable] [expression] When the variable or expression is read, stop the program.
  • c, awatch + [variable] [expression] When the variable or expression is read or written, stop the program.

(3) Set the capture point:

catch + event When the event occurs, stop the program.

event can be the following:

  • a, throw An exception thrown by C++. (throw is a keyword)
  • b. catch An exception caught by C++. (catch is a keyword)
  • c. When exec calls the system call exec. (exec is a keyword, currently this function is only available under HP-UX)
  • d. When fork calls the system to call fork. (fork is a keyword, currently this function is only available under HP-UX)
  • e. When vfork calls the system to call vfork. (vfork is a keyword, currently this function is only available under HP-UX)
  • f, load or load When loading a shared library (dynamic link library). (load is a keyword, currently this function is only available under HP-UX)
  • g, unload or unload when unloading a shared library (dynamic link library). (unload is a keyword, currently this function is only available under HP-UX)

(4) Capture signal:

handle + [argu] + signals

Signals: It is a signal defined by Linux/Unix. SIGINT means an interrupt character signal, that is, the signal of Ctrl+C, SIGBUS means a signal of a hardware failure; SIGCHLD means a signal for changing the status of a child process; SIGKILL means a signal for terminating the program, and so on.

argu:

  • nostop When the program being debugged receives a signal, GDB will not stop the program's execution, but will print a message to tell you that it received such a signal.
  • stop GDB stops your program when the program being debugged receives a signal.
  • print GDB displays a message when the program being debugged receives a signal.
  • noprint When the program being debugged receives a signal, GDB will not tell you that the signal was received.
  • pass or noignore GDB does not process signals when the program being debugged receives them. This means that GDB will hand over this signal to the program being debugged for processing.
  • nopass or ignore When the debugged program receives a signal, GDB will not let the debugged program handle the signal.

(5) Thread interruption:

break [linespec] thread [threadno] [if …]

linespec The line number of the source code where the breakpoint is set. For example: test.c:12 means that the file sets a breakpoint for line 12 in test.c.

threadno The ID of the thread. It is allocated by GDB, and you can view the thread information of the running program by entering info threads.

if … Set a break condition.

View information:

(1) View data:

print variable view variables

print *array@len View the array (array is the array pointer, len is the required data length)

The output format can be set by adding parameters:

/ 按十六进制格式显示变量。
/d 按十进制格式显示变量。
/u 按十六进制格式显示无符号整型。
/o 按八进制格式显示变量。
/t 按二进制格式显示变量。 
/a 按十六进制格式显示变量。
/c 按字符格式显示变量。
/f 按浮点数格式显示变量。

(2) View memory

examine /nfu + memory address (pointer variable)

  • n indicates the display memory length
  • f indicates the output format (see above)
  • u indicates the number of bytes specified (b single byte; h double byte; w four byte; g eight byte; the default is four byte)
  如:x /10cw pFilePath  (pFilePath为一个字符串指针,指针占4字节)
     x 为examine命令的简写。

(3) View stack information

backtrace [-n][n]

  • n indicates that only the stack information of the n layer on the top of the stack is printed.
  • -n means to print only the stack information of n layers above the bottom of the stack.
  • Without parameters, it means to print all stack information.

2.3 Single-step debugging

run®

continue©

next(n)

  • Command format: (gdb) next count: count indicates how many lines of code to execute in a single step, and the default is 1 line
  • Its biggest feature is that when a statement containing a calling function is encountered, no matter how many lines of code are contained inside the function, the next instruction will be executed in one step. That is to say, for the function called, the next command will only treat it as a line of code

step(s)

  • (gdb) step count: The parameter count indicates the number of lines executed at one time, and the default is 1 line.
  • Normally, the step command and the next command have the same function, and both execute the program step by step. The difference is that when the line of code executed by the step command contains a function, it will enter the function and stop execution at the first line of code of the function.

until(u)

  • (gdb) until: The until command without parameters can make the GDB debugger quickly run through the current loop body, and run until the loop body stops. Note that the until command does not play this role under any circumstances, only when the execution reaches the end of the loop body (the last line of code), the until command will have this effect; otherwise, the until command has the same function as the next command, just execute the program step by step

(gdb) until location: The parameter location is the line number of a certain line of code

View the value of the variable

print§

  • p num_1: The parameter num_1 is used to refer to the target variable or expression to be viewed or modified
  • Its function is to output or modify the value of the specified variable or expression in the process of GDB debugging the program

isplay

  • (gdb) display expr
  • (gdb) display/fmt expr
  • expr indicates the target variable or expression to be viewed; the parameter fmt is used to specify the format of the output variable or expression

img

  • (gdb) undisplay num…
  • (gdb) delete display num…
  • The parameter num... indicates the number of the target variable or expression, and the number of numbers can be multiple
  • (gdb) disable display num…
  • Disable automatic display of active variables or expressions in the list
  • (gdb) enable display num…
  • You can also activate a variable or expression that is currently disabled
  • Like the print command, the display command is also used during the debugging phase to view the value of a variable or expression
  • The difference between them is that when using the display command to view the value of a variable or expression, whenever the program pauses (such as single-step execution), the GDB debugger will automatically print it out for us, while the print command will not

GDB handle Command: Signal Handling

→(gdb) handle signal mode Among them, the signal parameter indicates the target signal to be set, which is usually the full name (SIGINT) or abbreviation (the part after removing 'SIG', such as INT) of a certain signal; if you want to specify all Signals can be represented by all.

The mode parameter is used to specify the way GDB handles the target information, and its value can be as follows:

  • ostop: When the signal occurs, GDB will not suspend the program, it can continue to execute, but will print a prompt message telling us that the signal has occurred;
  • stop: When the signal occurs, GDB will suspend program execution.
  • noprint: GDB will not print out any prompt information when the signal occurs;
  • print: When a signal occurs, GDB will print out the necessary prompt information;
  • nopass (or ignore): While GDB captures the target signal, the program is not allowed to process the signal by itself;
  • pass (or noignore): While GDB debugging captures the target signal, it also allows the program to automatically handle the signal.

In gdb mode, you can view the information of different signals through info signals or info signals <signal_name> (for example, info signals SIGINT).

GDB frame and backtrace commands: view stack information

(gdb) frame spec This command can select the stack frame specified by the spec parameter as the current stack frame. There are three commonly used methods for specifying the value of the spec parameter:

  1. Specified by the number of the stack frame. 0 is the stack frame number corresponding to the currently called function, and the function corresponding to the stack frame with the largest number is usually the main() main function;
  2. With the help of the address specification of the stack frame. The address of the stack frame can be seen in the information printed by the info frame command (will be discussed later);
  3. Specified by the function name of the function. Note that if it is a similar recursive function that corresponds to multiple stack frames, the stack frame with the smallest number is specified by this method.

(gdb) info frame We can view the information stored in the current stack frame

This command will print out the following information of the current stack frame in sequence:

  • The number of the current stack frame and the address of the stack frame;
  • The storage address of the function corresponding to the current stack frame, and the address of the code storage when the function is called
  • The caller of the current function, the address of the corresponding stack frame;
  • The programming language used to write this stack frame;
  • The storage address and value of the function parameter;
  • The storage address of the local variable in the function;
  • The register variables stored in the stack frame, such as the instruction register (represented by rip in the 64-bit environment, and eip in the 32-bit environment), stack base pointer register (represented by rbp in the 64-bit environment, and ebp in the 32-bit environment), etc.

In addition, you can also use info args the command to view the value of each parameter of the current function; use info locals the command to view the value of each local variable in the current function.

(gdb) backtrace [-full] [n] is used to print information of all stack frames in the current debugging environment

Among them, the parameters enclosed by [] are optional, and their meanings are:

  • n: an integer value, when it is a positive integer, it means to print the information of the innermost n stack frames; when n is a negative integer, it means to print the information of the outermost n stack frames;
  • -full: Print out the values ​​of local variables while printing the stack frame information.

GDB editing and searching source code

GDB edit command: edit files

  • (gdb) edit [location]

  • (gdb) edit [filename] : [location]

    • location represents the location in the program. This command indicates to activate the specified location of the file, and then edit it.
    • If you encounter an error "bash: /bin/ex: No such file or directory", because the default editor of GDB is ex, you need to specify the editor, such as export EDITOR=/usr/bin/vim or export EDITOR=/usr /bin/vi

GDB search command: search files

  • search

  • reverse-search

    • The command format of the first item means to search forward from the beginning of the current line, and the second item means to search backward from the beginning of the current line. Among them, regexp is a regular expression. A regular expression describes a string matching pattern, which can be used to check whether a string contains a certain substring, replace the matched substring, or extract a substring that meets a certain condition from a certain string. Many programming languages ​​support the use of regular expressions.

Three, GDB debugger usage

Generally speaking, GDB mainly helps you complete the following four functions:

1. Start your program, and you can run the program as you like according to your custom requirements.
2. Allow the program being debugged to stop at the breakpoint you specify. (The breakpoint can be a conditional expression)
3. When the program is stopped, you can check what happened in your program at this time.
4. Dynamically change the execution environment of your program.

From the above, GDB is no different from general debugging tools. It basically completes these functions, but in details, you will find that GDB is a powerful debugging tool. You may be more accustomed to graphical debugging tools, but sometimes, command-line debugging tools have functions that cannot be completed by graphical tools. Let's see them one by one.

A debug example:

源程序:tst.c

1 #include <stdio.h>
2
3 int func(int n)
4 {
5 int sum=0,i;
6 for(i=0; i<n; i++)
7 {
8 sum+=i;
9 }
10 return sum;
11 }
12
13
14 main()
15 {
16 int i;
17 long result = 0;
18 for(i=1; i<=100; i++)
19 {
20 result += i;
21 }
22
23 printf("result[1-100] = %d /n", result );
24 printf("result[1-250] = %d /n", func(250) );
25 }

Compile and generate executable files: (under Linux)

hchen/test> cc -g tst.c -o tst

Debug with GDB:

hchen/test> gdb tst <---------- 启动GDB
GNU gdb 5.1.1
Copyright 2002 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.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i386-SUSE-linux"...
(gdb) l <-------------------- l命令相当于list,从第一行开始例出原码。
1 #include <stdio.h>
2
3 int func(int n)
4 {
5 int sum=0,i;
6 for(i=0; i<n; i++)
7 {
8 sum+=i;
9 }
10 return sum;
(gdb) <-------------------- 直接回车表示,重复上一次命令
11 }
12
13
14 main()
15 {
16 int i;
17 long result = 0;
18 for(i=1; i<=100; i++)
19 {
20 result += i;
(gdb) break 16 <-------------------- 设置断点,在源程序第16行处。
Breakpoint 1 at 0x8048496: file tst.c, line 16.
(gdb) break func <-------------------- 设置断点,在函数func()入口处。
Breakpoint 2 at 0x8048456: file tst.c, line 5.
(gdb) info break <-------------------- 查看断点信息。
Num Type Disp Enb Address What
1 breakpoint keep y 0x08048496 in main at tst.c:16
2 breakpoint keep y 0x08048456 in func at tst.c:5
(gdb) r <--------------------- 运行程序,run命令简写
Starting program: /home/hchen/test/tst

Breakpoint 1, main () at tst.c:17 <---------- 在断点处停住。
17 long result = 0;
(gdb) n <--------------------- 单条语句执行,next命令简写。
18 for(i=1; i<=100; i++)
(gdb) n
20 result += i;
(gdb) n
18 for(i=1; i<=100; i++)
(gdb) n
20 result += i;
(gdb) c <--------------------- 继续运行程序,continue命令简写。
Continuing.
result[1-100] = 5050 <----------程序输出。

Breakpoint 2, func (n=250) at tst.c:5
5 int sum=0,i;
(gdb) n
6 for(i=1; i<=n; i++)
(gdb) p i <--------------------- 打印变量i的值,print命令简写。
$1 = 134513808
(gdb) n
8 sum+=i;
(gdb) n
6 for(i=1; i<=n; i++)
(gdb) p sum
$2 = 1
(gdb) n
8 sum+=i;
(gdb) p i
$3 = 2
(gdb) n
6 for(i=1; i<=n; i++)
(gdb) p sum
$4 = 3
(gdb) bt <--------------------- 查看函数堆栈。
#0 func (n=250) at tst.c:5
#1 0x080484e4 in main () at tst.c:24
#2 0x400409ed in __libc_start_main () from /lib/libc.so.6
(gdb) finish <--------------------- 退出函数。
Run till exit from #0 func (n=250) at tst.c:5
0x080484e4 in main () at tst.c:24
24 printf("result[1-250] = %d /n", func(250) );
Value returned is $6 = 31375
(gdb) c <--------------------- 继续运行。
Continuing.
result[1-250] = 31375 <----------程序输出。

Program exited with code 027. <--------程序退出,调试结束。
(gdb) q <--------------------- 退出gdb。
hchen/test>

Well, with the above perceptual knowledge, let us get to know gdb systematically.

Basic gdb commands:

GDB常用命令	格式	含义	简写
list	List [开始,结束]	列出文件的代码清单	l
prit	Print 变量名	打印变量内容	p
break	Break [行号或函数名]	设置断点	b
continue	Continue [开始,结束]	继续运行	c
info	Info 变量名	列出信息	i
next	Next	下一行	n
step	Step	进入函数(步入)	S
display	Display 变量名	显示参数	 
file	File 文件名(可以是绝对路径和相对路径)	加载文件	 
run	Run args	运行程序	r

Four, GDB actual combat

Here is a practical example using the above command:

[[email protected] bufbomb]# gdb bufbomb 
GNU gdb (GDB) Red Hat Enterprise Linux (7.2-75.el6)
Copyright (C) 2010 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-RedHat-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /root/Temp/bufbomb/bufbomb...done.
(gdb) b getbuf
Breakpoint 1 at 0x8048ad6
(gdb) run -t cdai
Starting program: /root/Temp/bufbomb/bufbomb -t cdai
Team: cdai
Cookie: 0x5e5ee04e

Breakpoint 1, 0x08048ad6 in getbuf ()
Missing separate debuginfos, use: debuginfo-install glibc-2.12-1.149.el6_6.4.i686

(gdb) bt
#0  0x08048ad6 in getbuf ()
#1  0x08048db2 in test ()
#2  0x08049085 in launch ()
#3  0x08049257 in main ()
(gdb) info frame 0
Stack frame at 0xffffb540:
 eip = 0x8048ad6 in getbuf; saved eip 0x8048db2
 called by frame at 0xffffb560
 Arglist at 0xffffb538, args: 
 Locals at 0xffffb538, Previous frame's sp is 0xffffb540
 Saved registers:
  ebp at 0xffffb538, eip at 0xffffb53c
(gdb) info registers
eax            0xc      12
ecx            0xffffb548       -19128
edx            0xc8c340 13157184
ebx            0x0      0
esp            0xffffb510       0xffffb510
ebp            0xffffb538       0xffffb538
esi            0x804b018        134524952
edi            0xffffffff       -1
eip            0x8048ad6        0x8048ad6 <getbuf+6>
eflags         0x282    [ SF IF ]
cs             0x23     35
ss             0x2b     43
ds             0x2b     43
es             0x2b     43
fs             0x0      0
gs             0x63     99
(gdb) x/10x $sp
0xffffb510:     0xf7ffc6b0      0x00000001      0x00000001      0xffffb564
0xffffb520:     0x08048448      0x0804a12c      0xffffb548      0x00c8aff4
0xffffb530:     0x0804b018      0xffffffff

(gdb) si
0x08048ad9 in getbuf ()
(gdb) si
0x08048adc in getbuf ()
(gdb) si
0x080489c0 in Gets ()
(gdb) n
Single stepping until exit from function Gets,
which has no line number information.
Type string:123
0x08048ae1 in getbuf ()
(gdb) si
0x08048ae2 in getbuf ()
(gdb) c
Continuing.
Dud: getbuf returned 0x1
Better luck next time

Program exited normally.
(gdb) quit

4.1 Reverse debugging

The Reversal Debugging function was added after GDB 7.0. Specifically, for example, I set breakpoints on getbuf() and main(), and when the program is started, it will stop at the breakpoint of the main() function. At this time, after typing record, continue to the next breakpoint getbuf(), and GDB will record the runtime information from main() to getbuf(). Now use rn to reverse debug from getbuf() to main(). Just like in "X-Men: Days of Future Past", it's amazing!

This method is suitable for finding the code that caused the bug in reverse from the bug, and the practicality varies with the situation. Of course, it also has limitations. When external conditions such as program I/O output change, GDB cannot "reverse".

[[email protected] bufbomb]# gdb bufbomb 
GNU gdb (GDB) Red Hat Enterprise Linux (7.2-75.el6)
Copyright (C) 2010 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-redhat-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /root/Temp/bufbomb/bufbomb...done.

(gdb) b getbuf
Breakpoint 1 at 0x8048ad6
(gdb) b main
Breakpoint 2 at 0x80490c6

(gdb) run -t cdai
The program being debugged has been started already.
Start it from the beginning? (y or n) y

Starting program: /root/Temp/bufbomb/bufbomb -t cdai

Breakpoint 2, 0x080490c6 in main ()
(gdb) record
(gdb) c
Continuing.
Team: cdai
Cookie: 0x5e5ee04e

Breakpoint 1, 0x08048ad6 in getbuf ()

(gdb) rn
Single stepping until exit from function getbuf,
which has no line number information.
0x08048dad in test ()
(gdb) rn
Single stepping until exit from function test,
which has no line number information.
0x08049080 in launch ()
(gdb) rn
Single stepping until exit from function launch,
which has no line number information.
0x08049252 in main ()

4.2 VSCode+GDB+Qemu debugging ARM64 linux kernel

The linux kernel is a very complicated system, and it is difficult for beginners to get started. If there is a convenient debugging environment, the learning efficiency can be improved by at least 5-10 times.

In order to learn the linux kernel, there are usually these two needs:

  1. Can get rid of the hardware, conveniently compile and run linux
  2. You can use graphical tools to debug linux

The author uses VSCode+GDB+Qemu to complete these two requirements:

  • qemu is used as a virtual machine to start linux.
  • VSCode+GDB is used as a debugging tool for graphical DEBUG.

The final effect is roughly as follows:

qemu running interface:

img

vscode debugging interface:

img

The following will introduce step by step how to build the above environment. All operations in this article are performed on a Vmware Ubuntu16 virtual machine.

Install the compilation toolchain

Since Ubuntu is an X86 architecture, in order to compile arm64 files, you need to install a cross-compilation toolchain

sudo apt-get install gcc-aarch64-linux-gnu
sudo apt-get install libncurses5-dev  build-essential git bison flex libssl-dev

make root file system

The startup of linux needs to cooperate with the root file system. Here we use busybox to make a simple root file system

compile busybox

wget  https://busybox.net/downloads/busybox-1.33.1.tar.bz2
tar -xjf busybox-1.33.1.tar.bz2
cd busybox-1.33.1

Turn on the static library compilation option

make menuconfig
Settings --->
 [*] Build static binary (no shared libs)

Specify compilation tools

export ARCH=arm64
export CROSS_COMPILE=aarch64-linux-gnu-

compile

make
make install

The compilation is complete, and the _install directory is generated under the busybox directory

custom file system

In order for the init process to start normally, some additional configuration is required

Add etc, dev and lib directories to the root directory

# bryant @ ubuntu in ~/Downloads/busybox-1.33.1/_install [1:02:17]
$ mkdir etc dev lib
# bryant @ ubuntu in ~/Downloads/busybox-1.33.1/_install [1:02:17]
$ ls
bin  dev  etc  lib  linuxrc  sbin  usr

Create files separately in etc:

# bryant @ ubuntu in ~/Downloads/busybox-1.33.1/_install/etc [1:06:13]
$ cat profile
#!/bin/sh
export HOSTNAME=bryant
export USER=root
export HOME=/home
export PS1="[$USER@$HOSTNAME \W]\# "
PATH=/bin:/sbin:/usr/bin:/usr/sbin
LD_LIBRARY_PATH=/lib:/usr/lib:$LD_LIBRARY_PATH
export PATH LD_LIBRARY_PATH

# bryant @ ubuntu in ~/Downloads/busybox-1.33.1/_install/etc [1:06:16]
$ cat inittab
::sysinit:/etc/init.d/rcS
::respawn:-/bin/sh
::askfirst:-/bin/sh
::ctrlaltdel:/bin/umount -a -r

# bryant @ ubuntu in ~/Downloads/busybox-1.33.1/_install/etc [1:06:19]
$ cat fstab
#device  mount-point    type     options   dump   fsck order
proc /proc proc defaults 0 0
tmpfs /tmp tmpfs defaults 0 0
sysfs /sys sysfs defaults 0 0
tmpfs /dev tmpfs defaults 0 0
debugfs /sys/kernel/debug debugfs defaults 0 0
kmod_mount /mnt 9p trans=virtio 0 0

# bryant @ ubuntu in ~/Downloads/busybox-1.33.1/_install/etc [1:06:26]
$ ls init.d
rcS

# bryant @ ubuntu in ~/Downloads/busybox-1.33.1/_install/etc [1:06:30]
$ cat init.d/rcS
mkdir -p /sys
mkdir -p /tmp
mkdir -p /proc
mkdir -p /mnt
/bin/mount -a
mkdir -p /dev/pts
mount -t devpts devpts /dev/pts
echo /sbin/mdev > /proc/sys/kernel/hotplug
mdev -s

Here is a little explanation of these files:

  1. After busybox is started as linuxrc, it will read /etc/profile, which sets some environment variables and shell properties
  2. Mount the file system according to the mount information provided by /etc/fstab
  3. busybox will read sysinit from /etc/inittab and execute it, where sysinit points to /etc/init.d/rcS
  4. In /etc/init.d/rcS, the mdev -s command is very important, it will scan the /sys directory, find character devices and block devices, and mknod under /dev

dev directory:

# bryant @ ubuntu in ~/Downloads/busybox-1.33.1/_install/dev [1:17:36]
$ sudo mknod console c 5 1

This step is very important, without the console file, the user mode output cannot be printed to the serial port

lib directory: copy the lib library, and support dynamically compiled applications to run :

# bryant @ ubuntu in ~/Downloads/busybox-1.33.1/_install/lib [1:18:43]
$ cp /usr/aarch64-linux-gnu/lib/*.so*  -a .

Compile the kernel

Configure the kernel

The linux kernel source code can be downloaded directly from github.

Generate .config according to arch/arm64/configs/defconfig file

make defconfig ARCH=arm64

Add the following configuration to the .config file

CONFIG_DEBUG_INFO=y 
CONFIG_INITRAMFS_SOURCE="./root"
CONFIG_INITRAMFS_ROOT_UID=0
CONFIG_INITRAMFS_ROOT_GID=0

CONFIG_DEBUG_INFO is for debugging

CONFIG_INITRAMFS_SOURCE is to specify the location of the kernel ramdisk, so that the ramdisk will be directly compiled into the kernel image after specifying.

We cp the previously created root file system to the root directory:

# bryant @ ubuntu in ~/Downloads/linux-arm64 on git:main x [1:26:56]
$ cp -r ../busybox-1.33.1/_install root

execute compile

make ARCH=arm64 Image -j8  CROSS_COMPILE=aarch64-linux-gnu-

Specifying the target as Image here will only compile the kernel, not the modules, which will increase the compilation speed

start qemu

download qemu

It should be noted that it is best to compile qemu from the source code. The version of qemu installed directly with apt-get may be too low, which makes it impossible to start the arm64 kernel. The author is using the 4.2.1 version of qemu

apt-get install build-essential zlib1g-dev pkg-config libglib2.0-dev binutils-dev libboost-all-dev autoconf libtool libssl-dev libpixman-1-dev libpython-dev python-pip python-capstone virtualenv
wget https://download.qemu.org/qemu-4.2.1.tar.xz
tar xvJf qemu-4.2.1.tar.xz
cd qemu-4.2.1
./configure --target-list=x86_64-softmmu,x86_64-linux-user,arm-softmmu,arm-linux-user,aarch64-softmmu,aarch64-linux-user --enable-kvm
make 
sudo make install

After the compilation is complete, qemu is in the /usr/local/bin directory

$ /usr/local/bin/qemu-system-aarch64 --version
QEMU emulator version 4.2.1
Copyright (c) 2003-2019 Fabrice Bellard and the QEMU Project developers

start the linux kernel

/usr/local/bin/qemu-system-aarch64 -m 512M -smp 4 -cpu cortex-a57 -machine virt -kernel

Here are some explanations for the parameters:

  • -m 512MMemory is 512M
  • -smp 44 Nuclear
  • -cpu cortex-a57cpu is cortex-a57
  • -kernelkernel image file
  • -appendThe cmdline parameter passed to the kernel. Among them, rdinit specifies the init process; nokaslr prohibits the randomization of the kernel start address, which is very important, otherwise GDB debugging may have problems; console=ttyAMA0 specifies the serial port, without this step, you cannot see the output of linux;
  • -nographicdisable graphics output
  • -sListen to the gdb port, and the gdb program can be connected through the port 1234.

Here is an explanation of how console=ttyAMA0 works.

Viewing the linux source code shows that ttyAMA0 corresponds to AMBA_PL011this driver:

config SERIAL_AMBA_PL011_CONSOLE
    bool "Support for console on AMBA serial port"
    depends on SERIAL_AMBA_PL011=y
    select SERIAL_CORE_CONSOLE
    select SERIAL_EARLYCON
    help
      Say Y here if you wish to use an AMBA PrimeCell UART as the system
      console (the system console is the device which receives all kernel
      messages and warnings and which allows logins in single user mode).

      Even if you say Y here, the currently visible framebuffer console
      (/dev/tty0) will still be used as the system console by default, but
      you can alter that using a kernel command line option such as
      "console=ttyAMA0". (Try "man bootparam" or see the documentation of
      your boot loader (lilo or loadlin) about how to pass options to the
      kernel at boot time.)

AMBA_PL011 is a standard serial port device of arm, and the output of qemu is the simulated serial port.

In the source code file of qemu, you can also see the related files of PL011:

# bryant @ ubuntu in ~/Downloads/qemu-4.2.1 [1:46:54]
$ find . -name "*pl011*"
./hw/char/pl011.c

After successfully starting Linux, the serial port prints are as follows:

[    3.401567] usbcore: registered new interface driver usbhid
[    3.404445] usbhid: USB HID core driver
[    3.425030] NET: Registered protocol family 17
[    3.429743] 9pnet: Installing 9P2000 support
[    3.435439] Key type dns_resolver registered
[    3.440299] registered taskstats version 1
[    3.443685] Loading compiled-in X.509 certificates
[    3.461041] input: gpio-keys as /devices/platform/gpio-keys/input/input0
[    3.473163] ALSA device list:
[    3.474432]   No soundcards found.
[    3.485283] uart-pl011 9000000.pl011: no DMA platform data
[    3.541376] Freeing unused kernel memory: 10752K
[    3.545897] Run /linuxrc as init process
[    3.548390]   with arguments:
[    3.550279]     /linuxrc
[    3.551073]     nokaslr
[    3.552216]   with environment:
[    3.554396]     HOME=/
[    3.555898]     TERM=linux
[    3.985835] 9pnet_virtio: no channels available for device kmod_mount
mount: mounting kmod_mount on /mnt failed: No such file or directory
/etc/init.d/rcS: line 8: can't create /proc/sys/kernel/hotplug: nonexistent directory

Please press Enter to activate this console.
[root@bryant ]#
[root@bryant ]#

VSCode+GDB

The GDB function is integrated in vscode, we can use it to graphically debug the linux kernel

First we add vscode's gdb configuration file (.vscode/launch.json):

{
    // Use IntelliSense to learn about possible attributes.
    // Hover to view descriptions of existing attributes.
    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "name": "kernel debug",
            "type": "cppdbg",
            "request": "launch",
            "program": "${workspaceFolder}/vmlinux",
            "cwd": "${workspaceFolder}",
            "MIMode": "gdb",
            "miDebuggerPath":"/usr/bin/gdb-multiarch",
            "miDebuggerServerAddress": "localhost:1234"
        }
    ]
}

Here are some explanations for several key parameters:

  • program: symbol file for debugging
  • miDebuggerPath: The path of gdb. It should be noted here that since we are arm64 kernel, we need to use gdb-multiarch to debug
  • miDebuggerServerAddress: Peer address, qemu will use port 1234 by default

After the configuration is complete, you can directly start GDB and connect to the linux kernel

img

In vscode, you can set breakpoints for single-step debugging

img

img


Copyright statement: This article is an original article written by Zhihu blogger "Playing with the Linux Kernel". It follows the CC 4.0 BY-SA copyright agreement. For reprinting, please attach the original source link and this statement.
Original link: https://zhuanlan.zhihu.com/p/639365490

Guess you like

Origin blog.csdn.net/m0_50662680/article/details/131484284