├── .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 | 
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 '
';
59 | foreach ($data as $key=>$value){
60 | echo '- '.$key.' : '.$value.'
';
61 | }
62 | 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 |
--------------------------------------------------------------------------------