Programming Servo: The Debug Way
Debug Servo and Rust in a modern way
Servo is a huge project. I have counted the lines of code for you. There are almost a hundred thousand lines of code in the Servo project. To develop such a big project, knowing how to debug in a right way is very important, since you would like to find the bottleneck in a fast and efficient way.
In this article, I will teach you some tips to use GDB developing and debugging your Rust code in the Servo project.
So, How to Debug?
I assume you are not familiar with software developing, but you have some skills to write codes.
So when you want to know somethings in the box, you might add some lines, such as:
This is a simple method. Straightforward enough! It does help you to figure out what’s going on about the code.
However, it requires compiling each time when you want to know another variable’s value in the program. Besides, when your program crashes or causes memory leak, it is hard to trace the underlying problem.
The simple way is not helpful enough, which means you need a more powerful tool. It could be GDB in Linux, or LLDB in macOS. (On Windows platform, VS debugger is also a very strong tool, but not in discussion in this article.)
I will talk about how to use GDB then. LLDB is very similar to GDB. Basically, their commands are almost the same, so I would just introduce how to use GDB to debug the Rust and the Servo.
Introduction to GDB
“GDB, the GNU Project debugger, allows you to see what is going on ‘inside’ another program while it executes — or what another program was doing at the moment it crashed.” — -from gnu.org
In other words, GDB allows you to control the program running and to get more information from the inside of the code.
For example, you can stop the program at a certain line in a file, which is called a “breakpoint”. When the program stops at the breakpoint, you can print them to see the values of the variables in the breakpoint scope.
You can also back trace the code from the breakpoint. Backtrace means to print all functions called before the breakpoint. Sometimes a crash in the program is not because of the code where it crashed at. It might happens earlier, and passes an invalid parameter to cause a crash.
There are some other usage, and I will mention them in the following paragraph.
GDB the Rust
First of all, I would create a simple Hello World to demonstrate how to use GDB in a Rust project. You might have installed Rust and Cargo, Haven’t you?
Please follow the steps in “the Rust book” to create a Hello World. Make sure you can compile, run the code, and understand what Cargo are doing.
To create a project:
Then, let’s start!
In order to show how to use GDB, I have designed a sample code. Please copy the following code to your
To build this code, simply run
There would be an executable file
./target/debug/hello_cargo. The build is set as debug mode by default, and we can use the debug build to run with GDB. However, if it is a release build, you cannot run with GDB, since the debug information is lost.
To run the program with GDB:
That’s it. You would see the interface in GDB like this:
Now you can enter some commands in GDB!
There are many commands in GDB, but I would only introduce the most important part for you. For my personal case, I usually only use these commands as well.
As I mentioned before, a breakpoint allows you to stop the program at a certain position. There are two way to set the breakpoints.
b as the command to set a breakpoint.
In the first case, you can break at a function. (You would need to enter the whole path, like
mod::mod::function in a big project)
Or, you can add the file path with a line number to define where to stop.
let’s see whether we have set successfully.
But I just want one breakpoint, so I could delete the first one by
Now there is just one breakpoint. Let’s run the program and see what happens!
run to start.
As you can see, the program has stopped at the position that we want. Then, we could do something at this breakpoint!
If you are wondering what the upstream functions has been called before runnning to this breakpoint, you can use command
Now GDB tells you that it is
main.rs line 3 (#1), which is
let msg = make_hello_string(&name);, called
main.rs line 9 (#0), which is belong to
You might say, that’s really obvious, doesn’t it?
Yep! However, what if you are debugging a big open source project, such as the Servo project, and need to figure out the backtrace for a module? General speaking, there are about thirty to forty function calls before the breakpoint in a module. To find the backtrace by reading the code is super hard, but now we can use GDB to do it.
A frame is one of the program states in the backtrace. We can switch to a frame we want, and to check some information in that frame.
You can use
f to use this command.
After the previous step, we have already set a breakpoint at
src/main.rs:9, and there are two frames in the backtrace, which are
Now I want to check frame
#1, and see what is the value of
name, which is at
src/main.rs:2, in the scope of frame
frame let you enter the scope in frame
#1, and then you can use
call to call a function at that scope.
How about switch to frame
#0, and see what value is
After checking some information we want know, we might want the program to continue. Use
c to continue running the code. The program will keep running until it meets another breakpoints or finish the execution.
Since there is just one breakpoint in this example, the program will run to the end.
Once you stop at a breakpoint, you could use
step command to run code line by line.
Once you stop at a breakpoint, you could use
down to switch frames instead of using
frame <number> directly.
If the program is running(you have commanded
run already), and you can enter
Ctrl+C to interrupt GDB, then the program will break right now. The process will paused at where it just run to. And the point where it stopped by the interrupt is a manual breakpoint for just once. You can add some other commands here, and once you have done, you can enter
c to continue the process.
Then, you can call some commands, such as
There are more commands powered by GDB. You can check the document for more information.
Almost there. Let’s go to debug the Servo project. I assume you are able to compile Servo.
To build in debug mode:
Once the build is done, and we want to debug Servo:
You can debug Servo now!
The concept in this article can be applied on not only Rust projects but also C++ projects. If you want to debug Firefox or Chromium, you can use the same way to debug them. I will not discuss some detail such as how to apply a complicated setting for GDB, so you might need to search for other advanced articles to learn more skills.
GDB is not a must for developers, but it indeed powers hackers. There are some contributors of Servo who are front-end developers. One of them is my best friend, and he has already contributed many commits for Servo. As a front-end developer, he never use GDB before. However, he gets some troubles while developing Servo recently, I think if he knows how to use GDB, it would be easier to figure out the reasons why there are some weird behaviors by the program.
I’m writing this article for him and all the developers who just become one of the Servo community. I hope this article is helpful for all of you. Have a nice hacking day! :)