├── .gitignore ├── LICENSE.txt ├── Makefile ├── README.md ├── Vagrantfile ├── assets └── screenshot1.png ├── demo-php-app ├── Dockerfile ├── Makefile ├── docker-compose.yml ├── nginx │ ├── default.conf │ ├── default_http.conf.template │ └── default_https.conf.template └── src │ └── index.php ├── demo-wordpress ├── Makefile ├── docker-compose.yml └── nginx │ ├── default.conf │ ├── default_http.conf.template │ └── default_https.conf.template ├── php-debug-image-stretch ├── Dockerfile ├── Makefile ├── docker-php-entrypoint ├── docker-php-ext-configure ├── docker-php-ext-enable ├── docker-php-ext-install └── docker-php-source ├── php-debug-image ├── Dockerfile ├── Makefile ├── docker-php-entrypoint ├── docker-php-ext-configure ├── docker-php-ext-enable ├── docker-php-ext-install └── docker-php-source ├── php_tool.bt ├── php_tool.py └── wordpress-debug-image ├── Dockerfile ├── Makefile └── docker-entrypoint.sh /.gitignore: -------------------------------------------------------------------------------- 1 | .vagrant 2 | bcc 3 | debs 4 | demo-wordpress/logs 5 | demo-wordpress/wordpress 6 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: demo-php-app demo-wordpress php-debug-image wordpress-debug-image vagrant src/bcc install-bcc 2 | 3 | BCC_VERSION=v0.10.0 4 | 5 | all: demo-php-app 6 | make -C demo-php-app up 7 | 8 | php-debug-image: 9 | make -C php-debug-image-stretch 10 | 11 | wordpress-debug-image: php-debug-image 12 | make -C wordpress-debug-image 13 | 14 | demo-php-app: php-debug-image 15 | make -C demo-php-app 16 | 17 | demo-wordpress: wordpress-debug-image 18 | curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar 19 | chmod +x wp-cli.phar 20 | sudo mv wp-cli.phar /usr/local/bin/wp 21 | wp --info 22 | wp core download --force --path=demo-wordpress/wordpress --version=latest 23 | make -C demo-wordpress 24 | 25 | vagrant: 26 | vagrant up 27 | 28 | src/bcc: 29 | mkdir -p src 30 | cd src && git clone -b ${BCC_VERSION} https://github.com/iovisor/bcc.git 31 | 32 | build-bcc: src/bcc 33 | docker build -t bcc-debian -f src/bcc/Dockerfile.debian src/bcc 34 | docker run -v `pwd`/debs:/debs bcc-debian sh -c "cp *.deb /debs" 35 | 36 | debs: build-bcc 37 | 38 | install-bcc-manually: src/bcc 39 | mkdir -p src/bcc/build 40 | cd src/bcc/build && sudo cmake .. -DCMAKE_INSTALL_PREFIX=/usr && sudo make && sudo make install 41 | 42 | install-bcc-with-debs: debs 43 | sudo dpkg -i debs/*.deb 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PHP Tracing Tool 2 | 3 | PHP tracing tool is a command line tool written in python 3 - based on the [BCC toolkit](https://github.com/iovisor/bcc) (eBPF) - for PHP code monitoring. 4 | 5 | Built to help you understand your program behavior in a "normal" environment, directly in production. 6 | 7 | It gives a deep observability of the application without adding specific php debugging code : function execution flow with latency of each function, tracing I/O syscalls with details … 8 | 9 | You can also **monitor your containerized application** because it works on the PID of the php process, it discuss directly with the kernel (eBPF), there is **no impact on your application**. You just need a PHP compiled with the `--enable-dtrace` debug option (the image is in the repo) 10 | 11 | ![screenshot](assets/screenshot1.png) 12 | 13 | This example trace a demo php program with a google maps API call. 14 | 15 | ## Command line options 16 | 17 | **-h** : help 18 | 19 | **-S** : print the syscalls details inside each function 20 | 21 | **--check** : print the generated BPF C program and quit 22 | 23 | **--debug** : debug mode: print the generated BPF C program 24 | 25 | ## Install an usage 26 | 27 | Be sure to have BCC on your machine (a Debian Buster). 28 | 29 | apt-get install bpfcc-tools python3-bpfcc 30 | 31 | Execute the script as root : 32 | 33 | sudo ./php_tool.py -h 34 | 35 | ## Complete Demo 36 | 37 | Setup the vagrant machine and go in 38 | 39 | cd vagrant-buster && vagrant up 40 | vagrant ssh 41 | cd /vagrant/php_tool 42 | 43 | Launch the PHP demo and nginx containers 44 | 45 | make 46 | 47 | Instrumenting the code with php_tool 48 | 49 | sudo ./php_tool.py -S PID [PID ...] 50 | 51 | Usage 52 | 53 | sudo ./php_tool.py 54 | 55 | There is also a Wordpress demo image. 56 | 57 | ## Licence 58 | 59 | Apache-2.0 © 2019 Nicolas Dubouilh, Mathieu Lecarme 60 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | Vagrant.configure("2") do |config| 5 | config.vm.box = "debian/buster64" 6 | config.vm.network "private_network", ip: "192.168.33.10" 7 | config.vm.provider "virtualbox" do |v| 8 | v.memory = 1024 9 | end 10 | config.vm.synced_folder ".", "/vagrant", disabled: true 11 | config.vm.synced_folder "./", "/vagrant/php_tool/", owner: "vagrant", group: "vagrant", type: "rsync", rsync__exclude: %w(src) 12 | config.vm.provision "shell", inline: <<-SHELL 13 | sudo su 14 | apt-get update -y 15 | apt-get install -y --no-install-recommends \ 16 | python \ 17 | python3 \ 18 | php \ 19 | python-pip \ 20 | python3-pip \ 21 | python3-bpfcc \ 22 | python-bpfcc \ 23 | bpfcc-tools \ 24 | sysvinit-utils \ 25 | apt-transport-https \ 26 | ca-certificates \ 27 | curl \ 28 | gnupg2 \ 29 | git \ 30 | software-properties-common 31 | 32 | curl -fsSL https://download.docker.com/linux/debian/gpg | sudo apt-key add - 33 | add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/debian $(lsb_release -cs) stable" 34 | apt-get update -y 35 | apt-get install -y --no-install-recommends \ 36 | docker-ce \ 37 | docker-ce-cli \ 38 | docker-compose \ 39 | containerd.io 40 | 41 | usermod -aG docker vagrant 42 | docker info 43 | 44 | SHELL 45 | end 46 | -------------------------------------------------------------------------------- /assets/screenshot1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/factorysh/PHP-tracing-tool/afddf762c5f7c267411cb1ed083ecd6e5b702fdd/assets/screenshot1.png -------------------------------------------------------------------------------- /demo-php-app/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM php-debug 2 | 3 | COPY . /var/www/html 4 | 5 | WORKDIR /var/www/html 6 | 7 | EXPOSE 9000 8 | 9 | CMD ["php-fpm"] 10 | -------------------------------------------------------------------------------- /demo-php-app/Makefile: -------------------------------------------------------------------------------- 1 | build: 2 | docker build . -t php-app 3 | 4 | up: 5 | docker-compose up -d 6 | 7 | down: 8 | docker-compose down 9 | -------------------------------------------------------------------------------- /demo-php-app/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | services: 4 | web: 5 | image: nginx 6 | ports: 7 | - "8080:80" 8 | volumes: 9 | - ./nginx:/etc/nginx/conf.d 10 | - ./src:/var/www/html 11 | restart: always 12 | 13 | php: 14 | image: php-app 15 | environment: 16 | USE_ZEND_DTRACE: 1 17 | volumes: 18 | - ./src:/var/www/html 19 | restart: always 20 | -------------------------------------------------------------------------------- /demo-php-app/nginx/default.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | server_name 127.0.0.1; 4 | 5 | root /var/www/html; 6 | index index.php; 7 | 8 | access_log /var/log/nginx/access.log; 9 | error_log /var/log/nginx/error.log; 10 | 11 | location / { 12 | try_files $uri $uri/ /index.php?$args; 13 | } 14 | 15 | location ~ \.php$ { 16 | try_files $uri =404; 17 | fastcgi_split_path_info ^(.+\.php)(/.+)$; 18 | fastcgi_pass php:9000; 19 | fastcgi_index index.php; 20 | include fastcgi_params; 21 | fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; 22 | fastcgi_param PATH_INFO $fastcgi_path_info; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /demo-php-app/nginx/default_http.conf.template: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | server_name FQDN_OR_IP; 4 | 5 | root /var/www/html; 6 | index index.php; 7 | 8 | access_log /var/log/nginx/access.log; 9 | error_log /var/log/nginx/error.log; 10 | 11 | location / { 12 | try_files $uri $uri/ /index.php?$args; 13 | } 14 | 15 | location ~ \.php$ { 16 | try_files $uri =404; 17 | fastcgi_split_path_info ^(.+\.php)(/.+)$; 18 | fastcgi_pass php:9000; 19 | fastcgi_index index.php; 20 | include fastcgi_params; 21 | fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; 22 | fastcgi_param PATH_INFO $fastcgi_path_info; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /demo-php-app/nginx/default_https.conf.template: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | listen [::]:80; 4 | server_name FQDN_OR_IP; 5 | 6 | location / { 7 | rewrite ^ https://$host$request_uri? permanent; 8 | } 9 | 10 | location ^~ /.well-known { 11 | allow all; 12 | root /data/letsencrypt/; 13 | } 14 | } 15 | 16 | server { 17 | listen 443 ssl http2; 18 | listen [::]:443 ssl http2; 19 | server_name FQDN_OR_IP www.FQDN_OR_IP; 20 | 21 | add_header Strict-Transport-Security "max-age=31536000" always; 22 | 23 | ssl_session_cache shared:SSL:20m; 24 | ssl_session_timeout 10m; 25 | 26 | ssl_protocols TLSv1 TLSv1.1 TLSv1.2; 27 | ssl_prefer_server_ciphers on; 28 | ssl_ciphers "ECDH+AESGCM:ECDH+AES256:ECDH+AES128:!ADH:!AECDH:!MD5;"; 29 | 30 | ssl_stapling on; 31 | ssl_stapling_verify on; 32 | resolver 8.8.8.8 8.8.4.4; 33 | 34 | root /var/www/html; 35 | index index.php; 36 | 37 | access_log /var/log/nginx/access.log; 38 | error_log /var/log/nginx/error.log; 39 | 40 | ssl_certificate /etc/letsencrypt/live/FQDN_OR_IP/fullchain.pem; 41 | ssl_certificate_key /etc/letsencrypt/live/FQDN_OR_IP/privkey.pem; 42 | ssl_trusted_certificate /etc/letsencrypt/live/FQDN_OR_IP/chain.pem; 43 | 44 | location / { 45 | try_files $uri $uri/ /index.php?$args; 46 | } 47 | 48 | location ~ \.php$ { 49 | try_files $uri =404; 50 | fastcgi_split_path_info ^(.+\.php)(/.+)$; 51 | fastcgi_pass php:9000; 52 | fastcgi_index index.php; 53 | include fastcgi_params; 54 | fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; 55 | fastcgi_param PATH_INFO $fastcgi_path_info; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /demo-php-app/src/index.php: -------------------------------------------------------------------------------- 1 | '', 'lat' => '', 'lng' => '', 'city' => '', 'department' => '', 'region' => '', 'country' => '', 'postal_code' => ''); 10 | //on formate l'adresse 11 | $address = str_replace(" ", "+", $address); 12 | //on fait l'appel à l'API google map pour géocoder cette adresse 13 | $json = file_get_contents("https://maps.google.com/maps/api/geocode/json?key=" . self::$apikey . "&address=$address&sensor=false®ion=fr"); 14 | $json = json_decode($json); 15 | //on enregistre les résultats recherchés 16 | if ($json->status == 'OK' && count($json->results) > 0) { 17 | $res = $json->results[0]; 18 | //adresse complète et latitude/longitude 19 | $data['address'] = $res->formatted_address; 20 | $data['lat'] = $res->geometry->location->lat; 21 | $data['lng'] = $res->geometry->location->lng; 22 | foreach ($res->address_components as $component) { 23 | //ville 24 | if ($component->types[0] == 'locality') { 25 | $data['city'] = $component->long_name; 26 | } 27 | //départment 28 | if ($component->types[0] == 'administrative_area_level_2') { 29 | $data['department'] = $component->long_name; 30 | } 31 | //région 32 | if ($component->types[0] == 'administrative_area_level_1') { 33 | $data['region'] = $component->long_name; 34 | } 35 | //pays 36 | if ($component->types[0] == 'country') { 37 | $data['country'] = $component->long_name; 38 | } 39 | //code postal 40 | if ($component->types[0] == 'postal_code') { 41 | $data['postal_code'] = $component->long_name; 42 | } 43 | } 44 | } 45 | return $data; 46 | } 47 | 48 | } 49 | 50 | $data = GmapApi::geocodeAddress('40 passage des panoramas 75002 Paris'); 51 | 52 | $my_file = 'index.php'; 53 | $handle = fopen($my_file, 'r') or die('Cannot open file: '.$my_file); 54 | $content = fread($handle, filesize($my_file)); 55 | fclose($handle); 56 | 57 | //on affiche les différente infos 58 | echo ''; 63 | -------------------------------------------------------------------------------- /demo-wordpress/Makefile: -------------------------------------------------------------------------------- 1 | up: 2 | docker-compose up -d 3 | 4 | down: 5 | docker-compose down 6 | -------------------------------------------------------------------------------- /demo-wordpress/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | services: 4 | 5 | wordpress: 6 | image: wordpress-debug 7 | restart: always 8 | volumes: 9 | - ./wordpress:/var/www/html 10 | environment: 11 | USE_ZEND_DTRACE: 1 12 | WORDPRESS_DB_HOST: db 13 | WORDPRESS_DB_USER: admin 14 | WORDPRESS_DB_PASSWORD: password 15 | WORDPRESS_DB_NAME: db_wordpress 16 | 17 | nginx: 18 | image: nginx 19 | ports: 20 | - '8080:80' 21 | volumes: 22 | - ./nginx:/etc/nginx/conf.d 23 | - ./logs/nginx:/var/log/nginx 24 | - ./wordpress:/var/www/html 25 | restart: always 26 | 27 | db: 28 | image: mysql:5.7 29 | restart: always 30 | environment: 31 | MYSQL_DATABASE: db_wordpress 32 | MYSQL_USER: admin 33 | MYSQL_PASSWORD: password 34 | MYSQL_RANDOM_ROOT_PASSWORD: '1' 35 | -------------------------------------------------------------------------------- /demo-wordpress/nginx/default.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | server_name 127.0.0.1; 4 | 5 | root /var/www/html; 6 | index index.php; 7 | 8 | access_log /var/log/nginx/access.log; 9 | error_log /var/log/nginx/error.log; 10 | 11 | location / { 12 | try_files $uri $uri/ /index.php?$args; 13 | } 14 | 15 | location ~ \.php$ { 16 | try_files $uri =404; 17 | fastcgi_split_path_info ^(.+\.php)(/.+)$; 18 | fastcgi_pass wordpress:9000; 19 | fastcgi_index index.php; 20 | include fastcgi_params; 21 | fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; 22 | fastcgi_param PATH_INFO $fastcgi_path_info; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /demo-wordpress/nginx/default_http.conf.template: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | server_name FQDN_OR_IP; 4 | 5 | root /var/www/html; 6 | index index.php; 7 | 8 | access_log /var/log/nginx/access.log; 9 | error_log /var/log/nginx/error.log; 10 | 11 | location / { 12 | try_files $uri $uri/ /index.php?$args; 13 | } 14 | 15 | location ~ \.php$ { 16 | try_files $uri =404; 17 | fastcgi_split_path_info ^(.+\.php)(/.+)$; 18 | fastcgi_pass wordpress:9000; 19 | fastcgi_index index.php; 20 | include fastcgi_params; 21 | fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; 22 | fastcgi_param PATH_INFO $fastcgi_path_info; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /demo-wordpress/nginx/default_https.conf.template: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | listen [::]:80; 4 | server_name FQDN_OR_IP; 5 | 6 | location / { 7 | rewrite ^ https://$host$request_uri? permanent; 8 | } 9 | 10 | location ^~ /.well-known { 11 | allow all; 12 | root /data/letsencrypt/; 13 | } 14 | } 15 | 16 | server { 17 | listen 443 ssl http2; 18 | listen [::]:443 ssl http2; 19 | server_name FQDN_OR_IP www.FQDN_OR_IP; 20 | 21 | add_header Strict-Transport-Security "max-age=31536000" always; 22 | 23 | ssl_session_cache shared:SSL:20m; 24 | ssl_session_timeout 10m; 25 | 26 | ssl_protocols TLSv1 TLSv1.1 TLSv1.2; 27 | ssl_prefer_server_ciphers on; 28 | ssl_ciphers "ECDH+AESGCM:ECDH+AES256:ECDH+AES128:!ADH:!AECDH:!MD5;"; 29 | 30 | ssl_stapling on; 31 | ssl_stapling_verify on; 32 | resolver 8.8.8.8 8.8.4.4; 33 | 34 | root /var/www/html; 35 | index index.php; 36 | 37 | access_log /var/log/nginx/access.log; 38 | error_log /var/log/nginx/error.log; 39 | 40 | ssl_certificate /etc/letsencrypt/live/FQDN_OR_IP/fullchain.pem; 41 | ssl_certificate_key /etc/letsencrypt/live/FQDN_OR_IP/privkey.pem; 42 | ssl_trusted_certificate /etc/letsencrypt/live/FQDN_OR_IP/chain.pem; 43 | 44 | location / { 45 | try_files $uri $uri/ /index.php?$args; 46 | } 47 | 48 | location ~ \.php$ { 49 | try_files $uri =404; 50 | fastcgi_split_path_info ^(.+\.php)(/.+)$; 51 | fastcgi_pass wordpress:9000; 52 | fastcgi_index index.php; 53 | include fastcgi_params; 54 | fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; 55 | fastcgi_param PATH_INFO $fastcgi_path_info; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /php-debug-image-stretch/Dockerfile: -------------------------------------------------------------------------------- 1 | # 2 | # NOTE: THIS DOCKERFILE IS GENERATED VIA "update.sh" 3 | # 4 | # PLEASE DO NOT EDIT IT DIRECTLY. 5 | # 6 | 7 | FROM debian:stretch-slim 8 | 9 | # prevent Debian's PHP packages from being installed 10 | # https://github.com/docker-library/php/pull/542 11 | RUN set -eux; \ 12 | { \ 13 | echo 'Package: php*'; \ 14 | echo 'Pin: release *'; \ 15 | echo 'Pin-Priority: -1'; \ 16 | } > /etc/apt/preferences.d/no-debian-php 17 | 18 | # dependencies required for running "phpize" 19 | # (see persistent deps below) 20 | ENV PHPIZE_DEPS \ 21 | autoconf \ 22 | dpkg-dev \ 23 | file \ 24 | g++ \ 25 | gcc \ 26 | libc-dev \ 27 | make \ 28 | pkg-config \ 29 | re2c 30 | 31 | # persistent / runtime deps 32 | RUN set -eux; \ 33 | apt-get update; \ 34 | apt-get install -y --no-install-recommends \ 35 | $PHPIZE_DEPS \ 36 | ca-certificates \ 37 | systemtap-sdt-dev \ 38 | curl \ 39 | xz-utils \ 40 | ; \ 41 | rm -rf /var/lib/apt/lists/* 42 | 43 | ENV PHP_INI_DIR /usr/local/etc/php 44 | RUN set -eux; \ 45 | mkdir -p "$PHP_INI_DIR/conf.d"; \ 46 | # allow running as an arbitrary user (https://github.com/docker-library/php/issues/743) 47 | [ ! -d /var/www/html ]; \ 48 | mkdir -p /var/www/html; \ 49 | chown www-data:www-data /var/www/html; \ 50 | chmod 777 /var/www/html 51 | 52 | #### 53 | ENV PHP_EXTRA_CONFIGURE_ARGS --enable-fpm --with-fpm-user=www-data --with-fpm-group=www-data --disable-cgi 54 | #### 55 | 56 | # Apply stack smash protection to functions using local buffers and alloca() 57 | # Make PHP's main executable position-independent (improves ASLR security mechanism, and has no performance impact on x86_64) 58 | # Enable optimization (-O2) 59 | # Enable linker optimization (this sorts the hash buckets to improve cache locality, and is non-default) 60 | # Adds GNU HASH segments to generated executables (this is used if present, and is much faster than sysv hash; in this configuration, sysv hash is also generated) 61 | # https://github.com/docker-library/php/issues/272 62 | ENV PHP_CFLAGS="-fstack-protector-strong -fpic -fpie -O2" 63 | ENV PHP_CPPFLAGS="$PHP_CFLAGS" 64 | ENV PHP_LDFLAGS="-Wl,-O1 -Wl,--hash-style=both -pie" 65 | 66 | ENV GPG_KEYS CBAF69F173A0FEA4B537F470D66C9593118BCCB6 F38252826ACD957EF380D39F2F7956BC5DA04B5D 67 | 68 | ENV PHP_VERSION 7.3.8 69 | ENV PHP_URL="https://www.php.net/get/php-7.3.8.tar.xz/from/this/mirror" PHP_ASC_URL="https://www.php.net/get/php-7.3.8.tar.xz.asc/from/this/mirror" 70 | ENV PHP_SHA256="f6046b2ae625d8c04310bda0737ac660dc5563a8e04e8a46c1ee24ea414ad5a5" PHP_MD5="" 71 | 72 | RUN set -eux; \ 73 | \ 74 | savedAptMark="$(apt-mark showmanual)"; \ 75 | apt-get update; \ 76 | apt-get install -y --no-install-recommends gnupg dirmngr; \ 77 | rm -rf /var/lib/apt/lists/*; \ 78 | \ 79 | mkdir -p /usr/src; \ 80 | cd /usr/src; \ 81 | \ 82 | curl -fsSL -o php.tar.xz "$PHP_URL"; \ 83 | \ 84 | if [ -n "$PHP_SHA256" ]; then \ 85 | echo "$PHP_SHA256 *php.tar.xz" | sha256sum -c -; \ 86 | fi; \ 87 | if [ -n "$PHP_MD5" ]; then \ 88 | echo "$PHP_MD5 *php.tar.xz" | md5sum -c -; \ 89 | fi; \ 90 | \ 91 | if [ -n "$PHP_ASC_URL" ]; then \ 92 | curl -fsSL -o php.tar.xz.asc "$PHP_ASC_URL"; \ 93 | export GNUPGHOME="$(mktemp -d)"; \ 94 | for key in $GPG_KEYS; do \ 95 | gpg --batch --keyserver ha.pool.sks-keyservers.net --recv-keys "$key"; \ 96 | done; \ 97 | gpg --batch --verify php.tar.xz.asc php.tar.xz; \ 98 | gpgconf --kill all; \ 99 | rm -rf "$GNUPGHOME"; \ 100 | fi; \ 101 | \ 102 | apt-mark auto '.*' > /dev/null; \ 103 | apt-mark manual $savedAptMark > /dev/null; \ 104 | apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false 105 | 106 | COPY docker-php-source /usr/local/bin/ 107 | 108 | RUN set -eux; \ 109 | \ 110 | savedAptMark="$(apt-mark showmanual)"; \ 111 | #### 112 | sed -e 's/stretch/buster/g' /etc/apt/sources.list > /etc/apt/sources.list.d/buster.list; \ 113 | { \ 114 | echo 'Package: *'; \ 115 | echo 'Pin: release n=buster'; \ 116 | echo 'Pin-Priority: -10'; \ 117 | echo; \ 118 | echo 'Package: libargon2*'; \ 119 | echo 'Pin: release n=buster'; \ 120 | echo 'Pin-Priority: 990'; \ 121 | } > /etc/apt/preferences.d/argon2-buster; \ 122 | #### 123 | apt-get update; \ 124 | apt-get install -y --no-install-recommends \ 125 | libargon2-dev \ 126 | libcurl4-openssl-dev \ 127 | libedit-dev \ 128 | libsodium-dev \ 129 | libsqlite3-dev \ 130 | libssl-dev \ 131 | libxml2-dev \ 132 | zlib1g-dev \ 133 | ${PHP_EXTRA_BUILD_DEPS:-} \ 134 | ; \ 135 | rm -rf /var/lib/apt/lists/*; \ 136 | \ 137 | export \ 138 | CFLAGS="$PHP_CFLAGS" \ 139 | CPPFLAGS="$PHP_CPPFLAGS" \ 140 | LDFLAGS="$PHP_LDFLAGS" \ 141 | ; \ 142 | docker-php-source extract; \ 143 | cd /usr/src/php; \ 144 | gnuArch="$(dpkg-architecture --query DEB_BUILD_GNU_TYPE)"; \ 145 | debMultiarch="$(dpkg-architecture --query DEB_BUILD_MULTIARCH)"; \ 146 | # https://bugs.php.net/bug.php?id=74125 147 | if [ ! -d /usr/include/curl ]; then \ 148 | ln -sT "/usr/include/$debMultiarch/curl" /usr/local/include/curl; \ 149 | fi; \ 150 | ./configure \ 151 | --enable-dtrace \ 152 | --build="$gnuArch" \ 153 | --with-config-file-path="$PHP_INI_DIR" \ 154 | --with-config-file-scan-dir="$PHP_INI_DIR/conf.d" \ 155 | \ 156 | # make sure invalid --configure-flags are fatal errors intead of just warnings 157 | --enable-option-checking=fatal \ 158 | \ 159 | # https://github.com/docker-library/php/issues/439 160 | --with-mhash \ 161 | \ 162 | # --enable-ftp is included here because ftp_ssl_connect() needs ftp to be compiled statically (see https://github.com/docker-library/php/issues/236) 163 | --enable-ftp \ 164 | # --enable-mbstring is included here because otherwise there's no way to get pecl to use it properly (see https://github.com/docker-library/php/issues/195) 165 | --enable-mbstring \ 166 | # --enable-mysqlnd is included here because it's harder to compile after the fact than extensions are (since it's a plugin for several extensions, not an extension in itself) 167 | --enable-mysqlnd \ 168 | # https://wiki.php.net/rfc/argon2_password_hash (7.2+) 169 | --with-password-argon2 \ 170 | # https://wiki.php.net/rfc/libsodium 171 | --with-sodium=shared \ 172 | \ 173 | --with-curl \ 174 | --with-libedit \ 175 | --with-openssl \ 176 | --with-zlib \ 177 | \ 178 | # bundled pcre does not support JIT on s390x 179 | # https://manpages.debian.org/stretch/libpcre3-dev/pcrejit.3.en.html#AVAILABILITY_OF_JIT_SUPPORT 180 | $(test "$gnuArch" = 's390x-linux-gnu' && echo '--without-pcre-jit') \ 181 | --with-libdir="lib/$debMultiarch" \ 182 | \ 183 | ${PHP_EXTRA_CONFIGURE_ARGS:-} \ 184 | ; \ 185 | make -j "$(nproc)"; \ 186 | find -type f -name '*.a' -delete; \ 187 | make install; \ 188 | find /usr/local/bin /usr/local/sbin -type f -executable -exec strip --strip-all '{}' + || true; \ 189 | make clean; \ 190 | \ 191 | # https://github.com/docker-library/php/issues/692 (copy default example "php.ini" files somewhere easily discoverable) 192 | cp -v php.ini-* "$PHP_INI_DIR/"; \ 193 | \ 194 | cd /; \ 195 | docker-php-source delete; \ 196 | \ 197 | # reset apt-mark's "manual" list so that "purge --auto-remove" will remove all build dependencies 198 | apt-mark auto '.*' > /dev/null; \ 199 | [ -z "$savedAptMark" ] || apt-mark manual $savedAptMark; \ 200 | find /usr/local -type f -executable -exec ldd '{}' ';' \ 201 | | awk '/=>/ { print $(NF-1) }' \ 202 | | sort -u \ 203 | | xargs -r dpkg-query --search \ 204 | | cut -d: -f1 \ 205 | | sort -u \ 206 | | xargs -r apt-mark manual \ 207 | ; \ 208 | apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false; \ 209 | \ 210 | # update pecl channel definitions https://github.com/docker-library/php/issues/443 211 | pecl update-channels; \ 212 | rm -rf /tmp/pear ~/.pearrc; \ 213 | # smoke test 214 | php --version 215 | 216 | COPY docker-php-ext-* docker-php-entrypoint /usr/local/bin/ 217 | 218 | # sodium was built as a shared module (so that it can be replaced later if so desired), so let's enable it too (https://github.com/docker-library/php/issues/598) 219 | RUN docker-php-ext-enable sodium 220 | 221 | ENTRYPOINT ["docker-php-entrypoint"] 222 | #### 223 | WORKDIR /var/www/html 224 | 225 | RUN set -eux; \ 226 | cd /usr/local/etc; \ 227 | if [ -d php-fpm.d ]; then \ 228 | # for some reason, upstream's php-fpm.conf.default has "include=NONE/etc/php-fpm.d/*.conf" 229 | sed 's!=NONE/!=!g' php-fpm.conf.default | tee php-fpm.conf > /dev/null; \ 230 | cp php-fpm.d/www.conf.default php-fpm.d/www.conf; \ 231 | else \ 232 | # PHP 5.x doesn't use "include=" by default, so we'll create our own simple config that mimics PHP 7+ for consistency 233 | mkdir php-fpm.d; \ 234 | cp php-fpm.conf.default php-fpm.d/www.conf; \ 235 | { \ 236 | echo '[global]'; \ 237 | echo 'include=etc/php-fpm.d/*.conf'; \ 238 | } | tee php-fpm.conf; \ 239 | fi; \ 240 | { \ 241 | echo '[global]'; \ 242 | echo 'error_log = /proc/self/fd/2'; \ 243 | echo; echo '; https://github.com/docker-library/php/pull/725#issuecomment-443540114'; echo 'log_limit = 8192'; \ 244 | echo; \ 245 | echo '[www]'; \ 246 | echo '; if we send this to /proc/self/fd/1, it never appears'; \ 247 | echo 'access.log = /proc/self/fd/2'; \ 248 | echo; \ 249 | echo 'clear_env = no'; \ 250 | echo; \ 251 | echo '; Ensure worker stdout and stderr are sent to the main error log.'; \ 252 | echo 'catch_workers_output = yes'; \ 253 | echo 'decorate_workers_output = no'; \ 254 | } | tee php-fpm.d/docker.conf; \ 255 | { \ 256 | echo '[global]'; \ 257 | echo 'daemonize = no'; \ 258 | echo; \ 259 | echo '[www]'; \ 260 | echo 'listen = 9000'; \ 261 | } | tee php-fpm.d/zz-docker.conf 262 | 263 | # Override stop signal to stop process gracefully 264 | # https://github.com/php/php-src/blob/17baa87faddc2550def3ae7314236826bc1b1398/sapi/fpm/php-fpm.8.in#L163 265 | STOPSIGNAL SIGQUIT 266 | 267 | EXPOSE 9000 268 | CMD ["php-fpm"] 269 | #### 270 | -------------------------------------------------------------------------------- /php-debug-image-stretch/Makefile: -------------------------------------------------------------------------------- 1 | build: 2 | docker build . -t php-debug 3 | -------------------------------------------------------------------------------- /php-debug-image-stretch/docker-php-entrypoint: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | # first arg is `-f` or `--some-option` 5 | if [ "${1#-}" != "$1" ]; then 6 | set -- php-fpm "$@" 7 | fi 8 | 9 | exec "$@" 10 | -------------------------------------------------------------------------------- /php-debug-image-stretch/docker-php-ext-configure: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | # prefer user supplied CFLAGS, but default to our PHP_CFLAGS 5 | : ${CFLAGS:=$PHP_CFLAGS} 6 | : ${CPPFLAGS:=$PHP_CPPFLAGS} 7 | : ${LDFLAGS:=$PHP_LDFLAGS} 8 | export CFLAGS CPPFLAGS LDFLAGS 9 | 10 | srcExists= 11 | if [ -d /usr/src/php ]; then 12 | srcExists=1 13 | fi 14 | docker-php-source extract 15 | if [ -z "$srcExists" ]; then 16 | touch /usr/src/php/.docker-delete-me 17 | fi 18 | 19 | cd /usr/src/php/ext 20 | 21 | usage() { 22 | echo "usage: $0 ext-name [configure flags]" 23 | echo " ie: $0 gd --with-jpeg-dir=/usr/local/something" 24 | echo 25 | echo 'Possible values for ext-name:' 26 | find . \ 27 | -mindepth 2 \ 28 | -maxdepth 2 \ 29 | -type f \ 30 | -name 'config.m4' \ 31 | | xargs -n1 dirname \ 32 | | xargs -n1 basename \ 33 | | sort \ 34 | | xargs 35 | echo 36 | echo 'Some of the above modules are already compiled into PHP; please check' 37 | echo 'the output of "php -i" to see which modules are already loaded.' 38 | } 39 | 40 | ext="$1" 41 | if [ -z "$ext" ] || [ ! -d "$ext" ]; then 42 | usage >&2 43 | exit 1 44 | fi 45 | shift 46 | 47 | pm='unknown' 48 | if [ -e /lib/apk/db/installed ]; then 49 | pm='apk' 50 | fi 51 | 52 | if [ "$pm" = 'apk' ]; then 53 | if \ 54 | [ -n "$PHPIZE_DEPS" ] \ 55 | && ! apk info --installed .phpize-deps > /dev/null \ 56 | && ! apk info --installed .phpize-deps-configure > /dev/null \ 57 | ; then 58 | apk add --no-cache --virtual .phpize-deps-configure $PHPIZE_DEPS 59 | fi 60 | fi 61 | 62 | if command -v dpkg-architecture > /dev/null; then 63 | gnuArch="$(dpkg-architecture --query DEB_BUILD_GNU_TYPE)" 64 | set -- --build="$gnuArch" "$@" 65 | fi 66 | 67 | cd "$ext" 68 | phpize 69 | ./configure "$@" 70 | -------------------------------------------------------------------------------- /php-debug-image-stretch/docker-php-ext-enable: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | extDir="$(php -d 'display_errors=stderr' -r 'echo ini_get("extension_dir");')" 5 | cd "$extDir" 6 | 7 | usage() { 8 | echo "usage: $0 [options] module-name [module-name ...]" 9 | echo " ie: $0 gd mysqli" 10 | echo " $0 pdo pdo_mysql" 11 | echo " $0 --ini-name 0-apc.ini apcu apc" 12 | echo 13 | echo 'Possible values for module-name:' 14 | find -maxdepth 1 \ 15 | -type f \ 16 | -name '*.so' \ 17 | -exec basename '{}' ';' \ 18 | | sort \ 19 | | xargs 20 | echo 21 | echo 'Some of the above modules are already compiled into PHP; please check' 22 | echo 'the output of "php -i" to see which modules are already loaded.' 23 | } 24 | 25 | opts="$(getopt -o 'h?' --long 'help,ini-name:' -- "$@" || { usage >&2 && false; })" 26 | eval set -- "$opts" 27 | 28 | iniName= 29 | while true; do 30 | flag="$1" 31 | shift 32 | case "$flag" in 33 | --help|-h|'-?') usage && exit 0 ;; 34 | --ini-name) iniName="$1" && shift ;; 35 | --) break ;; 36 | *) 37 | { 38 | echo "error: unknown flag: $flag" 39 | usage 40 | } >&2 41 | exit 1 42 | ;; 43 | esac 44 | done 45 | 46 | modules= 47 | for module; do 48 | if [ -z "$module" ]; then 49 | continue 50 | fi 51 | if [ -f "$module.so" ] && ! [ -f "$module" ]; then 52 | # allow ".so" to be optional 53 | module="$module.so" 54 | fi 55 | if ! [ -f "$module" ]; then 56 | echo >&2 "error: '$module' does not exist" 57 | echo >&2 58 | usage >&2 59 | exit 1 60 | fi 61 | modules="$modules $module" 62 | done 63 | 64 | if [ -z "$modules" ]; then 65 | usage >&2 66 | exit 1 67 | fi 68 | 69 | pm='unknown' 70 | if [ -e /lib/apk/db/installed ]; then 71 | pm='apk' 72 | fi 73 | 74 | apkDel= 75 | if [ "$pm" = 'apk' ]; then 76 | if \ 77 | [ -n "$PHPIZE_DEPS" ] \ 78 | && ! apk info --installed .phpize-deps > /dev/null \ 79 | && ! apk info --installed .phpize-deps-configure > /dev/null \ 80 | ; then 81 | apk add --no-cache --virtual '.docker-php-ext-enable-deps' binutils 82 | apkDel='.docker-php-ext-enable-deps' 83 | fi 84 | fi 85 | 86 | for module in $modules; do 87 | if readelf --wide --syms "$module" | grep -q ' zend_extension_entry$'; then 88 | # https://wiki.php.net/internals/extensions#loading_zend_extensions 89 | absModule="$(readlink -f "$module")" 90 | line="zend_extension=$absModule" 91 | else 92 | line="extension=$module" 93 | fi 94 | 95 | ext="$(basename "$module")" 96 | ext="${ext%.*}" 97 | if php -d 'display_errors=stderr' -r 'exit(extension_loaded("'"$ext"'") ? 0 : 1);'; then 98 | # this isn't perfect, but it's better than nothing 99 | # (for example, 'opcache.so' presents inside PHP as 'Zend OPcache', not 'opcache') 100 | echo >&2 101 | echo >&2 "warning: $ext ($module) is already loaded!" 102 | echo >&2 103 | continue 104 | fi 105 | 106 | ini="$PHP_INI_DIR/conf.d/${iniName:-"docker-php-ext-$ext.ini"}" 107 | if ! grep -q "$line" "$ini" 2>/dev/null; then 108 | echo "$line" >> "$ini" 109 | fi 110 | done 111 | 112 | if [ "$pm" = 'apk' ] && [ -n "$apkDel" ]; then 113 | apk del --no-network $apkDel 114 | fi 115 | -------------------------------------------------------------------------------- /php-debug-image-stretch/docker-php-ext-install: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | # prefer user supplied CFLAGS, but default to our PHP_CFLAGS 5 | : ${CFLAGS:=$PHP_CFLAGS} 6 | : ${CPPFLAGS:=$PHP_CPPFLAGS} 7 | : ${LDFLAGS:=$PHP_LDFLAGS} 8 | export CFLAGS CPPFLAGS LDFLAGS 9 | 10 | srcExists= 11 | if [ -d /usr/src/php ]; then 12 | srcExists=1 13 | fi 14 | docker-php-source extract 15 | if [ -z "$srcExists" ]; then 16 | touch /usr/src/php/.docker-delete-me 17 | fi 18 | 19 | cd /usr/src/php/ext 20 | 21 | usage() { 22 | echo "usage: $0 [-jN] ext-name [ext-name ...]" 23 | echo " ie: $0 gd mysqli" 24 | echo " $0 pdo pdo_mysql" 25 | echo " $0 -j5 gd mbstring mysqli pdo pdo_mysql shmop" 26 | echo 27 | echo 'if custom ./configure arguments are necessary, see docker-php-ext-configure' 28 | echo 29 | echo 'Possible values for ext-name:' 30 | find . \ 31 | -mindepth 2 \ 32 | -maxdepth 2 \ 33 | -type f \ 34 | -name 'config.m4' \ 35 | | xargs -n1 dirname \ 36 | | xargs -n1 basename \ 37 | | sort \ 38 | | xargs 39 | echo 40 | echo 'Some of the above modules are already compiled into PHP; please check' 41 | echo 'the output of "php -i" to see which modules are already loaded.' 42 | } 43 | 44 | opts="$(getopt -o 'h?j:' --long 'help,jobs:' -- "$@" || { usage >&2 && false; })" 45 | eval set -- "$opts" 46 | 47 | j=1 48 | while true; do 49 | flag="$1" 50 | shift 51 | case "$flag" in 52 | --help|-h|'-?') usage && exit 0 ;; 53 | --jobs|-j) j="$1" && shift ;; 54 | --) break ;; 55 | *) 56 | { 57 | echo "error: unknown flag: $flag" 58 | usage 59 | } >&2 60 | exit 1 61 | ;; 62 | esac 63 | done 64 | 65 | exts= 66 | for ext; do 67 | if [ -z "$ext" ]; then 68 | continue 69 | fi 70 | if [ ! -d "$ext" ]; then 71 | echo >&2 "error: $PWD/$ext does not exist" 72 | echo >&2 73 | usage >&2 74 | exit 1 75 | fi 76 | exts="$exts $ext" 77 | done 78 | 79 | if [ -z "$exts" ]; then 80 | usage >&2 81 | exit 1 82 | fi 83 | 84 | pm='unknown' 85 | if [ -e /lib/apk/db/installed ]; then 86 | pm='apk' 87 | fi 88 | 89 | apkDel= 90 | if [ "$pm" = 'apk' ]; then 91 | if [ -n "$PHPIZE_DEPS" ]; then 92 | if apk info --installed .phpize-deps-configure > /dev/null; then 93 | apkDel='.phpize-deps-configure' 94 | elif ! apk info --installed .phpize-deps > /dev/null; then 95 | apk add --no-cache --virtual .phpize-deps $PHPIZE_DEPS 96 | apkDel='.phpize-deps' 97 | fi 98 | fi 99 | fi 100 | 101 | popDir="$PWD" 102 | for ext in $exts; do 103 | cd "$ext" 104 | [ -e Makefile ] || docker-php-ext-configure "$ext" 105 | make -j"$j" 106 | make -j"$j" install 107 | find modules \ 108 | -maxdepth 1 \ 109 | -name '*.so' \ 110 | -exec basename '{}' ';' \ 111 | | xargs -r docker-php-ext-enable 112 | make -j"$j" clean 113 | cd "$popDir" 114 | done 115 | 116 | if [ "$pm" = 'apk' ] && [ -n "$apkDel" ]; then 117 | apk del --no-network $apkDel 118 | fi 119 | 120 | if [ -e /usr/src/php/.docker-delete-me ]; then 121 | docker-php-source delete 122 | fi 123 | -------------------------------------------------------------------------------- /php-debug-image-stretch/docker-php-source: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | dir=/usr/src/php 5 | 6 | usage() { 7 | echo "usage: $0 COMMAND" 8 | echo 9 | echo "Manage php source tarball lifecycle." 10 | echo 11 | echo "Commands:" 12 | echo " extract extract php source tarball into directory $dir if not already done." 13 | echo " delete delete extracted php source located into $dir if not already done." 14 | echo 15 | } 16 | 17 | case "$1" in 18 | extract) 19 | mkdir -p "$dir" 20 | if [ ! -f "$dir/.docker-extracted" ]; then 21 | tar -Jxf /usr/src/php.tar.xz -C "$dir" --strip-components=1 22 | touch "$dir/.docker-extracted" 23 | fi 24 | ;; 25 | 26 | delete) 27 | rm -rf "$dir" 28 | ;; 29 | 30 | *) 31 | usage 32 | exit 1 33 | ;; 34 | esac 35 | -------------------------------------------------------------------------------- /php-debug-image/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM debian:buster-slim 2 | 3 | # prevent Debian's PHP packages from being installed 4 | # https://github.com/docker-library/php/pull/542 5 | RUN set -eux; \ 6 | { \ 7 | echo 'Package: php*'; \ 8 | echo 'Pin: release *'; \ 9 | echo 'Pin-Priority: -1'; \ 10 | } > /etc/apt/preferences.d/no-debian-php 11 | 12 | # dependencies required for running "phpize" 13 | # (see persistent deps below) 14 | ENV PHPIZE_DEPS \ 15 | autoconf \ 16 | dpkg-dev \ 17 | file \ 18 | g++ \ 19 | gcc \ 20 | libc-dev \ 21 | make \ 22 | pkg-config \ 23 | re2c 24 | 25 | # persistent / runtime deps 26 | RUN set -eux; \ 27 | apt-get update; \ 28 | apt-get install -y --no-install-recommends \ 29 | $PHPIZE_DEPS \ 30 | ca-certificates \ 31 | systemtap-sdt-dev \ 32 | curl \ 33 | xz-utils \ 34 | ; \ 35 | rm -rf /var/lib/apt/lists/* 36 | 37 | ENV PHP_INI_DIR /usr/local/etc/php 38 | RUN set -eux; \ 39 | mkdir -p "$PHP_INI_DIR/conf.d"; \ 40 | # allow running as an arbitrary user (https://github.com/docker-library/php/issues/743) 41 | [ ! -d /var/www/html ]; \ 42 | mkdir -p /var/www/html; \ 43 | chown www-data:www-data /var/www/html; \ 44 | chmod 777 /var/www/html 45 | 46 | #### 47 | ENV PHP_EXTRA_CONFIGURE_ARGS --enable-fpm --with-fpm-user=www-data --with-fpm-group=www-data --disable-cgi 48 | #### 49 | 50 | # Apply stack smash protection to functions using local buffers and alloca() 51 | # Make PHP's main executable position-independent (improves ASLR security mechanism, and has no performance impact on x86_64) 52 | # Enable optimization (-O2) 53 | # Enable linker optimization (this sorts the hash buckets to improve cache locality, and is non-default) 54 | # Adds GNU HASH segments to generated executables (this is used if present, and is much faster than sysv hash; in this configuration, sysv hash is also generated) 55 | # https://github.com/docker-library/php/issues/272 56 | ENV PHP_CFLAGS="-fstack-protector-strong -fpic -fpie -O2" 57 | ENV PHP_CPPFLAGS="$PHP_CFLAGS" 58 | ENV PHP_LDFLAGS="-Wl,-O1 -Wl,--hash-style=both -pie" 59 | 60 | ENV GPG_KEYS CBAF69F173A0FEA4B537F470D66C9593118BCCB6 F38252826ACD957EF380D39F2F7956BC5DA04B5D 61 | 62 | ENV PHP_VERSION 7.3.7 63 | ENV PHP_URL="https://www.php.net/get/php-7.3.7.tar.xz/from/this/mirror" PHP_ASC_URL="https://www.php.net/get/php-7.3.7.tar.xz.asc/from/this/mirror" 64 | ENV PHP_SHA256="ba067200ba649956b3a92ec8b71a6ed8ce8a099921212443c1bcf3260a29274c" PHP_MD5="" 65 | 66 | ENV USE_ZEND_DTRACE 1 67 | 68 | RUN set -eux; \ 69 | \ 70 | savedAptMark="$(apt-mark showmanual)"; \ 71 | apt-get update; \ 72 | apt-get install -y --no-install-recommends gnupg dirmngr; \ 73 | rm -rf /var/lib/apt/lists/*; \ 74 | \ 75 | mkdir -p /usr/src; \ 76 | cd /usr/src; \ 77 | \ 78 | curl -fsSL -o php.tar.xz "$PHP_URL"; \ 79 | \ 80 | if [ -n "$PHP_SHA256" ]; then \ 81 | echo "$PHP_SHA256 *php.tar.xz" | sha256sum -c -; \ 82 | fi; \ 83 | if [ -n "$PHP_MD5" ]; then \ 84 | echo "$PHP_MD5 *php.tar.xz" | md5sum -c -; \ 85 | fi; \ 86 | \ 87 | if [ -n "$PHP_ASC_URL" ]; then \ 88 | curl -fsSL -o php.tar.xz.asc "$PHP_ASC_URL"; \ 89 | export GNUPGHOME="$(mktemp -d)"; \ 90 | for key in $GPG_KEYS; do \ 91 | gpg --batch --keyserver ha.pool.sks-keyservers.net --recv-keys "$key"; \ 92 | done; \ 93 | gpg --batch --verify php.tar.xz.asc php.tar.xz; \ 94 | gpgconf --kill all; \ 95 | rm -rf "$GNUPGHOME"; \ 96 | fi; \ 97 | \ 98 | apt-mark auto '.*' > /dev/null; \ 99 | apt-mark manual $savedAptMark > /dev/null; \ 100 | apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false 101 | 102 | COPY docker-php-source /usr/local/bin/ 103 | 104 | RUN set -eux; \ 105 | \ 106 | savedAptMark="$(apt-mark showmanual)"; \ 107 | apt-get update; \ 108 | apt-get install -y --no-install-recommends \ 109 | libargon2-dev \ 110 | libcurl4-openssl-dev \ 111 | libedit-dev \ 112 | libsodium-dev \ 113 | libsqlite3-dev \ 114 | libssl-dev \ 115 | libxml2-dev \ 116 | zlib1g-dev \ 117 | ${PHP_EXTRA_BUILD_DEPS:-} \ 118 | ; \ 119 | rm -rf /var/lib/apt/lists/*; \ 120 | \ 121 | export \ 122 | CFLAGS="$PHP_CFLAGS" \ 123 | CPPFLAGS="$PHP_CPPFLAGS" \ 124 | LDFLAGS="$PHP_LDFLAGS" \ 125 | ; \ 126 | docker-php-source extract; \ 127 | cd /usr/src/php; \ 128 | gnuArch="$(dpkg-architecture --query DEB_BUILD_GNU_TYPE)"; \ 129 | debMultiarch="$(dpkg-architecture --query DEB_BUILD_MULTIARCH)"; \ 130 | # https://bugs.php.net/bug.php?id=74125 131 | if [ ! -d /usr/include/curl ]; then \ 132 | ln -sT "/usr/include/$debMultiarch/curl" /usr/local/include/curl; \ 133 | fi; \ 134 | ./configure \ 135 | --enable-dtrace \ 136 | --build="$gnuArch" \ 137 | --with-config-file-path="$PHP_INI_DIR" \ 138 | --with-config-file-scan-dir="$PHP_INI_DIR/conf.d" \ 139 | \ 140 | # make sure invalid --configure-flags are fatal errors intead of just warnings 141 | --enable-option-checking=fatal \ 142 | \ 143 | # https://github.com/docker-library/php/issues/439 144 | --with-mhash \ 145 | \ 146 | # --enable-ftp is included here because ftp_ssl_connect() needs ftp to be compiled statically (see https://github.com/docker-library/php/issues/236) 147 | --enable-ftp \ 148 | # --enable-mbstring is included here because otherwise there's no way to get pecl to use it properly (see https://github.com/docker-library/php/issues/195) 149 | --enable-mbstring \ 150 | # --enable-mysqlnd is included here because it's harder to compile after the fact than extensions are (since it's a plugin for several extensions, not an extension in itself) 151 | --enable-mysqlnd \ 152 | # https://wiki.php.net/rfc/argon2_password_hash (7.2+) 153 | --with-password-argon2 \ 154 | # https://wiki.php.net/rfc/libsodium 155 | --with-sodium=shared \ 156 | \ 157 | --with-curl \ 158 | --with-libedit \ 159 | --with-openssl \ 160 | --with-zlib \ 161 | \ 162 | # bundled pcre does not support JIT on s390x 163 | # https://manpages.debian.org/stretch/libpcre3-dev/pcrejit.3.en.html#AVAILABILITY_OF_JIT_SUPPORT 164 | $(test "$gnuArch" = 's390x-linux-gnu' && echo '--without-pcre-jit') \ 165 | --with-libdir="lib/$debMultiarch" \ 166 | \ 167 | ${PHP_EXTRA_CONFIGURE_ARGS:-} \ 168 | ; \ 169 | make -j "$(nproc)"; \ 170 | find -type f -name '*.a' -delete; \ 171 | make install; \ 172 | find /usr/local/bin /usr/local/sbin -type f -executable -exec strip --strip-all '{}' + || true; \ 173 | make clean; \ 174 | \ 175 | # https://github.com/docker-library/php/issues/692 (copy default example "php.ini" files somewhere easily discoverable) 176 | cp -v php.ini-* "$PHP_INI_DIR/"; \ 177 | \ 178 | cd /; \ 179 | docker-php-source delete; \ 180 | \ 181 | # reset apt-mark's "manual" list so that "purge --auto-remove" will remove all build dependencies 182 | apt-mark auto '.*' > /dev/null; \ 183 | [ -z "$savedAptMark" ] || apt-mark manual $savedAptMark; \ 184 | find /usr/local -type f -executable -exec ldd '{}' ';' \ 185 | | awk '/=>/ { print $(NF-1) }' \ 186 | | sort -u \ 187 | | xargs -r dpkg-query --search \ 188 | | cut -d: -f1 \ 189 | | sort -u \ 190 | | xargs -r apt-mark manual \ 191 | ; \ 192 | apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false; \ 193 | \ 194 | # update pecl channel definitions https://github.com/docker-library/php/issues/443 195 | pecl update-channels; \ 196 | rm -rf /tmp/pear ~/.pearrc; \ 197 | # smoke test 198 | php --version 199 | 200 | COPY docker-php-ext-* docker-php-entrypoint /usr/local/bin/ 201 | 202 | # sodium was built as a shared module (so that it can be replaced later if so desired), so let's enable it too (https://github.com/docker-library/php/issues/598) 203 | RUN docker-php-ext-enable sodium 204 | 205 | # temporary "freetype-config" workaround for https://github.com/docker-library/php/issues/865 (https://bugs.php.net/bug.php?id=76324) 206 | RUN { echo '#!/bin/sh'; echo 'exec pkg-config "$@" freetype2'; } > /usr/local/bin/freetype-config && chmod +x /usr/local/bin/freetype-config 207 | 208 | ENTRYPOINT ["docker-php-entrypoint"] 209 | #### 210 | WORKDIR /var/www/html 211 | 212 | RUN set -eux; \ 213 | cd /usr/local/etc; \ 214 | if [ -d php-fpm.d ]; then \ 215 | # for some reason, upstream's php-fpm.conf.default has "include=NONE/etc/php-fpm.d/*.conf" 216 | sed 's!=NONE/!=!g' php-fpm.conf.default | tee php-fpm.conf > /dev/null; \ 217 | cp php-fpm.d/www.conf.default php-fpm.d/www.conf; \ 218 | else \ 219 | # PHP 5.x doesn't use "include=" by default, so we'll create our own simple config that mimics PHP 7+ for consistency 220 | mkdir php-fpm.d; \ 221 | cp php-fpm.conf.default php-fpm.d/www.conf; \ 222 | { \ 223 | echo '[global]'; \ 224 | echo 'include=etc/php-fpm.d/*.conf'; \ 225 | } | tee php-fpm.conf; \ 226 | fi; \ 227 | { \ 228 | echo '[global]'; \ 229 | echo 'error_log = /proc/self/fd/2'; \ 230 | echo; echo '; https://github.com/docker-library/php/pull/725#issuecomment-443540114'; echo 'log_limit = 8192'; \ 231 | echo; \ 232 | echo '[www]'; \ 233 | echo '; if we send this to /proc/self/fd/1, it never appears'; \ 234 | echo 'access.log = /proc/self/fd/2'; \ 235 | echo; \ 236 | echo 'clear_env = no'; \ 237 | echo; \ 238 | echo '; Ensure worker stdout and stderr are sent to the main error log.'; \ 239 | echo 'catch_workers_output = yes'; \ 240 | echo 'decorate_workers_output = no'; \ 241 | } | tee php-fpm.d/docker.conf; \ 242 | { \ 243 | echo '[global]'; \ 244 | echo 'daemonize = no'; \ 245 | echo; \ 246 | echo '[www]'; \ 247 | echo 'listen = 9000'; \ 248 | } | tee php-fpm.d/zz-docker.conf 249 | 250 | # Override stop signal to stop process gracefully 251 | # https://github.com/php/php-src/blob/17baa87faddc2550def3ae7314236826bc1b1398/sapi/fpm/php-fpm.8.in#L163 252 | STOPSIGNAL SIGQUIT 253 | 254 | EXPOSE 9000 255 | CMD ["php-fpm"] 256 | -------------------------------------------------------------------------------- /php-debug-image/Makefile: -------------------------------------------------------------------------------- 1 | build: 2 | docker build . -t php-debug 3 | -------------------------------------------------------------------------------- /php-debug-image/docker-php-entrypoint: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | # first arg is `-f` or `--some-option` 5 | if [ "${1#-}" != "$1" ]; then 6 | set -- php-fpm "$@" 7 | fi 8 | 9 | exec "$@" 10 | -------------------------------------------------------------------------------- /php-debug-image/docker-php-ext-configure: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | # prefer user supplied CFLAGS, but default to our PHP_CFLAGS 5 | : ${CFLAGS:=$PHP_CFLAGS} 6 | : ${CPPFLAGS:=$PHP_CPPFLAGS} 7 | : ${LDFLAGS:=$PHP_LDFLAGS} 8 | export CFLAGS CPPFLAGS LDFLAGS 9 | 10 | srcExists= 11 | if [ -d /usr/src/php ]; then 12 | srcExists=1 13 | fi 14 | docker-php-source extract 15 | if [ -z "$srcExists" ]; then 16 | touch /usr/src/php/.docker-delete-me 17 | fi 18 | 19 | cd /usr/src/php/ext 20 | 21 | usage() { 22 | echo "usage: $0 ext-name [configure flags]" 23 | echo " ie: $0 gd --with-jpeg-dir=/usr/local/something" 24 | echo 25 | echo 'Possible values for ext-name:' 26 | find . \ 27 | -mindepth 2 \ 28 | -maxdepth 2 \ 29 | -type f \ 30 | -name 'config.m4' \ 31 | | xargs -n1 dirname \ 32 | | xargs -n1 basename \ 33 | | sort \ 34 | | xargs 35 | echo 36 | echo 'Some of the above modules are already compiled into PHP; please check' 37 | echo 'the output of "php -i" to see which modules are already loaded.' 38 | } 39 | 40 | ext="$1" 41 | if [ -z "$ext" ] || [ ! -d "$ext" ]; then 42 | usage >&2 43 | exit 1 44 | fi 45 | shift 46 | 47 | pm='unknown' 48 | if [ -e /lib/apk/db/installed ]; then 49 | pm='apk' 50 | fi 51 | 52 | if [ "$pm" = 'apk' ]; then 53 | if \ 54 | [ -n "$PHPIZE_DEPS" ] \ 55 | && ! apk info --installed .phpize-deps > /dev/null \ 56 | && ! apk info --installed .phpize-deps-configure > /dev/null \ 57 | ; then 58 | apk add --no-cache --virtual .phpize-deps-configure $PHPIZE_DEPS 59 | fi 60 | fi 61 | 62 | if command -v dpkg-architecture > /dev/null; then 63 | gnuArch="$(dpkg-architecture --query DEB_BUILD_GNU_TYPE)" 64 | set -- --build="$gnuArch" "$@" 65 | fi 66 | 67 | cd "$ext" 68 | phpize 69 | ./configure "$@" 70 | -------------------------------------------------------------------------------- /php-debug-image/docker-php-ext-enable: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | extDir="$(php -d 'display_errors=stderr' -r 'echo ini_get("extension_dir");')" 5 | cd "$extDir" 6 | 7 | usage() { 8 | echo "usage: $0 [options] module-name [module-name ...]" 9 | echo " ie: $0 gd mysqli" 10 | echo " $0 pdo pdo_mysql" 11 | echo " $0 --ini-name 0-apc.ini apcu apc" 12 | echo 13 | echo 'Possible values for module-name:' 14 | find -maxdepth 1 \ 15 | -type f \ 16 | -name '*.so' \ 17 | -exec basename '{}' ';' \ 18 | | sort \ 19 | | xargs 20 | echo 21 | echo 'Some of the above modules are already compiled into PHP; please check' 22 | echo 'the output of "php -i" to see which modules are already loaded.' 23 | } 24 | 25 | opts="$(getopt -o 'h?' --long 'help,ini-name:' -- "$@" || { usage >&2 && false; })" 26 | eval set -- "$opts" 27 | 28 | iniName= 29 | while true; do 30 | flag="$1" 31 | shift 32 | case "$flag" in 33 | --help|-h|'-?') usage && exit 0 ;; 34 | --ini-name) iniName="$1" && shift ;; 35 | --) break ;; 36 | *) 37 | { 38 | echo "error: unknown flag: $flag" 39 | usage 40 | } >&2 41 | exit 1 42 | ;; 43 | esac 44 | done 45 | 46 | modules= 47 | for module; do 48 | if [ -z "$module" ]; then 49 | continue 50 | fi 51 | if [ -f "$module.so" ] && ! [ -f "$module" ]; then 52 | # allow ".so" to be optional 53 | module="$module.so" 54 | fi 55 | if ! [ -f "$module" ]; then 56 | echo >&2 "error: '$module' does not exist" 57 | echo >&2 58 | usage >&2 59 | exit 1 60 | fi 61 | modules="$modules $module" 62 | done 63 | 64 | if [ -z "$modules" ]; then 65 | usage >&2 66 | exit 1 67 | fi 68 | 69 | pm='unknown' 70 | if [ -e /lib/apk/db/installed ]; then 71 | pm='apk' 72 | fi 73 | 74 | apkDel= 75 | if [ "$pm" = 'apk' ]; then 76 | if \ 77 | [ -n "$PHPIZE_DEPS" ] \ 78 | && ! apk info --installed .phpize-deps > /dev/null \ 79 | && ! apk info --installed .phpize-deps-configure > /dev/null \ 80 | ; then 81 | apk add --no-cache --virtual '.docker-php-ext-enable-deps' binutils 82 | apkDel='.docker-php-ext-enable-deps' 83 | fi 84 | fi 85 | 86 | for module in $modules; do 87 | if readelf --wide --syms "$module" | grep -q ' zend_extension_entry$'; then 88 | # https://wiki.php.net/internals/extensions#loading_zend_extensions 89 | absModule="$(readlink -f "$module")" 90 | line="zend_extension=$absModule" 91 | else 92 | line="extension=$module" 93 | fi 94 | 95 | ext="$(basename "$module")" 96 | ext="${ext%.*}" 97 | if php -d 'display_errors=stderr' -r 'exit(extension_loaded("'"$ext"'") ? 0 : 1);'; then 98 | # this isn't perfect, but it's better than nothing 99 | # (for example, 'opcache.so' presents inside PHP as 'Zend OPcache', not 'opcache') 100 | echo >&2 101 | echo >&2 "warning: $ext ($module) is already loaded!" 102 | echo >&2 103 | continue 104 | fi 105 | 106 | ini="$PHP_INI_DIR/conf.d/${iniName:-"docker-php-ext-$ext.ini"}" 107 | if ! grep -q "$line" "$ini" 2>/dev/null; then 108 | echo "$line" >> "$ini" 109 | fi 110 | done 111 | 112 | if [ "$pm" = 'apk' ] && [ -n "$apkDel" ]; then 113 | apk del --no-network $apkDel 114 | fi 115 | -------------------------------------------------------------------------------- /php-debug-image/docker-php-ext-install: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | # prefer user supplied CFLAGS, but default to our PHP_CFLAGS 5 | : ${CFLAGS:=$PHP_CFLAGS} 6 | : ${CPPFLAGS:=$PHP_CPPFLAGS} 7 | : ${LDFLAGS:=$PHP_LDFLAGS} 8 | export CFLAGS CPPFLAGS LDFLAGS 9 | 10 | srcExists= 11 | if [ -d /usr/src/php ]; then 12 | srcExists=1 13 | fi 14 | docker-php-source extract 15 | if [ -z "$srcExists" ]; then 16 | touch /usr/src/php/.docker-delete-me 17 | fi 18 | 19 | cd /usr/src/php/ext 20 | 21 | usage() { 22 | echo "usage: $0 [-jN] ext-name [ext-name ...]" 23 | echo " ie: $0 gd mysqli" 24 | echo " $0 pdo pdo_mysql" 25 | echo " $0 -j5 gd mbstring mysqli pdo pdo_mysql shmop" 26 | echo 27 | echo 'if custom ./configure arguments are necessary, see docker-php-ext-configure' 28 | echo 29 | echo 'Possible values for ext-name:' 30 | find . \ 31 | -mindepth 2 \ 32 | -maxdepth 2 \ 33 | -type f \ 34 | -name 'config.m4' \ 35 | | xargs -n1 dirname \ 36 | | xargs -n1 basename \ 37 | | sort \ 38 | | xargs 39 | echo 40 | echo 'Some of the above modules are already compiled into PHP; please check' 41 | echo 'the output of "php -i" to see which modules are already loaded.' 42 | } 43 | 44 | opts="$(getopt -o 'h?j:' --long 'help,jobs:' -- "$@" || { usage >&2 && false; })" 45 | eval set -- "$opts" 46 | 47 | j=1 48 | while true; do 49 | flag="$1" 50 | shift 51 | case "$flag" in 52 | --help|-h|'-?') usage && exit 0 ;; 53 | --jobs|-j) j="$1" && shift ;; 54 | --) break ;; 55 | *) 56 | { 57 | echo "error: unknown flag: $flag" 58 | usage 59 | } >&2 60 | exit 1 61 | ;; 62 | esac 63 | done 64 | 65 | exts= 66 | for ext; do 67 | if [ -z "$ext" ]; then 68 | continue 69 | fi 70 | if [ ! -d "$ext" ]; then 71 | echo >&2 "error: $PWD/$ext does not exist" 72 | echo >&2 73 | usage >&2 74 | exit 1 75 | fi 76 | exts="$exts $ext" 77 | done 78 | 79 | if [ -z "$exts" ]; then 80 | usage >&2 81 | exit 1 82 | fi 83 | 84 | pm='unknown' 85 | if [ -e /lib/apk/db/installed ]; then 86 | pm='apk' 87 | fi 88 | 89 | apkDel= 90 | if [ "$pm" = 'apk' ]; then 91 | if [ -n "$PHPIZE_DEPS" ]; then 92 | if apk info --installed .phpize-deps-configure > /dev/null; then 93 | apkDel='.phpize-deps-configure' 94 | elif ! apk info --installed .phpize-deps > /dev/null; then 95 | apk add --no-cache --virtual .phpize-deps $PHPIZE_DEPS 96 | apkDel='.phpize-deps' 97 | fi 98 | fi 99 | fi 100 | 101 | popDir="$PWD" 102 | for ext in $exts; do 103 | cd "$ext" 104 | [ -e Makefile ] || docker-php-ext-configure "$ext" 105 | make -j"$j" 106 | make -j"$j" install 107 | find modules \ 108 | -maxdepth 1 \ 109 | -name '*.so' \ 110 | -exec basename '{}' ';' \ 111 | | xargs -r docker-php-ext-enable 112 | make -j"$j" clean 113 | cd "$popDir" 114 | done 115 | 116 | if [ "$pm" = 'apk' ] && [ -n "$apkDel" ]; then 117 | apk del --no-network $apkDel 118 | fi 119 | 120 | if [ -e /usr/src/php/.docker-delete-me ]; then 121 | docker-php-source delete 122 | fi 123 | -------------------------------------------------------------------------------- /php-debug-image/docker-php-source: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | dir=/usr/src/php 5 | 6 | usage() { 7 | echo "usage: $0 COMMAND" 8 | echo 9 | echo "Manage php source tarball lifecycle." 10 | echo 11 | echo "Commands:" 12 | echo " extract extract php source tarball into directory $dir if not already done." 13 | echo " delete delete extracted php source located into $dir if not already done." 14 | echo 15 | } 16 | 17 | case "$1" in 18 | extract) 19 | mkdir -p "$dir" 20 | if [ ! -f "$dir/.docker-extracted" ]; then 21 | tar -Jxf /usr/src/php.tar.xz -C "$dir" --strip-components=1 22 | touch "$dir/.docker-extracted" 23 | fi 24 | ;; 25 | 26 | delete) 27 | rm -rf "$dir" 28 | ;; 29 | 30 | *) 31 | usage 32 | exit 1 33 | ;; 34 | esac 35 | -------------------------------------------------------------------------------- /php_tool.bt: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bpftrace 2 | 3 | BEGIN 4 | { 5 | printf("PHP observe tool. Hit Ctrl-C to end.\n"); 6 | printf("%-8s %-8s %-16s %-42s %s\n", "TIME", "PID", "COMM", "FUNC", "LATMS"); 7 | } 8 | 9 | tracepoint:syscalls:sys_exit_socket, 10 | tracepoint:syscalls:sys_enter_socketpair, 11 | tracepoint:syscalls:sys_enter_bind, 12 | tracepoint:syscalls:sys_enter_listen, 13 | tracepoint:syscalls:sys_enter_accept4, 14 | tracepoint:syscalls:sys_enter_accept, 15 | tracepoint:syscalls:sys_enter_connect, 16 | tracepoint:syscalls:sys_enter_getsockname, 17 | tracepoint:syscalls:sys_enter_getpeername, 18 | tracepoint:syscalls:sys_enter_sendto, 19 | tracepoint:syscalls:sys_enter_recvfrom, 20 | tracepoint:syscalls:sys_enter_setsockopt, 21 | tracepoint:syscalls:sys_enter_getsockopt, 22 | tracepoint:syscalls:sys_enter_shutdown, 23 | tracepoint:syscalls:sys_enter_sendmsg, 24 | tracepoint:syscalls:sys_enter_sendmmsg, 25 | tracepoint:syscalls:sys_enter_recvmsg, 26 | tracepoint:syscalls:sys_enter_recvmmsg, 27 | tracepoint:syscalls:sys_enter_read, 28 | tracepoint:syscalls:sys_enter_write, 29 | tracepoint:syscalls:sys_enter_sendfile64 30 | / pid == 31325 / 31 | { 32 | @syslat[0] = nsecs; 33 | } 34 | 35 | tracepoint:syscalls:sys_exit_socket, 36 | tracepoint:syscalls:sys_exit_socketpair, 37 | tracepoint:syscalls:sys_exit_bind, 38 | tracepoint:syscalls:sys_exit_listen, 39 | tracepoint:syscalls:sys_exit_accept4, 40 | tracepoint:syscalls:sys_exit_accept, 41 | tracepoint:syscalls:sys_exit_connect, 42 | tracepoint:syscalls:sys_exit_getsockname, 43 | tracepoint:syscalls:sys_exit_getpeername, 44 | tracepoint:syscalls:sys_exit_sendto, 45 | tracepoint:syscalls:sys_exit_recvfrom, 46 | tracepoint:syscalls:sys_exit_setsockopt, 47 | tracepoint:syscalls:sys_exit_getsockopt, 48 | tracepoint:syscalls:sys_exit_shutdown, 49 | tracepoint:syscalls:sys_exit_sendmsg, 50 | tracepoint:syscalls:sys_exit_sendmmsg, 51 | tracepoint:syscalls:sys_exit_recvmsg, 52 | tracepoint:syscalls:sys_exit_recvmmsg, 53 | tracepoint:syscalls:sys_exit_read, 54 | tracepoint:syscalls:sys_exit_write, 55 | tracepoint:syscalls:sys_exit_sendfile64 56 | / pid == 31325 / 57 | { 58 | $lat = (nsecs - @syslat[0]); 59 | time("%H:%M:%S "); 60 | printf("%-8d %-18s %-42s %d.ns\n", pid, comm, probe, $lat); 61 | delete(@syslat[0]); 62 | } 63 | 64 | usdt::function__entry 65 | { 66 | @start[tid] = nsecs; 67 | time("%H:%M:%S "); 68 | printf("%-8d %-16s %s->%-42s\n", pid, comm, str(arg3), str(arg0)); 69 | } 70 | 71 | usdt::function__return 72 | / @start[tid] / 73 | { 74 | $latms = (nsecs - @start[tid]) / 1000000; 75 | time("%H:%M:%S "); 76 | printf("%-8d %-16s %s->%-42s %d.ms\n", pid, comm, str(arg3), str(arg0), $latms); 77 | delete(@start[tid]); 78 | } 79 | -------------------------------------------------------------------------------- /php_tool.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from __future__ import print_function 4 | from bcc import BPF, USDT 5 | import argparse 6 | import ctypes as ct 7 | import time 8 | import os 9 | import io 10 | import ipaddress 11 | import socket 12 | from collections import defaultdict 13 | 14 | # globals 15 | SYSCALLS = ["socket", "socketpair", "bind", "listen", "accept", "accept4", 16 | "connect", "getsockname", "getpeername", "sendto", "recvfrom", 17 | "setsockopt", "getsockopt", "shutdown", "sendmsg", "sendmmsg", 18 | "recvmsg", "recvmmsg", "read", "write", "open", "openat", "creat", 19 | "close", "sendfile64"] 20 | 21 | SYSCALL = 1 22 | DISK = 3 23 | NET = 4 24 | PADDING = " " 25 | BLUE = '\033[95m' 26 | UNDERLINE = '\033[4m' 27 | ENDC = '\033[0m' 28 | 29 | # C result class 30 | 31 | class CallEvent(ct.Structure): 32 | _fields_ = [ 33 | ("depth", ct.c_ulonglong), 34 | ("pid", ct.c_ulonglong), 35 | ("lat", ct.c_ulonglong), 36 | ("type", ct.c_ulonglong), 37 | ("fd_type", ct.c_ulonglong), 38 | ("fdw", ct.c_ulonglong), 39 | ("fdr", ct.c_ulonglong), 40 | ("fd_ret", ct.c_ulonglong), 41 | ("bytes_write", ct.c_ulonglong), 42 | ("bytes_read", ct.c_ulonglong), 43 | ("addr", ct.c_ulonglong), 44 | ("clazz", ct.c_char * 80), 45 | ("method", ct.c_char * 80), 46 | ("file", ct.c_char * 80), 47 | ] 48 | 49 | 50 | ############################################################################### 51 | # TEMPLATES 52 | ############################################################################### 53 | 54 | # program template 55 | PROGRAM = """ 56 | #include 57 | #include 58 | 59 | struct call_t { 60 | u64 depth; // first bit is direction (0 entry, 1 return) 61 | u64 pid; // (tgid << 32) + pid from bpf_get_current... 62 | u64 lat; // time latency 63 | u64 type; // syscall or php function 64 | u64 fd_type; // disk or net filedescriptor 65 | u64 fdw; // filedescriptor write 66 | u64 fdr; // filedescriptor read 67 | u64 fd_ret; // returned filedescriptor 68 | u64 bytes_write; // number of write bytes 69 | u64 bytes_read; // number of read bytes 70 | u64 addr; // addr to connect 71 | char clazz[80]; // class name 72 | char method[80]; // method name 73 | char file[80]; // php file name 74 | }; 75 | 76 | #define SYS 1 77 | #define FUNC 2 78 | #define DISK 3 79 | #define NET 4 80 | 81 | BPF_PERF_OUTPUT(calls); 82 | BPF_HASH(entry, u64, u64); 83 | BPF_HASH(start, u64, u64); 84 | BPF_HASH(start_func, u64, u64); 85 | BPF_HASH(fd, u64, u64); 86 | BPF_HASH(addr, u64, u64); 87 | BPF_HASH(filedescriptors, u64, u64); 88 | """ 89 | 90 | # php probes template 91 | PHP_TRACE_TEMPLATE = """ 92 | int {name}(struct pt_regs *ctx) {{ 93 | u64 *depth, zero = 0, clazz = 0, method = 0, file = 0; 94 | struct call_t data = {{}}; 95 | u64 pid = bpf_get_current_pid_tgid(); 96 | 97 | {read_class} 98 | {read_method} 99 | {read_file} 100 | bpf_probe_read(&data.clazz, sizeof(data.clazz), (void *)clazz); 101 | bpf_probe_read(&data.method, sizeof(data.method), (void *)method); 102 | bpf_probe_read(&data.file, sizeof(data.file), (void *)file); 103 | u64 id = clazz + method + file; 104 | 105 | data.type = FUNC; 106 | data.pid = pid; 107 | depth = entry.lookup_or_init(&data.pid, &zero); 108 | data.depth = {depth}; 109 | {update_func} 110 | 111 | if (!(data.depth & (1ULL << 63))) {{ 112 | u64 time = bpf_ktime_get_ns(); 113 | start_func.update(&id, &time); 114 | }} else {{ 115 | u64 *start_ns = start_func.lookup(&id); 116 | if (!start_ns) {{ 117 | calls.perf_submit(ctx, &data, sizeof(data)); 118 | start_func.delete(&id); 119 | return 0; 120 | }} 121 | data.lat = bpf_ktime_get_ns() - *start_ns; 122 | start_func.delete(&method); 123 | }} 124 | 125 | calls.perf_submit(ctx, &data, sizeof(data)); 126 | return 0; 127 | }} 128 | """ 129 | 130 | # syscall tracepoint template 131 | SYS_TRACE_TEMPLATE = """ 132 | TRACEPOINT_PROBE(syscalls, sys_enter_{syscall_name}) {{ 133 | u64 pid = bpf_get_current_pid_tgid(); 134 | if ({pid_condition}) {{ 135 | return 0; 136 | }} 137 | u64 time = bpf_ktime_get_ns(); 138 | start.update(&pid, &time); 139 | {syscall_enter_logic} 140 | return 0; 141 | }} 142 | 143 | TRACEPOINT_PROBE(syscalls, sys_exit_{syscall_name}) {{ 144 | u64 pid = bpf_get_current_pid_tgid(); 145 | if ({pid_condition}) {{ 146 | return 0; 147 | }} 148 | 149 | u64 *depth, zero = 0, clazz = 0, method = 0; 150 | 151 | struct call_t data = {{}}; 152 | data.type = SYS; 153 | data.pid = pid; 154 | depth = entry.lookup_or_init(&data.pid, &zero); 155 | data.depth = *depth; 156 | char method_str[80] = "{syscall_name}"; 157 | bpf_probe_read(&data.method, sizeof(data.method), method_str); 158 | 159 | u64 *start_ns = start.lookup(&pid); 160 | if (!start_ns) {{ 161 | calls.perf_submit(args, &data, sizeof(data)); 162 | return 0; 163 | }} 164 | 165 | data.lat = bpf_ktime_get_ns() - *start_ns; 166 | {syscall_exit_logic} 167 | calls.perf_submit(args, &data, sizeof(data)); 168 | return 0; 169 | }} 170 | """ 171 | 172 | 173 | class SyscallEvents: 174 | e = defaultdict(list) 175 | 176 | def event(self, syscalls, enter, exit): 177 | for syscall in syscalls: 178 | self.e[syscall].append((enter, exit)) 179 | 180 | def plugin(self, syscall): 181 | return ("".join(a[0] for a in self.e[syscall]), 182 | "".join(a[1] for a in self.e[syscall])) 183 | 184 | def pid_condition(self, pids): 185 | return " && ".join("pid >> 32 != %s" % str(pid) for pid in pids) 186 | 187 | def syscall(self, pids, syscall): 188 | enter, exit = self.plugin(syscall) 189 | return SYS_TRACE_TEMPLATE.format(syscall_name=syscall, 190 | pid_condition=self.pid_condition(pids), 191 | syscall_enter_logic=enter, 192 | syscall_exit_logic=exit 193 | ) 194 | 195 | def generate(self, pids, syscalls=None): 196 | if syscalls is None: 197 | global SYSCALLS 198 | syscalls = SYSCALLS 199 | return "".join(self.syscall(pids, syscall) for syscall in syscalls) 200 | 201 | 202 | def print_event(pid, lat, message, depth): 203 | return ("%-6d %-10s %-40s" % 204 | (pid, str(lat), (PADDING * (depth - 1)) + message)) 205 | 206 | 207 | def syscall_message(event): 208 | message = io.StringIO() 209 | message.write("sys.") 210 | message.write(BLUE) 211 | message.write(event.method.decode("utf-8", "replace")) 212 | message.write(ENDC) 213 | if event.fdw > 0: 214 | message.write(" write on fd: %s" % event.fdw) 215 | if event.fdr > 0: 216 | message.write(" read fd: %s" % event.fdr) 217 | if event.fd_ret > 0: 218 | message.write(" return fd: %s" % event.fd_ret) 219 | 220 | if event.addr > 0: 221 | addr = str(ipaddress.ip_address(event.addr)) 222 | rev = addr.split('.')[::-1] 223 | addr = '.'.join(rev) 224 | message.write(" connect to: %s" % addr) 225 | try: 226 | host = socket.gethostbyaddr(addr) 227 | message.write(" -> %s" % host[0]) 228 | except socket.herror or socket.gaierror: 229 | pass 230 | return message.getvalue() 231 | 232 | 233 | class Process: 234 | total_lat = 0 235 | total_net_time = 0 236 | total_disk_time = 0 237 | net_write_volume = 0 238 | disk_write_volume = 0 239 | net_read_volume = 0 240 | disk_read_volume = 0 241 | 242 | def __init__(self): 243 | self.data_buffer = io.StringIO() 244 | 245 | def reset(self): 246 | self.total_lat = 0 247 | self.total_net_lat = 0 248 | self.total_disk_lat = 0 249 | self.net_write_volume = 0 250 | self.disk_write_volume = 0 251 | self.net_read_volume = 0 252 | self.disk_read_volume = 0 253 | 254 | def add_in_buffer(self, data): 255 | self.data_buffer.write(data + '\n') 256 | 257 | def get_buffer(self): 258 | return self.data_buffer.getvalue() 259 | 260 | 261 | class Callback: 262 | process_dict = defaultdict(Process) 263 | 264 | def __init__(self, args): 265 | self.args = args 266 | 267 | def __call__(self, cpu, data, size): 268 | event = ct.cast(data, ct.POINTER(CallEvent)).contents 269 | depth = event.depth & (~(1 << 63)) 270 | if depth == 0: 271 | return 272 | process = self.process_dict[str(event.pid)] 273 | if event.type == SYSCALL: 274 | process.total_lat += event.lat 275 | 276 | if event.fd_type == NET: 277 | process.total_net_time += event.lat 278 | 279 | if event.bytes_write > 0: 280 | process.net_write_volume += event.bytes_write 281 | elif event.bytes_read > 0: 282 | process.net_read_volume += event.bytes_read 283 | 284 | elif event.fd_type == DISK: 285 | process.total_disk_time += event.lat 286 | 287 | if event.bytes_write > 0: 288 | process.disk_write_volume += event.bytes_write 289 | elif event.bytes_read > 0: 290 | process.disk_read_volume += event.bytes_read 291 | 292 | if not self.args.syscalls: 293 | return 294 | 295 | process.add_in_buffer(print_event( 296 | event.pid >> 32, 297 | event.lat, 298 | syscall_message(event), 299 | depth 300 | )) 301 | 302 | else: 303 | # Return function case 304 | if event.depth & (1 << 63): 305 | direction = "<- " 306 | 307 | if SYSCALLS: 308 | if process.total_lat > 0: 309 | process.add_in_buffer(print_event( 310 | event.pid >> 32, 311 | process.total_lat, 312 | BLUE + "traced syscalls total latence" + ENDC, 313 | depth 314 | )) 315 | 316 | if process.total_net_time > 0: 317 | process.add_in_buffer( 318 | print_event( 319 | event.pid >> 32, process.total_net_time, BLUE + ( 320 | "sys time spent on the network |-> %d bytes written, %d bytes read" % 321 | (process.net_write_volume, process.net_read_volume)) + ENDC, depth)) 322 | 323 | if process.total_disk_time > 0: 324 | process.add_in_buffer( 325 | print_event( 326 | event.pid >> 32, process.total_disk_time, BLUE + ( 327 | "sys time spent on the disk |-> %d bytes written, %d bytes read" % 328 | (process.disk_write_volume, process.disk_read_volume)) + ENDC, depth)) 329 | 330 | # reset counters 331 | process.reset() 332 | # Entry function case 333 | else: 334 | direction = "-> " 335 | 336 | process.add_in_buffer( 337 | print_event( 338 | event.pid >> 32, 339 | str( 340 | event.lat) if event.lat > 0 else "-", 341 | "".join( 342 | (direction, 343 | event.clazz.decode( 344 | 'utf-8', 345 | 'replace'), 346 | ".", 347 | event.method.decode( 348 | 'utf-8', 349 | 'replace'), 350 | " ", 351 | UNDERLINE, 352 | "from ", 353 | event.file.decode( 354 | 'utf-8', 355 | 'replace'), 356 | ENDC)), 357 | depth)) 358 | # Quit the program on the last main return 359 | if event.depth & ( 360 | 1 << 63) and event.method.decode( 361 | 'utf-8', 362 | 'replace') == "main" and depth == 1: 363 | print(process.get_buffer()) 364 | del self.process_dict[str(event.pid)] 365 | if not self.process_dict: 366 | exit() 367 | 368 | 369 | class PHPEvents: 370 | usdt_tab = [] 371 | txt = [] 372 | probes = [] 373 | 374 | def probe(self, pids, probe_name, func_name, read_class, read_method, 375 | read_file, is_return=False): 376 | "Generate the c for php probes" 377 | depth = "*depth + 1" if not is_return else "*depth | (1ULL << 63)" 378 | update = "++(*depth);" if not is_return else "if (*depth) --(*depth);" 379 | values = { 380 | 'name': func_name, 381 | 'read_class': read_class, 382 | 'read_method': read_method, 383 | 'read_file': read_file, 384 | 'depth': depth, 385 | 'update_func': update 386 | } 387 | self.txt.append(PHP_TRACE_TEMPLATE.format(**values)) 388 | self.probes.append((probe_name, func_name)) 389 | 390 | def generate(self, pids): 391 | for probe_name, func_name in self.probes: 392 | for pid in pids: 393 | usdt = USDT(pid=pid) 394 | usdt.enable_probe_or_bail(probe_name, func_name) 395 | self.usdt_tab.append(usdt) 396 | return "".join(self.txt) 397 | 398 | 399 | def c_program(pids): 400 | "Generate the C program" 401 | program = io.StringIO() 402 | program.write(PROGRAM) 403 | php = PHPEvents() 404 | 405 | php.probe(pids, 406 | "function__entry", 407 | "php_entry", 408 | "bpf_usdt_readarg(4, ctx, &clazz);", 409 | "bpf_usdt_readarg(1, ctx, &method);", 410 | "bpf_usdt_readarg(2, ctx, &file);", 411 | is_return=False) 412 | php.probe(pids, 413 | "function__return", 414 | "php_return", 415 | "bpf_usdt_readarg(4, ctx, &clazz);", 416 | "bpf_usdt_readarg(1, ctx, &method);", 417 | "bpf_usdt_readarg(2, ctx, &file);", 418 | is_return=True) 419 | 420 | program.write(php.generate(pids)) 421 | # trace syscalls 422 | 423 | s = SyscallEvents() 424 | # intercept when an open filedescriptor is read. get the fd for printing 425 | # and get the type for sort the latence in NET or DISK 426 | s.event(("read",), """ 427 | u64 fdarg = args->fd; 428 | fd.update(&pid, &fdarg); 429 | 430 | """, 431 | """ 432 | data.bytes_read = args->ret; 433 | u64 *fdarg = fd.lookup(&pid); 434 | if (fdarg) { 435 | data.fdr = *fdarg; 436 | fd.delete(&pid); 437 | u64 *fdt = filedescriptors.lookup(fdarg); 438 | if (fdt) { 439 | data.fd_type = *fdt; 440 | } 441 | } 442 | 443 | """) 444 | 445 | # intercept when write on an open filedescriptor. get the fd for printing 446 | # and get the type for sort the latence in NET or DISK 447 | s.event(("write", "sendto", "sendmsg"), """ 448 | u64 fdarg = args->fd; 449 | fd.update(&pid, &fdarg); 450 | 451 | """, 452 | """ 453 | data.bytes_write = args->ret; 454 | u64 *fdarg = fd.lookup(&pid); 455 | if (fdarg) { 456 | data.fdw = *fdarg; 457 | fd.delete(&pid); 458 | u64 *fdt = filedescriptors.lookup(fdarg); 459 | if (fdt) { 460 | data.fd_type = *fdt; 461 | } 462 | } 463 | 464 | """) 465 | 466 | # store in a map the filedescriptors when open or socket open it. 467 | # and store the type: NET or DISK 468 | s.event(("open", "openat", "creat"), "", """ 469 | u64 ret = args->ret; 470 | u64 flag = DISK; 471 | filedescriptors.update(&ret, &flag); 472 | data.fd_ret = ret; 473 | 474 | """) 475 | 476 | s.event(("socket",), "", """ 477 | u64 ret = args->ret; 478 | u64 flag = NET; 479 | filedescriptors.update(&ret, &flag); 480 | data.fd_ret = ret; 481 | 482 | """) 483 | 484 | # decorator for trace the address in the connect arg 485 | s.event(("connect",), """ 486 | struct sockaddr_in *useraddr = ((struct sockaddr_in *)(args->uservaddr)); 487 | u64 a = useraddr->sin_addr.s_addr; 488 | addr.update(&pid, &a); 489 | u64 fdarg = args->fd; 490 | fd.update(&pid, &fdarg); 491 | 492 | """, 493 | """ 494 | u64 *a = addr.lookup(&pid); 495 | if (a) { 496 | data.addr = *a; 497 | addr.delete(&pid); 498 | } 499 | u64 *fdarg = fd.lookup(&pid); 500 | if (fdarg) { 501 | data.fdw = *fdarg; 502 | fd.delete(&pid); 503 | } 504 | 505 | """) 506 | 507 | s.event(("bind",), """ 508 | struct sockaddr_in *useraddr = ((struct sockaddr_in *)(args->umyaddr)); 509 | u64 a = useraddr->sin_addr.s_addr; 510 | addr.update(&pid, &a); 511 | u64 fdarg = args->fd; 512 | fd.update(&pid, &fdarg); 513 | 514 | """, 515 | """ 516 | u64 *a = addr.lookup(&pid); 517 | if (a) { 518 | data.addr = *a; 519 | addr.delete(&pid); 520 | } 521 | u64 *fdarg = fd.lookup(&pid); 522 | if (fdarg) { 523 | data.fdw = *fdarg; 524 | fd.delete(&pid); 525 | } 526 | 527 | """) 528 | 529 | program.write(s.generate(pids)) 530 | return program.getvalue(), php.usdt_tab 531 | 532 | 533 | def main(): 534 | # cli arguments 535 | parser = argparse.ArgumentParser( 536 | description="php_tool", 537 | formatter_class=argparse.RawDescriptionHelpFormatter) 538 | parser.add_argument( 539 | "pid", 540 | type=int, 541 | nargs="+", 542 | help="process id to attach to") 543 | parser.add_argument( 544 | "--debug", action="store_true", 545 | help="debug mode: print the generated BPF program") 546 | parser.add_argument( 547 | "--check", action="store_true", 548 | help="print the generated BPF program and quit") 549 | parser.add_argument( 550 | "-S", "--syscalls", action="store_true", 551 | help="print the syscalls details inside each function") 552 | args = parser.parse_args() 553 | 554 | program, usdt_tab = c_program(args.pid) 555 | 556 | # debug options 557 | if args.check or args.debug: 558 | print(program) 559 | if args.check: 560 | exit() 561 | 562 | # inject the C program generated in eBPF 563 | bpf = BPF(text=program, usdt_contexts=usdt_tab) 564 | 565 | print("php super tool, pid = %s... Ctrl-C to quit." % (args.pid)) 566 | print("%-6s %-10s %s" % ("PID", "LAT", "METHOD")) 567 | 568 | # don't forget the page_cnt option for increase the ring buffer size 569 | bpf["calls"].open_perf_buffer(Callback(args), page_cnt=8192) 570 | while True: 571 | try: 572 | bpf.perf_buffer_poll() 573 | except KeyboardInterrupt: 574 | exit() 575 | 576 | 577 | if __name__ == "__main__": 578 | main() 579 | -------------------------------------------------------------------------------- /wordpress-debug-image/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM php-debug 2 | 3 | # install the PHP extensions we need (https://make.wordpress.org/hosting/handbook/handbook/server-environment/#php-extensions) 4 | RUN set -ex; \ 5 | \ 6 | savedAptMark="$(apt-mark showmanual)"; \ 7 | \ 8 | apt-get update; \ 9 | apt-get install -y --no-install-recommends \ 10 | libjpeg-dev \ 11 | libmagickwand-dev \ 12 | libpng-dev \ 13 | libzip-dev \ 14 | ; \ 15 | \ 16 | docker-php-ext-configure gd --with-png-dir=/usr --with-jpeg-dir=/usr; \ 17 | docker-php-ext-install -j "$(nproc)" \ 18 | bcmath \ 19 | exif \ 20 | gd \ 21 | mysqli \ 22 | opcache \ 23 | zip \ 24 | ; \ 25 | pecl install imagick-3.4.4; \ 26 | docker-php-ext-enable imagick; \ 27 | \ 28 | # reset apt-mark's "manual" list so that "purge --auto-remove" will remove all build dependencies 29 | apt-mark auto '.*' > /dev/null; \ 30 | apt-mark manual $savedAptMark; \ 31 | ldd "$(php -r 'echo ini_get("extension_dir");')"/*.so \ 32 | | awk '/=>/ { print $3 }' \ 33 | | sort -u \ 34 | | xargs -r dpkg-query -S \ 35 | | cut -d: -f1 \ 36 | | sort -u \ 37 | | xargs -rt apt-mark manual; \ 38 | \ 39 | apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false; \ 40 | rm -rf /var/lib/apt/lists/* 41 | 42 | # set recommended PHP.ini settings 43 | # see https://secure.php.net/manual/en/opcache.installation.php 44 | RUN { \ 45 | echo 'opcache.memory_consumption=128'; \ 46 | echo 'opcache.interned_strings_buffer=8'; \ 47 | echo 'opcache.max_accelerated_files=4000'; \ 48 | echo 'opcache.revalidate_freq=2'; \ 49 | echo 'opcache.fast_shutdown=1'; \ 50 | } > /usr/local/etc/php/conf.d/opcache-recommended.ini 51 | # https://codex.wordpress.org/Editing_wp-config.php#Configure_Error_Logging 52 | RUN { \ 53 | echo 'error_reporting = 4339'; \ 54 | echo 'display_errors = Off'; \ 55 | echo 'display_startup_errors = Off'; \ 56 | echo 'log_errors = On'; \ 57 | echo 'error_log = /dev/stderr'; \ 58 | echo 'log_errors_max_len = 1024'; \ 59 | echo 'ignore_repeated_errors = On'; \ 60 | echo 'ignore_repeated_source = Off'; \ 61 | echo 'html_errors = Off'; \ 62 | } > /usr/local/etc/php/conf.d/error-logging.ini 63 | 64 | VOLUME /var/www/html 65 | 66 | ENV WORDPRESS_VERSION 5.2.2 67 | ENV WORDPRESS_SHA1 3605bcbe9ea48d714efa59b0eb2d251657e7d5b0 68 | 69 | RUN set -ex; \ 70 | curl -o wordpress.tar.gz -fSL "https://wordpress.org/wordpress-${WORDPRESS_VERSION}.tar.gz"; \ 71 | echo "$WORDPRESS_SHA1 *wordpress.tar.gz" | sha1sum -c -; \ 72 | # upstream tarballs include ./wordpress/ so this gives us /usr/src/wordpress 73 | tar -xzf wordpress.tar.gz -C /usr/src/; \ 74 | rm wordpress.tar.gz; \ 75 | chown -R www-data:www-data /usr/src/wordpress 76 | 77 | COPY docker-entrypoint.sh /usr/local/bin/ 78 | 79 | ENTRYPOINT ["docker-entrypoint.sh"] 80 | CMD ["php-fpm"] 81 | -------------------------------------------------------------------------------- /wordpress-debug-image/Makefile: -------------------------------------------------------------------------------- 1 | build: 2 | docker build . -t wordpress-debug 3 | 4 | bin: 5 | mkdir -p bin 6 | 7 | bin/wp-cli.phar: bin 8 | curl -o bin/wp-cli.phar https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar 9 | 10 | -------------------------------------------------------------------------------- /wordpress-debug-image/docker-entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euo pipefail 3 | 4 | # usage: file_env VAR [DEFAULT] 5 | # ie: file_env 'XYZ_DB_PASSWORD' 'example' 6 | # (will allow for "$XYZ_DB_PASSWORD_FILE" to fill in the value of 7 | # "$XYZ_DB_PASSWORD" from a file, especially for Docker's secrets feature) 8 | file_env() { 9 | local var="$1" 10 | local fileVar="${var}_FILE" 11 | local def="${2:-}" 12 | if [ "${!var:-}" ] && [ "${!fileVar:-}" ]; then 13 | echo >&2 "error: both $var and $fileVar are set (but are exclusive)" 14 | exit 1 15 | fi 16 | local val="$def" 17 | if [ "${!var:-}" ]; then 18 | val="${!var}" 19 | elif [ "${!fileVar:-}" ]; then 20 | val="$(< "${!fileVar}")" 21 | fi 22 | export "$var"="$val" 23 | unset "$fileVar" 24 | } 25 | 26 | if [[ "$1" == apache2* ]] || [ "$1" == php-fpm ]; then 27 | if [ "$(id -u)" = '0' ]; then 28 | case "$1" in 29 | apache2*) 30 | user="${APACHE_RUN_USER:-www-data}" 31 | group="${APACHE_RUN_GROUP:-www-data}" 32 | 33 | # strip off any '#' symbol ('#1000' is valid syntax for Apache) 34 | pound='#' 35 | user="${user#$pound}" 36 | group="${group#$pound}" 37 | ;; 38 | *) # php-fpm 39 | user='www-data' 40 | group='www-data' 41 | ;; 42 | esac 43 | else 44 | user="$(id -u)" 45 | group="$(id -g)" 46 | fi 47 | 48 | if [ ! -e index.php ] && [ ! -e wp-includes/version.php ]; then 49 | # if the directory exists and WordPress doesn't appear to be installed AND the permissions of it are root:root, let's chown it (likely a Docker-created directory) 50 | if [ "$(id -u)" = '0' ] && [ "$(stat -c '%u:%g' .)" = '0:0' ]; then 51 | chown "$user:$group" . 52 | fi 53 | 54 | echo >&2 "WordPress not found in $PWD - copying now..." 55 | if [ -n "$(ls -A)" ]; then 56 | echo >&2 "WARNING: $PWD is not empty! (copying anyhow)" 57 | fi 58 | sourceTarArgs=( 59 | --create 60 | --file - 61 | --directory /usr/src/wordpress 62 | --owner "$user" --group "$group" 63 | ) 64 | targetTarArgs=( 65 | --extract 66 | --file - 67 | ) 68 | if [ "$user" != '0' ]; then 69 | # avoid "tar: .: Cannot utime: Operation not permitted" and "tar: .: Cannot change mode to rwxr-xr-x: Operation not permitted" 70 | targetTarArgs+=( --no-overwrite-dir ) 71 | fi 72 | tar "${sourceTarArgs[@]}" . | tar "${targetTarArgs[@]}" 73 | echo >&2 "Complete! WordPress has been successfully copied to $PWD" 74 | if [ ! -e .htaccess ]; then 75 | # NOTE: The "Indexes" option is disabled in the php:apache base image 76 | cat > .htaccess <<-'EOF' 77 | # BEGIN WordPress 78 | 79 | RewriteEngine On 80 | RewriteBase / 81 | RewriteRule ^index\.php$ - [L] 82 | RewriteCond %{REQUEST_FILENAME} !-f 83 | RewriteCond %{REQUEST_FILENAME} !-d 84 | RewriteRule . /index.php [L] 85 | 86 | # END WordPress 87 | EOF 88 | chown "$user:$group" .htaccess 89 | fi 90 | fi 91 | 92 | # allow any of these "Authentication Unique Keys and Salts." to be specified via 93 | # environment variables with a "WORDPRESS_" prefix (ie, "WORDPRESS_AUTH_KEY") 94 | uniqueEnvs=( 95 | AUTH_KEY 96 | SECURE_AUTH_KEY 97 | LOGGED_IN_KEY 98 | NONCE_KEY 99 | AUTH_SALT 100 | SECURE_AUTH_SALT 101 | LOGGED_IN_SALT 102 | NONCE_SALT 103 | ) 104 | envs=( 105 | WORDPRESS_DB_HOST 106 | WORDPRESS_DB_USER 107 | WORDPRESS_DB_PASSWORD 108 | WORDPRESS_DB_NAME 109 | WORDPRESS_DB_CHARSET 110 | WORDPRESS_DB_COLLATE 111 | "${uniqueEnvs[@]/#/WORDPRESS_}" 112 | WORDPRESS_TABLE_PREFIX 113 | WORDPRESS_DEBUG 114 | WORDPRESS_CONFIG_EXTRA 115 | ) 116 | haveConfig= 117 | for e in "${envs[@]}"; do 118 | file_env "$e" 119 | if [ -z "$haveConfig" ] && [ -n "${!e}" ]; then 120 | haveConfig=1 121 | fi 122 | done 123 | 124 | # linking backwards-compatibility 125 | if [ -n "${!MYSQL_ENV_MYSQL_*}" ]; then 126 | haveConfig=1 127 | # host defaults to "mysql" below if unspecified 128 | : "${WORDPRESS_DB_USER:=${MYSQL_ENV_MYSQL_USER:-root}}" 129 | if [ "$WORDPRESS_DB_USER" = 'root' ]; then 130 | : "${WORDPRESS_DB_PASSWORD:=${MYSQL_ENV_MYSQL_ROOT_PASSWORD:-}}" 131 | else 132 | : "${WORDPRESS_DB_PASSWORD:=${MYSQL_ENV_MYSQL_PASSWORD:-}}" 133 | fi 134 | : "${WORDPRESS_DB_NAME:=${MYSQL_ENV_MYSQL_DATABASE:-}}" 135 | fi 136 | 137 | # only touch "wp-config.php" if we have environment-supplied configuration values 138 | if [ "$haveConfig" ]; then 139 | : "${WORDPRESS_DB_HOST:=mysql}" 140 | : "${WORDPRESS_DB_USER:=root}" 141 | : "${WORDPRESS_DB_PASSWORD:=}" 142 | : "${WORDPRESS_DB_NAME:=wordpress}" 143 | : "${WORDPRESS_DB_CHARSET:=utf8}" 144 | : "${WORDPRESS_DB_COLLATE:=}" 145 | 146 | # version 4.4.1 decided to switch to windows line endings, that breaks our seds and awks 147 | # https://github.com/docker-library/wordpress/issues/116 148 | # https://github.com/WordPress/WordPress/commit/1acedc542fba2482bab88ec70d4bea4b997a92e4 149 | sed -ri -e 's/\r$//' wp-config* 150 | 151 | if [ ! -e wp-config.php ]; then 152 | awk ' 153 | /^\/\*.*stop editing.*\*\/$/ && c == 0 { 154 | c = 1 155 | system("cat") 156 | if (ENVIRON["WORDPRESS_CONFIG_EXTRA"]) { 157 | print "// WORDPRESS_CONFIG_EXTRA" 158 | print ENVIRON["WORDPRESS_CONFIG_EXTRA"] "\n" 159 | } 160 | } 161 | { print } 162 | ' wp-config-sample.php > wp-config.php <<'EOPHP' 163 | // If we're behind a proxy server and using HTTPS, we need to alert Wordpress of that fact 164 | // see also http://codex.wordpress.org/Administration_Over_SSL#Using_a_Reverse_Proxy 165 | if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') { 166 | $_SERVER['HTTPS'] = 'on'; 167 | } 168 | 169 | EOPHP 170 | chown "$user:$group" wp-config.php 171 | elif [ -e wp-config.php ] && [ -n "$WORDPRESS_CONFIG_EXTRA" ] && [[ "$(< wp-config.php)" != *"$WORDPRESS_CONFIG_EXTRA"* ]]; then 172 | # (if the config file already contains the requested PHP code, don't print a warning) 173 | echo >&2 174 | echo >&2 'WARNING: environment variable "WORDPRESS_CONFIG_EXTRA" is set, but "wp-config.php" already exists' 175 | echo >&2 ' The contents of this variable will _not_ be inserted into the existing "wp-config.php" file.' 176 | echo >&2 ' (see https://github.com/docker-library/wordpress/issues/333 for more details)' 177 | echo >&2 178 | fi 179 | 180 | # see http://stackoverflow.com/a/2705678/433558 181 | sed_escape_lhs() { 182 | echo "$@" | sed -e 's/[]\/$*.^|[]/\\&/g' 183 | } 184 | sed_escape_rhs() { 185 | echo "$@" | sed -e 's/[\/&]/\\&/g' 186 | } 187 | php_escape() { 188 | local escaped="$(php -r 'var_export(('"$2"') $argv[1]);' -- "$1")" 189 | if [ "$2" = 'string' ] && [ "${escaped:0:1}" = "'" ]; then 190 | escaped="${escaped//$'\n'/"' + \"\\n\" + '"}" 191 | fi 192 | echo "$escaped" 193 | } 194 | set_config() { 195 | key="$1" 196 | value="$2" 197 | var_type="${3:-string}" 198 | start="(['\"])$(sed_escape_lhs "$key")\2\s*," 199 | end="\);" 200 | if [ "${key:0:1}" = '$' ]; then 201 | start="^(\s*)$(sed_escape_lhs "$key")\s*=" 202 | end=";" 203 | fi 204 | sed -ri -e "s/($start\s*).*($end)$/\1$(sed_escape_rhs "$(php_escape "$value" "$var_type")")\3/" wp-config.php 205 | } 206 | 207 | set_config 'DB_HOST' "$WORDPRESS_DB_HOST" 208 | set_config 'DB_USER' "$WORDPRESS_DB_USER" 209 | set_config 'DB_PASSWORD' "$WORDPRESS_DB_PASSWORD" 210 | set_config 'DB_NAME' "$WORDPRESS_DB_NAME" 211 | set_config 'DB_CHARSET' "$WORDPRESS_DB_CHARSET" 212 | set_config 'DB_COLLATE' "$WORDPRESS_DB_COLLATE" 213 | 214 | for unique in "${uniqueEnvs[@]}"; do 215 | uniqVar="WORDPRESS_$unique" 216 | if [ -n "${!uniqVar}" ]; then 217 | set_config "$unique" "${!uniqVar}" 218 | else 219 | # if not specified, let's generate a random value 220 | currentVal="$(sed -rn -e "s/define\(\s*(([\'\"])$unique\2\s*,\s*)(['\"])(.*)\3\s*\);/\4/p" wp-config.php)" 221 | if [ "$currentVal" = 'put your unique phrase here' ]; then 222 | set_config "$unique" "$(head -c1m /dev/urandom | sha1sum | cut -d' ' -f1)" 223 | fi 224 | fi 225 | done 226 | 227 | if [ "$WORDPRESS_TABLE_PREFIX" ]; then 228 | set_config '$table_prefix' "$WORDPRESS_TABLE_PREFIX" 229 | fi 230 | 231 | if [ "$WORDPRESS_DEBUG" ]; then 232 | set_config 'WP_DEBUG' 1 boolean 233 | fi 234 | 235 | if ! TERM=dumb php -- <<'EOPHP' 236 | connect_error) { 259 | fwrite($stderr, "\n" . 'MySQL Connection Error: (' . $mysql->connect_errno . ') ' . $mysql->connect_error . "\n"); 260 | --$maxTries; 261 | if ($maxTries <= 0) { 262 | exit(1); 263 | } 264 | sleep(3); 265 | } 266 | } while ($mysql->connect_error); 267 | 268 | if (!$mysql->query('CREATE DATABASE IF NOT EXISTS `' . $mysql->real_escape_string($dbName) . '`')) { 269 | fwrite($stderr, "\n" . 'MySQL "CREATE DATABASE" Error: ' . $mysql->error . "\n"); 270 | $mysql->close(); 271 | exit(1); 272 | } 273 | 274 | $mysql->close(); 275 | EOPHP 276 | then 277 | echo >&2 278 | echo >&2 "WARNING: unable to establish a database connection to '$WORDPRESS_DB_HOST'" 279 | echo >&2 ' continuing anyways (which might have unexpected results)' 280 | echo >&2 281 | fi 282 | fi 283 | 284 | # now that we're definitely done writing configuration, let's clear out the relevant envrionment variables (so that stray "phpinfo()" calls don't leak secrets from our code) 285 | for e in "${envs[@]}"; do 286 | unset "$e" 287 | done 288 | fi 289 | 290 | exec "$@" 291 | --------------------------------------------------------------------------------