Building a PHP 7.4.21 Docker image with support for GDB debugger
When your process gets stuck somewhere, but you have no idea where, or if it segfaults, xdebug may not help. I had a process stuck in a…
When your process gets stuck somewhere, but you have no idea where, or if it segfaults, xdebug
may not help. I had a process stuck in a loop outside of PHP. I tried strace
on the process, but it only showed an infinite loop in a library outside of PHP, with no chance of tracing where in my PHP code that was.
So, what to do? Could I start the gdb
debugger and get a stack trace from there?
This does not tell us anything interesting, right?
It’s missing something — debug symbols for PHP. They enable the debugger to get you the real PHP stack trace. They cannot be installed; you have to compile PHP with an --enable-debug
flag and a few other tweaks. I have the whole Dockerfile ready at https://gist.github.com/ondrejhlavacek/f929ad3ab6ae2563f1ce7df41ee5bf05.
The diff
Let’s have a look at the diff and take it step by step.
+ gdb \
Install gdb
to the image, part of the apt-get install
command.
-ENV PHP_EXTRA_CONFIGURE_ARGS --with-apxs2 --disable-cgi
+ENV PHP_EXTRA_CONFIGURE_ARGS --with-apxs2 --disable-cgi --enable-debug
Add an --enable-debug
flag when running ./configure
. This is pretty self-explanatory. It enables debug mode in PHP.
-ENV PHP_LDFLAGS="-Wl,-O1 -pie"
+ENV PHP_LDFLAGS="-Wl,-O0 -pie"
This turns off all optimizations, which is necessary as they may delete debug symbols.
-find /usr/local/bin /usr/local/sbin -type f -executable -exec strip --strip-all ‘{}’ + || true; \
This was the trickiest part. The strip
program literally strips all redundant code from a binary, including debug symbols.
-docker-php-source delete; \
Having a PHP source in the image allows you to load PHP-specific debugging functions from the .gdbinit
file in the debugger. Have a look at the file at https://github.com/php/php-src/blob/master/.gdbinit.
So, once we have the Dockerfile ready, we can build it and test it.
docker build . -t php:7.4.21-apache-buster-debug
docker run -i -t php:7.4.21-apache-buster-debug /bin/bash
gdb /usr/local/bin/php
source /usr/src/php/.gdbinit
If you can see Reading symbols from /usr/local/bin/php...done.
, everything is in running order.
Once this is working, you can include this image in your Dockerfile
and build your application. You can then attach gdb
to any running PHP process and get a nice stack trace, using the zbacktrace
function from the PHP's own .gdbinit
file.
This is what I wanted. Shortly after locating where the process kept freezing, we were able to find the bug and fix it once and for all.
Debugging in containers
If you run your app in Docker, just add --cap-add SYS_PTRACE
to the docker run
command line.
If you’re debugging PHP in a Kubernetes pod, you have to set up both container and pod securityContext
.
spec:
securityContext:
runAsUser: 0
runAsGroup: 0
...
containers:
- name: my-container
securityContext:
capabilities:
add: [ "SYS_PTRACE" ]
...