The problem#
I was modifying a screensaver written using SDL and noticed that sometimes there were many instances of it left running, even after unlocking the screen. Another bug was causing the screensaver to use 100% CPU, resulting in it using up all of my processing power just for a simple screensaver.
The solution#
Make the program exit immediately when it receives a SIGTERM signal by including the function
void exitImmediately(int sig) {
abort();
}
and making the SIGTERM signal handler call it:
signal(SIGTERM, exitImmediately);
The details#
What's a screensaver?#
A screensaver for XScreenSaver is a program that draws to an X window. The input is managed by XScreenSaver. All the screensaver does is draw to the window it is given and stop when it is told to through a SIGTERM signal (the normal way to request that a program exits).
The actual drawing can be done through any API as long as it has a way to attach to an existing X window. In our case, the screensaver uses SDL.
Interrogating hung processes#
gdb
can attach to running processes:
$ gdb -p $(pidof noflipqlo)
...
Debugging symbols#
The
is skipping over a lot of initial loading messages from
...
gdb
. You may want to pay attention to the ones about reading symbols
for seeing which packages are missing debugging symbols:
Reading symbols from /lib/x86_64-linux-gnu/libgcc_s.so.1...(no debugging symbols found)...done.
Loaded symbols for /lib/x86_64-linux-gnu/libgcc_s.so.1
Reading symbols from /lib/x86_64-linux-gnu/libc.so.6...Reading symbols from /usr/lib/debug//lib/x86_64-linux-gnu/libc-2.19.so...done.
done.
Loaded symbols for /lib/x86_64-linux-gnu/libc.so.6
In Debian/Ubuntu many packages have -dbg
variants of the -dev
packages with the debugging symbols. You'll probably want the ones
for glibc and SDL, at least:
$ sudo apt-get install libc6-dbg libsdl1.2-dbg libsdl2-dbg
Showing the backtrace#
Once done attaching to the process, you can ask for a
backtrace (also bt
or where
) to find out what the
program is currently doing:
(gdb) backtrace
#0 0x00007fb6322eb6fd in nanosleep () at ../sysdeps/unix/syscall-template.S:81
#1 0x00007fb63340ec55 in SDL_Delay_REAL (ms=<optimized out>)
at /tmp/buildd/libsdl2-2.0.2+dfsg1/src/timer/unix/SDL_systimer.c:197
#2 0x0000000000402b57 in main ()
Interrogating hung screensavers#
Given that the process we want to attach to is a screensaver and the
screensaver naturally prevents normal interaction with the computer, I
ran gdb
in a terminal over SSH from another computer.
Where was it stuck?#
The backtrace above was in a good state. Here's what the backtrace looks
like when SDL_Quit()
is called and the program hangs:1
(gdb) bt
#0 0x00007ff498b1a50d in poll () at ../sysdeps/unix/syscall-template.S:81
#1 0x00007ff496416252 in ?? () from /usr/lib/x86_64-linux-gnu/libxcb.so.1
#2 0x00007ff496417ddf in xcb_wait_for_event ()
from /usr/lib/x86_64-linux-gnu/libxcb.so.1
#3 0x00007ff499853008 in _XReadEvents ()
from /usr/lib/x86_64-linux-gnu/libX11.so.6
#4 0x00007ff49983a9ea in XIfEvent ()
from /usr/lib/x86_64-linux-gnu/libX11.so.6
#5 0x00007ff499c0ef61 in X11_HideWindow (_this=0xe33bd0, window=0xeb3640)
at /tmp/buildd/libsdl2-2.0.2+dfsg1/src/video/x11/SDL_x11window.c:943
#6 0x00007ff499bfe8d0 in SDL_HideWindow_REAL (window=0xeb3640)
at /tmp/buildd/libsdl2-2.0.2+dfsg1/src/video/SDL_video.c:1801
#7 0x00007ff499c00408 in SDL_DestroyWindow_REAL (window=0xeb3640)
at /tmp/buildd/libsdl2-2.0.2+dfsg1/src/video/SDL_video.c:2228
#8 0x00007ff499c006f5 in SDL_VideoQuit_REAL ()
at /tmp/buildd/libsdl2-2.0.2+dfsg1/src/video/SDL_video.c:2350
#9 0x00007ff499b6b715 in SDL_QuitSubSystem_REAL (flags=flags@entry=29233)
at /tmp/buildd/libsdl2-2.0.2+dfsg1/src/SDL.c:297
#10 0x00007ff499b6b848 in SDL_Quit_REAL ()
at /tmp/buildd/libsdl2-2.0.2+dfsg1/src/SDL.c:357
It's getting stuck inside a call to
SDL_DestroyWindow()
and no windows were created
by our program, just the one we attached to. This suggests that
SDL_DestroyWindow()
doesn't work properly in that case, so we should
avoid calling it, which means avoiding calling SDL_Quit()
.
Why not exit()
?#
abort()
does less when exiting than exit()
(the
newer quick_exit()
would have also worked). Most
importantly, the screensaver uses atexit()
to call SDL's
cleanup function SQL_Quit()
whenever exit()
is called:
atexit(SDL_Quit);
Why many processes?#
This explains why there would be one hung screensaver process, but not why there would be a lot of them. The reason for that is that XScreenSaver regularly kills (with a SIGTERM) and restarts the screensaver process. The intention is to rotate to a different screensaver, but even if there's only one screensaver enabled, it does it anyway and just starts up the same one again.
-
I actually didn't have a broken version of the code around, so as a workaround, I just attached to the screensaver and used the
call
command and then hit Ctrl+c when it hung in order to generate that backtrace:(gdb) call SDL_Quit() ^C Program received signal SIGINT, Interrupt. 0x00007ff498b1a50d in poll () at ../sysdeps/unix/syscall-template.S:81 81 ../sysdeps/unix/syscall-template.S: No such file or directory. The program being debugged was signaled while in a function called from GDB. GDB remains in the frame where the signal was received. To change this behavior use "set unwindonsignal on". Evaluation of the expression containing the function (SDL_Quit) will be abandoned. When the function is done executing, GDB will silently stop. (gdb) bt ...
Comments
Have something to add? Post a comment by sending an email to comments@aweirdimagination.net. You may use Markdown for formatting.
There are no comments yet.