How we build and operate the Keboola data platform
Ondřej Hlaváček 3 min read

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…

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 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" ]
...

If you liked this article please share it.

Comments ()

Read next

MySQL + SSL + Doctrine

MySQL + SSL + Doctrine

Enabling and enforcing SSL connection on MySQL is easy: Just generate the certificates and configure the server to require secure…
Ondřej Popelka 8 min read