Add a post on gdb scripting
This commit is contained in:
parent
c941fe6ac5
commit
601c65e809
2 changed files with 258 additions and 0 deletions
BIN
images/roundedcorners.jpg
Normal file
BIN
images/roundedcorners.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 120 KiB |
258
posts/2021-11-13-gdb-scripting.md
Normal file
258
posts/2021-11-13-gdb-scripting.md
Normal file
|
@ -0,0 +1,258 @@
|
|||
---
|
||||
author: Sanchayan Maity
|
||||
title: Automate debugging using GDB scripting
|
||||
tags: linux, gdb, gdb scripting, gstreamer
|
||||
---
|
||||
|
||||
Recently I was working on a GStreamer
|
||||
[plugin](https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/572)
|
||||
in Rust. The plugin basically rounds the corners of an incoming video,
|
||||
something akin to the `border-radius` property in CSS. Below is how it looks
|
||||
like when running on a video.
|
||||
|
||||
![](/images/roundedcorners.jpg)
|
||||
|
||||
The GStreamer pipeline for the same.
|
||||
|
||||
```bash
|
||||
gst-launch-1.0 filesrc location=~/Downloads/bunny.mp4 ! decodebin ! videoconvert ! video/x-raw,format=I420 ! roundedcorners border-radius-px=100 ! video/x-raw,format=A420 ! videoconvert ! gtksink
|
||||
```
|
||||
|
||||
This was my first time working on a video plugin in GStreamer. Had a lot to
|
||||
learn on how to use the `BaseTransform` class from GStreamer, among other
|
||||
things. Without getting into the GStreamer specific details here, I basically
|
||||
ran into a problem for which I needed to do some debugging for figuring out
|
||||
what was going on in the internals of GStreamer.
|
||||
|
||||
Now, while I never had problems using GDB from the command line, but, the way I
|
||||
was using it earlier was just not good enough. I would start the pipeline, then
|
||||
attach gdb to a running process, place breakpoints by manually typing out the
|
||||
whole thing and then start. For one off debugging sessions, where may be you
|
||||
just want to quickly inspect the backtrace from a crash or may be look into a
|
||||
deadlock condition where your code hanged, this could be fine. However, when
|
||||
you have to repeat this multiple times, do a source code change, compile and
|
||||
then check again, it becomes frustrating.
|
||||
|
||||
## GDB Dashboard
|
||||
|
||||
Looking for a better way, I first stumbled on
|
||||
[gdb-dashboard](https://github.com/cyrus-and/gdb-dashboard).
|
||||
This is quite useful since it can give the needed information without having to
|
||||
type anything. Using gdb
|
||||
[hooks](https://git.sr.ht/~sanchayanmaity/dotfiles/tree/master/item/gdb/.gdbinit.d/hooks),
|
||||
the dashboard can be triggered when appropriate. See the rest of my
|
||||
[gdb configuration](https://git.sr.ht/~sanchayanmaity/dotfiles/tree/master/item/gdb/.gdbinit.d)
|
||||
to get an idea. I use this in scenarios like where code is stuck due to a
|
||||
deadlock, I need to look at the backtrace of a crash or any such one off simple
|
||||
investigation.
|
||||
|
||||
## Construct breakpoint command in neovim & copy to clipboard
|
||||
|
||||
The next small improvement I did was more specific to my use of neovim. Am
|
||||
generally navigating source code using neovim, which would be opened in one
|
||||
kitty tab and gdb would be running in terminal in next tab or a split. Wanted
|
||||
to be able to quickly place a breakpoint without having to type anything out on
|
||||
the gdb prompt. Wrote a small piece of vimscript code which generates the gdb
|
||||
command, I would have to type on the gdb prompt to enable a breakpoint,
|
||||
considering the current line and file on which my cursor is at in the source
|
||||
when opened in neovim.
|
||||
|
||||
```vimscript
|
||||
function! CopyBpLocToClipboard() abort
|
||||
let linenumber = line(".")
|
||||
let filepath = expand("%")
|
||||
let breakpoint = "break " . filepath . ":" . linenumber
|
||||
silent execute "!wl-copy " . breakpoint
|
||||
endfunction
|
||||
|
||||
nnoremap <silent> <Leader>yb :<C-U>call CopyBpLocToClipboard()<CR>
|
||||
```
|
||||
|
||||
So I can hit the key binding above and a command like below will be copied to
|
||||
the clipboard which I can paste on gdb prompt.
|
||||
|
||||
```bash
|
||||
break subprojects/gst-plugins-base/gst-libs/gst/video/video-frame.c:104
|
||||
```
|
||||
|
||||
Nifty!!!
|
||||
|
||||
## GDB scripting
|
||||
|
||||
Now imagine a scenario where may be one wants to look at multiple places in the
|
||||
source code and when the program is running, inspect certain variables or just
|
||||
print out a back trace each time a specific code point is reached.
|
||||
|
||||
The dumb way to do this and the way which I was also doing it earlier, was to
|
||||
load the executable in gdb or attach to a running process, place a break point,
|
||||
run, inspect the local variables or print stack trace, place the next break
|
||||
point and repeat this whole process. Just time consuming and a waste of time.
|
||||
|
||||
GDB can completely automate the above process. Let's see how.
|
||||
|
||||
Below is the `.gdbinit` file I came up with for my problem. This is what is
|
||||
called a command file by gdb.
|
||||
|
||||
```bash
|
||||
set confirm off
|
||||
set breakpoint pending on
|
||||
set logging on
|
||||
set logging overwrite on
|
||||
set print pretty on
|
||||
set pagination off
|
||||
break subprojects/gst-plugins-base/gst-libs/gst/video/video-frame.c:104 if meta->n_planes == 4
|
||||
break subprojects/gst-plugins-base/gst-libs/gst/video/gstvideometa.c:228
|
||||
break subprojects/gstreamer/gst/gstbuffer.c:1410
|
||||
break subprojects/gst-plugins-base/gst-libs/gst/video/gstvideometa.c:231
|
||||
break subprojects/gst-plugins-base/gst-libs/gst/video/gstvideometa.c:237
|
||||
break subprojects/gst-plugins-base/gst-libs/gst/video/video-frame.c:136
|
||||
|
||||
commands 1
|
||||
print i
|
||||
print *frame
|
||||
enable 2
|
||||
enable 3
|
||||
enable 4
|
||||
enable 5
|
||||
enable 6
|
||||
continue
|
||||
end
|
||||
commands 2
|
||||
print offset
|
||||
continue
|
||||
end
|
||||
commands 3
|
||||
print offset
|
||||
print size
|
||||
continue
|
||||
end
|
||||
commands 4
|
||||
print *(GstBufferImpl *)buffer
|
||||
print idx
|
||||
print length
|
||||
print skip
|
||||
continue
|
||||
end
|
||||
commands 5
|
||||
disable 2
|
||||
disable 3
|
||||
disable 4
|
||||
print *(GstBufferImpl *)buffer
|
||||
print info->data
|
||||
print skip
|
||||
print *data
|
||||
continue
|
||||
end
|
||||
commands 6
|
||||
print *frame
|
||||
quit
|
||||
end
|
||||
|
||||
disable 2
|
||||
disable 3
|
||||
disable 4
|
||||
disable 5
|
||||
disable 6
|
||||
run
|
||||
```
|
||||
|
||||
The command I was using to debug my GStreamer plugin in this pipeline with gdb.
|
||||
|
||||
```bash
|
||||
gdb --nx -x .gdbinit --args env RUST_BACKTRACE=1 GST_DEBUG=3,basetransform:6 GST_PLUGIN_PATH=$GST_PLUGIN_PATH:~/GitSources/gst-plugins-rs/target/debug gst-launch-1.0 filesrc location=~/Downloads/bunny.mp4 ! decodebin ! videoconvert ! video/x-raw,format=I420 ! roundedcorners border-radius-px=100 ! video/x-raw,format=A420 ! videoconvert ! gtksink
|
||||
```
|
||||
|
||||
In the command above, the `-x` parameter tells gdb to use the command file. The
|
||||
`--nx` flag tells gdb to not read any any `.gdbinit` files in any directory, as
|
||||
I did not want to use `gdb-dashboard` for this. `--args` is how I tell gdb what
|
||||
to run, which is my GStreamer pipeline. You can see `gdb --help` for details on
|
||||
the flags.
|
||||
|
||||
Now, let's understand what the command file does. The ones below are just some
|
||||
settings we want gdb to use. Note that we have turned on logging and pretty
|
||||
printing.
|
||||
|
||||
```bash
|
||||
set confirm off
|
||||
set breakpoint pending on
|
||||
set logging on
|
||||
set logging overwrite on
|
||||
set print pretty on
|
||||
set pagination off
|
||||
```
|
||||
|
||||
Next we specify the breakpoints. We have six breakpoints. These are the
|
||||
locations which were of interest to me.
|
||||
|
||||
```bash
|
||||
break subprojects/gst-plugins-base/gst-libs/gst/video/video-frame.c:104 if meta->n_planes == 4
|
||||
break subprojects/gst-plugins-base/gst-libs/gst/video/gstvideometa.c:228
|
||||
break subprojects/gstreamer/gst/gstbuffer.c:1410
|
||||
break subprojects/gst-plugins-base/gst-libs/gst/video/gstvideometa.c:231
|
||||
break subprojects/gst-plugins-base/gst-libs/gst/video/gstvideometa.c:237
|
||||
break subprojects/gst-plugins-base/gst-libs/gst/video/video-frame.c:136
|
||||
```
|
||||
|
||||
Breakpoints can be enabled conditionally. The `if meta->n_planes == 4` implies
|
||||
to consider this breakpoint only when we get a video frame with 4 planes.
|
||||
|
||||
We can now tell gdb what should it do when each of the breakpoint above is hit.
|
||||
|
||||
```bash
|
||||
commands 1
|
||||
print i
|
||||
print *frame
|
||||
enable 2
|
||||
enable 3
|
||||
enable 4
|
||||
enable 5
|
||||
enable 6
|
||||
continue
|
||||
end
|
||||
```
|
||||
|
||||
`commands 1` implies these are the commands for gdb to execute when breakpoint
|
||||
1 is hit. When breakpoint 1 is hit, it will print the value of `i` and `frame`.
|
||||
The other breakpoints get enabled only after the first one is hit. This is
|
||||
because at the end of command file, we have
|
||||
|
||||
```bash
|
||||
disable 2
|
||||
disable 3
|
||||
disable 4
|
||||
disable 5
|
||||
disable 6
|
||||
```
|
||||
|
||||
which tells gdb to start with these breakpoints disabled. They will get enabled
|
||||
only when we hit breakpoint 1. The `continue` just tells gdb to continue, as we
|
||||
do not want to stop on hitting a breakpoint and only want to inspect in the end
|
||||
using gdb log.
|
||||
|
||||
Similarly we have for other breakpoints.
|
||||
|
||||
The `run` at the end tells gdb to start running immediately. In normal usage
|
||||
one would have to explicitly type `run` on the gdb prompt to make gdb start
|
||||
debugging.
|
||||
|
||||
Now, since we turned on logging after running the below on the terminal
|
||||
|
||||
```bash
|
||||
gdb --nx -x .gdbinit --args env RUST_BACKTRACE=1 GST_DEBUG=3,basetransform:6 GST_PLUGIN_PATH=$GST_PLUGIN_PATH:~/GitSources/gst-plugins-rs/target/debug gst-launch-1.0 filesrc location=~/Downloads/bunny.mp4 ! decodebin ! videoconvert ! video/x-raw,format=I420 ! roundedcorners border-radius-px=100 ! video/x-raw,format=A420 ! videoconvert ! gtksink
|
||||
```
|
||||
|
||||
gdb will run the pipeline, considering the command file it was passed and log
|
||||
whatever it was asked to log when each breakpoint is encountered. And now since
|
||||
we had turned on logging and pretty printing, gdb will nicely log everything in
|
||||
default `gdb.txt` file. You can see the exact log text file
|
||||
[here](https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/572#note_1107146),
|
||||
where I have attached the `gdbinit` and the other two log files.
|
||||
|
||||
Now, one can comfortably look at this log and see what is going on. Once the
|
||||
command file is written, the whole debugging process is completely automated.
|
||||
Run, sit back and then look at the logs.
|
||||
|
||||
Using gdb is now a breeze and hassle free experience. Being able to automate
|
||||
and log the debugging process like this, also means you could share your
|
||||
command file and someone else can replicate this. Wish I would have learned
|
||||
this sooner.
|
Loading…
Reference in a new issue