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