Debugging Plug-ins
Eeek! The plug-in you’re working on has a bug in it! And the fix isn’t completely obvious, so you want to use a debugger to see what is going on. But hmm, how does one start a plug-in under a debugger if GIMP is the one who is starting the plug-in…
To address this issue, libgimp has some hooks controlled by the
GIMP_PLUGIN_DEBUG
environment variable at runtime.
GIMP_PLUGIN_DEBUG
lets you arrange that a plug-in suspends when it starts,
and then you can start a debugger and attach the debugger to the pid of the
plug-in.
GIMP_PLUGIN_DEBUG
also lets you arrange for log messages of levels WARNING
,
CRITICAL
, and ERROR
(from the plug-in, GIMP libraries, and GLib libraries)
to be fatal and generate a backtrace at that point (also called
a stack trace, similar to that generated in a debugger using the ‘bt’ command.)
When GIMP_PLUGIN_DEBUG
is not defined, depending on how other GLib environment
variables are defined, a plug-in may quietly execute past
warning and critical logged events, and only terminate on a ERROR logged event
(without a backtrace), or on a soft or hard fault such as a memory exception
(possibly producing a core dump that you can examine using a debugger.)
Format of GIMP_PLUGIN_DEBUG
GIMP_PLUGIN_DEBUG=name<,options>
“name” specifies the name of the plug-in that you wish to debug, or “all” to debug every plug-in. See below “Plug-in names”.
“options” is zero or more of the following options, separated by :’s
run: suspend the plug-in when its run func is called.
query: suspend the plug-in when its query func is called.
init: suspend the plug-in when its init func is called.
quit: suspend the plug-in when its quit func is called.
pid: just print the pid of the plug-in on run_proc.
fatal-warnings: similar to "gimp --g-fatal-warnings" on the command line,
but for the plugin process
fw: shorthand for above.
fatal-criticals: make CRITICAL level messages fatal (but not WARNING)
In the absence of an options string, only ERRORs are fatal and generate a backtrace according to stack-trace-mode
To use a debugger on a C-language plug-in
-
Ensure GIMP was built with debugging information (
gcc -g
) -
In a terminal, start GIMP with the environment variable
GIMP_PLUGIN_DEBUG
set ( e.g.GIMP_PLUGIN_DEBUG=blur,on gimp
) -
In another terminal, start a debugger (lldb, gdb, or other) and load the plug-in program into the debugger. Loading only loads the debug symbols. ( e.g.
>gdb blur
) -
Invoke the plug-in procedure in GIMP. GIMP will start the plug-in process, then suspend it and print the pid of the plug-in process to the terminal where you started GIMP.
-
In the debugger, attach to the pid of the plug-in process. (e.g.
gdb> attach <pid>
) Expect the debugger to say where the plug-in is suspended. -
In the debugger, set breakpoints (or examine memory, or step, etc.)
-
Windows: resume the plug-in process with
gimp-debug-resume.exe <pid>
Linux and Unix-like: the gdb
continue
command might resume the attached process. Possibly you will have to send a SIGCONT signal:kill -SIGCONT <pid>
-
In the debugger, enter “continue”. Expect the plug-in to resume under control of the debugger and pause at breakpoints. (e.g.
gdb> continue
)
Examples using a debugger
GIMP_PLUGIN_DEBUG=blur
When the blur plug-in's run func is called (the run phase),
GIMP suspends it and prints to the console:
(blur:9000): LibGimp-DEBUG: Waiting for debugger...
9000 is the pid of the new plug-in process. You can start your debugger,
attach to the plug-in, set breakpoints/watches/etc. and continue from there.
Using gdb command "continue" will resume the plugin.
Expect the plugin to execute until it hits a breakpoint or until a WARNING
or worse event is logged or until a hard fault such as a memory violation.
Then the debugger will be back in control.
GIMP_PLUGIN_DEBUG=blur,on
GIMP_PLUGIN_DEBUG=blur,run:fatal-warnings
Same effect as above.
GIMP_PLUGIN_DEBUG=blur,pid
Prints:
(blur:9000): LibGimp-DEBUG: Here I am!
This simply prints the pid but doesn't suspend the plug-in. It is a
convenience for a plug-in having a GUI. The GUI starts up and waits
for user input. When you attach, you will find the plug-in waiting
in the event loop.
GIMP_PLUGIN_DEBUG=blur,query
GIMP_PLUGIN_DEBUG=blur,init
GIMP_PLUGIN_DEBUG=blur,quit
Same effect as for "run", but instead libgimp suspends the plug-in before
one of a plugin's phases: query, init, run, quit
E.G. when it is queried or init'ed on GIMP startup.
To get a backtrace for a plug-in in any language
-
Ensure GIMP (and all libraries you want details for) were built with debugging information (
gcc -g
) -
In a terminal, start GIMP with the environment variable
GIMP_PLUGIN_DEBUG
set ( e.g.>GIMP_PLUGIN_DEBUG=all,fatal-criticals gimp
) Expect GIMP to start normally. -
In GIMP, do something that would invoke a plugin.
Whenever the specified plug-in processes generate log events of the specified levels or worse, libgimp will print or offer to print (depends on stack-trace-mode) a backtrace, and then terminate the plug-in. The GIMP app will usually continue to run.
For interpreted language plug-ins, the backtrace will include many frames
from the interpreter and modules such as PyGObject. Exceptions in the
interpreted language may print on their own and not generate log events
to be caught by GIMP_PLUGIN_DEBUG
. But log events from the interpreter
calling out (to LibGimp and GLib) can generate backtraces.
GIMP_PLUGIN_DEBUG
and stack-trace-mode
GIMP_PLUGIN_DEBUG=all,fatal-warning
only makes the machinery consider
generating a backtrace, for more log events. The ‘stack-trace-mode’ pertains.
The GIMP app on the command line takes a flag:
--stack-trace-mode [never, query, always]
When the GIMP app forks a plugin process, it passes that arg to the plugin, and the arg controls how a backtrace is printed.
The default is “query”, which means libgimp will ask you: “[E]xit [S]tacktrace [P]roceed” (similar to the GLib default handler for ERROR log events.)
“always” means libgimp prints a backtrace (and then the plugin terminates.)
“never” means libgimp does not print a backtrace, only a message. But for GIMP_PLUGIN_DEBUG=all,fatal-warning, the plugin terminates on the first WARNING.
Examples getting a backtrace on logged events
GIMP_PLUGIN_DEBUG=blur,fatal-warnings
The blur plug-in will run until the first logged WARNING or worse,
from the plug-in, GIMP libraries, or GLib libraries.
Then a backtrace can print to the console and the blur plug-in terminate.
Usually you will also see a message from the main GIMP app
saying the plugin aborted without returning a value.
GIMP_PLUGIN_DEBUG=all,fatal-criticals
Every plug-in (whether you invoked it from the GIMP GUI or it was called
by another plug-in) will run until the first logged CRITICAL or worse,
from the plug-in, GIMP libraries, or GLib libraries.
Then, as above, a backtrace can print etc.
GIMP_PLUGIN_DEBUG=all
As above, for all plugins, but only for a logged ERROR.
Quality of a backtrace
A detailed backtrace depends on:
- building GIMP and dependencies with debug info enabled
- having a debugger installed
When a debugger is not installed, a backtrace may lack details such as function names and line numbers.
More about logging levels
See https://developer.gnome.org/glib/stable/glib-Message-Logging.html
Level | Engendered by |
---|---|
WARNING | g_warning() |
CRITICAL | g_return_value_if_fail() or g_return_if_fail() |
ERROR | g_assert() or g_error() |
Use of logging levels is by convention, and libraries that GIMP uses may not follow conventions.
Generally speaking…
WARNINGs are common but don’t signify much. They might mean that your plug-in code does not understand, or is sloppy with, the GIMP API.
CRITICALs are rare but more significant. They usually mean that GIMP will
attempt to continue past an errant condition. The GLib function
g_return_value_if_fail()
is often used in GIMP code as a precondition,
required to succeed before a GIMP function executes its body,
the function returning early when the precondition fails.
ERRORs are usually dire. They always terminate a plug-in.
Plug-in names
A plug-in may register many PDB procedures. Use the plug-in name, not a
procedure-name, e.g. “file-psd” not “file-psd-save”. When a plug-in is “run”,
one of its PDB procedures is run, and all of its PDB procedures
are covered by a GIMP_PLUGIN_DEBUG
definition.
A name is usually the name of the executable file, including any suffix. Examples: “file-psd” or “foggify.py” or on some platforms “foo.exe.”
Usually an interpreted plug-in has a hashbang on the first line e.g. “#!/usr/bin/env python3” in the first line of foo.py. Then the file is executable, GIMP forks that file, and the Linux loader invokes the interpreter.
However, GIMP still allows a plug-in source
to lack a hashbang (and it is not technically “an executable”)
but then GIMP forks the interpreter, passing the script filename.
In this case, you must still use the name of the script file e.g. “foo.py”
in GIMP_PLUGIN_DEBUG
.
(Since forever, GIMP does not understand Python packages. GIMP only installs Python plugins from directories named like foo/foo.py. A directory that is a Python package (having an init.py file) will be read by GIMP at startup, but GIMP will only install one plugin from that directory, and only if the .py file is named like the directory.)
Finding plug-in source by name
A GIMP supported C language plug-in’s source should be in a similar-named directory in the GIMP repository. For example, “file-psd” is a directory (but there is no “file-psd.c”.)
An interpreted plug-in is installed in a directory of a similar name e.g. “plug-ins/foggify/foggify.py”. But in the GIMP repository, foggify.py does not live at foggify/foggify.py but at plug-ins/python/foggify.py .
Examples using other debug tools
Hmm, but what about memory debuggers such as valgrind or purify? For those you can set the following:
GIMP_PLUGIN_DEBUG_WRAP=name<,options>
This is similar to `GIMP_PLUGIN_DEBUG`. Only "query", "init", and "run"
are valid, and "on" defaults to simply "run"
GIMP_PLUGIN_DEBUG_WRAPPER=debugger
debugger refers to the debugger program, such as valgrind. You can
put command line options here too, they will be parsed like they do
in the shell.
Windows
When compiled with Windows, a plug-in process is halted by Windows functions.
It must be resumed externally by invoking gimp-debug-resume.exe <pid>
.
The pid of the running plug-in can be found by invoking gimp-debug-resume.exe
without parameters. It shows the pid of all running processes. Another option
for finding the pid is checking the terminal where GIMP is running, see below.
For this example we will use the screenshot plug-in and assume that you are using a CLANG profile of MSYS2. It can be either CLANGARM64 or CLANG64.
- Open three (3) CLANG terminals. In the first terminal start GIMP using its path in the prefix you set for your GIMP build and this command:
GIMP_PLUGIN_DEBUG=screenshot.exe,run $GIMP_PREFIX/bin/gimp-2.99.exe
- In the second terminal load lldb with the plug-in:
lldb $GIMP_PREFIX/lib/gimp/2.99/plug-ins/screenshot/screenshot.exe
-
In GIMP start your plug-in so that it’s run function will be called. You should see something in the first terminal like:
(screenshot.exe:8992): LibGimp-DEBUG: 16:44:50.894: Debugging (restart externally): 8992
. The number at the end is the pid of the plug-in. -
In the second terminal, attach it to the pid of the plug-in process:
(lldb) process attach --pid 8992
Note that (lldb) is the prompt of the debugger, not something that you need to type yourself. Expect the debugger to say where the plug-in is suspended.
- We now need to resume the plug-in with a tool from your build directory. In the third terminal type the following, but adjust the number to the pid that you see in your first terminal:
/home/username/gimp/_build/tools/gimp-debug-resume.exe 8992
- In the second terminal in lldb type
c
(continue). The plug-in should resume running until a breakpoint or crash happens…