├── .gitignore ├── .gitattributes ├── Dockerfile ├── .gitmodules ├── tests ├── test_time_limit.sh ├── test_pid.sh ├── test_verbose.sh ├── test_mem.sh ├── test_pgrep_trace_limit.sh ├── test_buffer_full.sh ├── test_pgrep.sh ├── test_child.sh ├── test_varpeek_range.sh ├── test_pgrep_time_limit.sh ├── test_varpeek.sh ├── test_flamegraph.sh ├── test_filter.sh ├── test_glopeek.sh └── test.sh ├── .github └── workflows │ └── phpspy_test.yml ├── struct_dump.sh ├── LICENSE ├── Makefile ├── stackcollapse-phpspy.pl ├── phpspy_trace_tpl.c ├── struct_dump.gdb ├── addr_objdump.c ├── php_structs_70.h ├── php_structs_71.h ├── php_structs_72.h ├── php_structs_73.h ├── php_structs_74.h ├── php_structs_80.h ├── php_structs_81.h ├── php_structs_82.h ├── php_structs_83.h ├── php_structs_84.h ├── phpspy.h ├── pgrep.c ├── event_fout.c ├── event_callgrind.c ├── top.c ├── README.md ├── phpspy_trace.c └── phpspy.c /.gitignore: -------------------------------------------------------------------------------- 1 | phpspy 2 | *~ 3 | \#*\# 4 | .\#* -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | vendor/** linguist-vendored 2 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM php:8.1-fpm-alpine 2 | 3 | RUN apk add --update alpine-sdk \ 4 | && git clone --recursive https://github.com/adsr/phpspy.git \ 5 | && cd phpspy \ 6 | && make 7 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "vendor/termbox"] 2 | path = vendor/termbox 3 | url = https://github.com/termbox/termbox.git 4 | [submodule "vendor/termbox2"] 5 | path = vendor/termbox2 6 | url = https://github.com/termbox/termbox2.git 7 | -------------------------------------------------------------------------------- /tests/test_time_limit.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | $PHP -r 'sleep(4);' & 4 | php_pid=$! 5 | phpspy_opts=(--limit=0 --time-limit-ms=1000 --pid=$php_pid) 6 | declare -A expected 7 | expected[include_sleep]='sleep' 8 | use_sudo=1 9 | use_timeout_s=2 10 | source $TEST_SH 11 | wait $php_pid 12 | -------------------------------------------------------------------------------- /tests/test_pid.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | $PHP -r 'sleep(1);' & 4 | php_pid=$! 5 | phpspy_opts=(--pid $php_pid) 6 | declare -A expected 7 | expected[frame_0 ]='^0 sleep :-1$' 8 | expected[frame_1 ]='^1
:-1$' 9 | use_sudo=1 10 | source $TEST_SH 11 | wait $php_pid 12 | -------------------------------------------------------------------------------- /tests/test_verbose.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | phpspy_opts=(--verbose-fields=pt -- $PHP -r 'usleep(1000000);') 4 | declare -A expected 5 | expected[frame_0 ]='^0 usleep :-1$' 6 | expected[frame_1 ]='^1
:-1$' 7 | expected[verbose_ts ]='^# trace_ts = \d+\.\d+' 8 | expected[verbose_pid ]='^# pid = \d+$' 9 | source $TEST_SH 10 | -------------------------------------------------------------------------------- /tests/test_mem.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if { $PHPSPY -v | grep -q USE_ZEND=y; }; then 4 | skip='alloc_globals not available in USE_ZEND=y build' 5 | elif { objdump -tT $(command -v $PHP) | grep -q alloc_globals; }; then 6 | phpspy_opts=(--memory-usage -- $PHP -r 'sleep(1);') 7 | declare -A expected 8 | expected[mem]='^# mem \d+ \d+$' 9 | else 10 | skip='alloc_globals symbol not found' 11 | fi 12 | source $TEST_SH 13 | -------------------------------------------------------------------------------- /tests/test_pgrep_trace_limit.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | $PHP -r 'sleep(4);' & 4 | php_pid=$! 5 | pid_file=$(mktemp) 6 | echo "$php_pid" >$pid_file 7 | phpspy_opts=(--pgrep "--pidfile $pid_file" --threads 2 --limit=1) 8 | declare -A expected 9 | expected[frame_0 ]='^0 sleep :-1$' 10 | expected[frame_1 ]='^1
:-1$' 11 | use_timeout_s=2 12 | use_sudo=1 13 | source $TEST_SH 14 | wait $php_pid 15 | rm -f $pid_file 16 | -------------------------------------------------------------------------------- /tests/test_buffer_full.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | phpspy_opts=(--continue-on-error --buffer-size=24 -- $PHP -r 'usleep(1000000);') 4 | declare -A expected 5 | declare -A not_expected 6 | expected[frame_0 ]='^0 usleep :-1$' 7 | not_expected[no_frame_1 ]='^1
:-1$' 8 | source $TEST_SH 9 | 10 | phpspy_opts=(--buffer-size=24 -- $PHP -r 'usleep(1000000);') 11 | declare -A not_expected 12 | not_expected[anything]='^.+$' 13 | source $TEST_SH 14 | -------------------------------------------------------------------------------- /tests/test_pgrep.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | $PHP -r 'sleep(1);' & 4 | php_pid=$! 5 | pid_file=$(mktemp) 6 | echo "$php_pid" >$pid_file 7 | phpspy_opts=(--pgrep "--pidfile $pid_file" --threads 2 --event-handler-opts m) 8 | declare -A expected 9 | expected[frame_0 ]='^0 sleep :-1$' 10 | expected[frame_1 ]='^1
:-1$' 11 | use_timeout_s=2 12 | use_sudo=1 13 | non_zero_ok=1 14 | source $TEST_SH 15 | wait $php_pid 16 | rm -f $pid_file 17 | -------------------------------------------------------------------------------- /tests/test_child.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | phpspy_opts=(--request-info=qcup -- $PHP -r 'usleep(1000000);') 4 | declare -A expected 5 | expected[frame_0 ]='^0 usleep :-1$' 6 | expected[frame_1 ]='^1
:-1$' 7 | expected[req_uri ]='^# uri = -$' 8 | expected[req_path ]='^# path = (Standard input code|-)$' 9 | expected[req_qstring ]='^# qstring = -$' 10 | expected[req_cookie ]='^# cookie = -$' 11 | expected[req_ts ]='^# ts = \d+\.\d+$' 12 | source $TEST_SH 13 | -------------------------------------------------------------------------------- /.github/workflows/phpspy_test.yml: -------------------------------------------------------------------------------- 1 | name: phpspy_test 2 | on: [push, pull_request] 3 | jobs: 4 | phpspy_test_job: 5 | runs-on: ubuntu-24.04 6 | env: 7 | TERM: xterm 8 | steps: 9 | - name: checkout 10 | uses: actions/checkout@v2 11 | with: 12 | submodules: recursive 13 | - name: deps 14 | run: | 15 | sudo apt-get install -y php8.3-cli 16 | echo "php_path:=/usr/bin/php8.3" | sudo tee config.mk 17 | - name: test 18 | run: make test 19 | -------------------------------------------------------------------------------- /tests/test_varpeek_range.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | read -r -d '' php_src <<'EOD' 4 | $php_file 15 | phpspy_opts=(--limit=0 --peek-var "a@$php_file:4-7" --peek-var "b@$php_file:4-7" -- $PHP $php_file) 16 | declare -A expected 17 | expected[varpeek_range_a_1]="^# varpeek a@$php_file:\d+ = 1" 18 | expected[varpeek_range_b_2]="^# varpeek b@$php_file:\d+ = 2" 19 | expected[varpeek_range_a_3]="^# varpeek a@$php_file:\d+ = 3" 20 | expected[varpeek_range_b_4]="^# varpeek b@$php_file:\d+ = 4" 21 | source $TEST_SH 22 | rm -f $php_file 23 | -------------------------------------------------------------------------------- /tests/test_pgrep_time_limit.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | $PHP -r 'sleep(4);' & 4 | php_pid=$! 5 | pid_file=$(mktemp) 6 | echo "$php_pid" >$pid_file 7 | phpspy_opts=(--pgrep "--pidfile $pid_file" --threads 2 --time-limit-ms=1000) 8 | declare -A expected 9 | expected[frame_0 ]='^0 sleep :-1$' 10 | expected[frame_1 ]='^1
:-1$' 11 | use_timeout_s=2 12 | use_sudo=1 13 | source $TEST_SH 14 | wait $php_pid 15 | rm -f $pid_file 16 | 17 | phpspy_opts=(--pgrep "--full hope_this_does_not_exist_lol" --threads 2 --time-limit-ms=1000) 18 | declare -A expected 19 | expected[nothing]='^$' 20 | use_timeout_s=2 21 | use_sudo=1 22 | source $TEST_SH 23 | wait $php_pid 24 | rm -f $pid_file 25 | -------------------------------------------------------------------------------- /tests/test_varpeek.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | read -r -d '' php_src <<'EOD' 4 | $php_file 13 | phpspy_opts=(--limit=0 --peek-var "a@$php_file:4" -- $PHP $php_file) 14 | declare -A expected 15 | expected[varpeek ]="^# varpeek a@$php_file:4 = 42" 16 | source $TEST_SH 17 | rm -f $php_file 18 | 19 | read -r -d '' php_src <<'EOD' 20 | 42, 'j' => 'dolphin']; 23 | sleep(1); 24 | } 25 | f(); 26 | EOD 27 | php_file=$(mktemp) 28 | echo "$php_src" >$php_file 29 | phpspy_opts=(--limit=0 --peek-var "a@$php_file:4" -- $PHP $php_file) 30 | declare -A expected 31 | expected[varpeek ]="^# varpeek a@$php_file:4 = k=42,j=dolphin," 32 | source $TEST_SH 33 | rm -f $php_file 34 | -------------------------------------------------------------------------------- /tests/test_flamegraph.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | REPO=$(dirname $(dirname $TEST_SH)) 4 | 5 | on_exit() { rm -f $flame_svg; } 6 | fail() { echo -e " \x1b[31mERR \x1b[0m $@"; exit 1; } 7 | 8 | flame_svg=$(mktemp) 9 | trap on_exit EXIT 10 | 11 | $PHPSPY -O/dev/null -E/dev/null --request-info=qcup 2>/dev/null \ 12 | -- $PHP -r 'sleep(2);' \ 13 | | $REPO/stackcollapse-phpspy.pl \ 14 | | $REPO/vendor/flamegraph.pl \ 15 | > $flame_svg 16 | grep -Pq '\d+ samples' $flame_svg || fail "Failed to generate flame graph" 17 | 18 | $PHPSPY -O/dev/null -E/dev/null --request-info=QCUP 2>/dev/null \ 19 | -- $PHP -r 'sleep(2);' \ 20 | | $REPO/stackcollapse-phpspy.pl \ 21 | | $REPO/vendor/flamegraph.pl \ 22 | > $flame_svg 23 | grep -Pq '\d+ samples' $flame_svg || fail "Failed to generate flame graph" 24 | 25 | echo -e " \x1b[32mOK \x1b[0m flamegraph" 26 | -------------------------------------------------------------------------------- /tests/test_filter.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | phpspy_opts=(--limit=0 --filter '(nano|poop)sleep' -- $PHP -r '$n=10000; while(--$n) {usleep(1); time_nanosleep(0, 1000);}') 4 | declare -A expected 5 | declare -A not_expected 6 | expected[include_nanosleep]='nanosleep' 7 | not_expected[exclude_usleep]='usleep' 8 | source $TEST_SH 9 | 10 | phpspy_opts=(--limit=0 --filter-negate 'usle+[pP]' -- $PHP -r '$n=10000; while(--$n) {usleep(1); time_nanosleep(0, 1000);}') 11 | declare -A expected 12 | declare -A not_expected 13 | expected[negate_include_nanosleep]='nanosleep' 14 | not_expected[negate_exclude_usleep]='usleep' 15 | source $TEST_SH 16 | 17 | phpspy_opts=(--limit=1 --filter 'usleep' -- $PHP -r 'time_nanosleep(1, 0); usleep(1000000);') 18 | declare -A expected 19 | declare -A not_expected 20 | expected[limit_include_usleep]='usleep' 21 | not_expected[limit_exclude_nanosleep]='time_nanosleep' 22 | source $TEST_SH 23 | -------------------------------------------------------------------------------- /struct_dump.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | this_dir=$(cd $(dirname "${BASH_SOURCE[0]}") >/dev/null && pwd) 3 | phpsrc_dir=$1 4 | 5 | if [ -z "$phpsrc_dir" ]; then 6 | echo "Required: php-src directory" 7 | exit 1 8 | fi 9 | 10 | pushd $phpsrc_dir 11 | git fetch --tags 12 | for phpv in php-7.0.33 \ 13 | php-7.1.33 \ 14 | php-7.2.34 \ 15 | php-7.3.33 \ 16 | php-7.4.33 \ 17 | php-8.0.30 \ 18 | php-8.1.28 \ 19 | php-8.2.18 \ 20 | php-8.3.6 \ 21 | master 22 | do 23 | git reset --hard HEAD \ 24 | && git clean -fdx \ 25 | && git checkout $phpv \ 26 | && git clean -fdx \ 27 | && ./buildconf --force \ 28 | && ./configure \ 29 | && make -j$(grep -c '^proc' /proc/cpuinfo) \ 30 | && gdb -batch -ex "printf \"$phpv\n\n\"" -x "$this_dir/struct_dump.gdb" --args ./sapi/cli/php >"$this_dir/struct_dump.$phpv.out" 31 | done 32 | popd 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Adam Saponara 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /tests/test_glopeek.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | read -r -d '' php_src <<'EOD' 4 | $php_file 16 | phpspy_opts=(--limit=0 --peek-global "server.REQUEST_TIME" --peek-global "globals.my_global" --peek-global "server.FAKE_VAR" --peek-global "server.SCRIPT_NAME" --peek-global "server.X" -- $PHP $php_file) 17 | declare -A expected 18 | expected[glopeek_server ]="^# glopeek server.REQUEST_TIME = \d+" 19 | expected[glopeek_globals ]="^# glopeek globals.my_global = test_global" 20 | expected[glopeek_server_new]="^# glopeek server.FAKE_VAR = 121" 21 | expected[glopeek_server_mod]="^# glopeek server.SCRIPT_NAME = not_real.php" 22 | source $TEST_SH 23 | rm -f $php_file 24 | 25 | export Ez=1 # These keys collide 26 | export FY=2 27 | phpspy_opts=(--limit=0 --peek-global "server.Ez" --peek-global "server.FY" -- $PHP -r "sleep(1);") 28 | declare -A expected 29 | expected[glopeek1 ]="^# glopeek server.Ez = 1" 30 | expected[glopeek2 ]="^# glopeek server.FY = 2" 31 | source $TEST_SH 32 | -------------------------------------------------------------------------------- /tests/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | maybe_sudo='' 4 | [ -n "$use_sudo" ] && maybe_sudo='sudo -n' 5 | 6 | maybe_timeout='' 7 | [ -n "$use_timeout_s" ] && maybe_timeout="timeout $use_timeout_s" 8 | 9 | if [ -n "$skip" ]; then 10 | echo -e " \x1b[33mSKIP\x1b[0m $skip" 11 | exit 0 12 | fi 13 | 14 | actual=$( 15 | $maybe_sudo $maybe_timeout $PHPSPY \ 16 | --limit=1 \ 17 | --child-stdout=/dev/null \ 18 | --child-stderr=/dev/null \ 19 | "${phpspy_opts[@]}" 20 | ) 21 | 22 | exit_code=$? 23 | if [ -z "$non_zero_ok" -a "$exit_code" -ne 0 ]; then 24 | echo -e " \x1b[31mERR \x1b[0m exit_code=$exit_code" 25 | exit 1 26 | fi 27 | 28 | for testname in "${!expected[@]}"; do 29 | expected_re="${expected[$testname]}" 30 | if grep -Pq "$expected_re" <<<"$actual"; then 31 | echo -e " \x1b[32mOK \x1b[0m $testname" 32 | else 33 | echo -e " \x1b[31mERR \x1b[0m $testname\nexpected=$expected_re\n\nactual=\n$actual" 34 | exit 1 35 | fi 36 | done 37 | 38 | for testname in "${!not_expected[@]}"; do 39 | not_expected_re="${not_expected[$testname]}" 40 | if ! grep -Pq "$not_expected_re" <<<"$actual"; then 41 | echo -e " \x1b[32mOK \x1b[0m $testname" 42 | else 43 | echo -e " \x1b[31mERR \x1b[0m $testname\nnot_expected=$not_expected_re\n\nactual=\n$actual" 44 | exit 1 45 | fi 46 | done 47 | 48 | unset expected 49 | unset not_expected 50 | unset use_sudo 51 | unset use_timeout_s 52 | unset skip 53 | unset non_zero_ok 54 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | phpspy_cflags:=-std=c11 -Wall -Wextra -pedantic -g -O3 -Wno-address-of-packed-member $(CFLAGS) 2 | phpspy_libs:=-pthread $(LDLIBS) 3 | phpspy_ldflags:=$(LDFLAGS) 4 | phpspy_includes:=-I. -I./vendor 5 | phpspy_defines:= 6 | phpspy_tests:=$(wildcard tests/test_*.sh) 7 | phpspy_sources:=phpspy.c pgrep.c top.c addr_objdump.c event_fout.c event_callgrind.c 8 | 9 | termbox_inlcudes=-Ivendor/termbox2/ 10 | 11 | prefix?=/usr/local 12 | 13 | php_path?=php 14 | 15 | sinclude config.mk 16 | 17 | has_phpconf := $(shell command -v php-config >/dev/null 2>&1 && echo :) 18 | git_sha := $(shell test -d .git && command -v git >/dev/null && git rev-parse --short HEAD) 19 | 20 | ifdef USE_ZEND 21 | $(or $(has_phpconf), $(error Need php-config)) 22 | phpspy_includes:=$(phpspy_includes) $$(php-config --includes) 23 | phpspy_defines:=$(phpspy_defines) -DUSE_ZEND=1 24 | endif 25 | 26 | ifdef COMMIT 27 | phpspy_defines:=$(phpspy_defines) -DCOMMIT=$(COMMIT) 28 | else ifneq ($(git_sha),) 29 | phpspy_defines:=$(phpspy_defines) -DCOMMIT=$(git_sha) 30 | endif 31 | 32 | all: phpspy 33 | 34 | phpspy: $(wildcard *.c *.h) vendor/termbox2/termbox2.h 35 | $(CC) $(phpspy_cflags) $(phpspy_includes) $(termbox_inlcudes) $(phpspy_defines) $(phpspy_sources) -o phpspy $(phpspy_ldflags) $(phpspy_libs) 36 | 37 | vendor/termbox2/termbox2.h: 38 | if [ -d "$(CURDIR)/.git" ]; then \ 39 | git submodule update --init --recursive; \ 40 | cd vendor/termbox2 && git reset --hard; \ 41 | else \ 42 | cd vendor; \ 43 | git clone https://github.com/termbox/termbox2.git; \ 44 | fi 45 | 46 | test: phpspy $(phpspy_tests) 47 | @total=0; \ 48 | pass=0; \ 49 | for t in $(phpspy_tests); do \ 50 | tput bold; echo TEST $$t; tput sgr0; \ 51 | PHPSPY=./phpspy PHP=$(php_path) TEST_SH=$$(dirname $$t)/test.sh ./$$t; ec=$$?; echo; \ 52 | [ $$ec -eq 0 ] && pass=$$((pass+1)); \ 53 | total=$$((total+1)); \ 54 | done; \ 55 | printf "Passed %d out of %d tests\n" $$pass $$total ; \ 56 | [ $$pass -eq $$total ] || exit 1 57 | 58 | install: phpspy 59 | install -D -v -m 755 phpspy $(DESTDIR)$(prefix)/bin/phpspy 60 | 61 | clean: 62 | cd vendor/termbox2 && $(MAKE) clean 63 | rm -f phpspy 64 | 65 | .PHONY: all test install clean phpspy 66 | -------------------------------------------------------------------------------- /stackcollapse-phpspy.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | # 3 | # stackcolllapse-phpspy.pl collapse phpspy samples into single lines. 4 | # 5 | # Parses php samples generated by phpspy and outputs stacks as 6 | # single lines, with methods separated by semicolons, and then a space and an 7 | # occurrence count. For use with flamegraph.pl. 8 | # 9 | # USAGE: ./stackcollapse-phpspy.pl infile > outfile 10 | # 11 | # Example Input: 12 | # ... 13 | # 0 sleep :-1 14 | # 1 aaa /home/mlauter/profiling/sample.php:5 15 | # 2 bbb /home/mlauter/profiling/sample.php:10 16 | # 3
/home/mlauter/profiling/sample.php:25 17 | # # - - - 18 | # 0 sleep :-1 19 | # 1 aaa /home/mlauter/profiling/sample.php:5 20 | # 2
/home/mlauter/profiling/sample.php:28 21 | # # - - - 22 | # 0 sleep :-1 23 | # 1 aaa /home/mlauter/profiling/sample.php:5 24 | # 2 bbb /home/mlauter/profiling/sample.php:10 25 | # 3 ccc /home/mlauter/profiling/sample.php:15 26 | # 4
/home/mlauter/profiling/sample.php:22 27 | # # - - - 28 | # ... 29 | # 30 | # Example Output: 31 | #
;ccc;bbb;aaa 1 32 | #
;aaa 1 33 | #
;bbb;aaa;sleep 1 34 | # 35 | # To make a flamegraph: 36 | # ./stackcollapse-phpspy.pl infile | ./vendor/flamegraph.pl > svg.out 37 | 38 | use strict; 39 | use warnings; 40 | 41 | use Getopt::Long qw(:config gnu_getopt no_ignore_case); 42 | use Encode qw(decode encode); 43 | 44 | # parameters 45 | my $help = 0; 46 | 47 | sub usage { 48 | die < outfile\n 50 | --h|help # print this message 51 | USAGE_END 52 | } 53 | 54 | GetOptions( 55 | 'help|h' => \$help 56 | ) or usage(); 57 | usage() if $help; 58 | 59 | # internals 60 | my %stacks; 61 | my @frames; 62 | 63 | while (defined(my $line = <>)) { 64 | next unless $line =~ /^(?:#|\d+) \S/; 65 | 66 | my ($depth, $func) = (split ' ', $line)[0,1]; 67 | 68 | # decode the utf-8 bytes and make them into characters 69 | # and turn anything that's invalid into U+FFFD 70 | $func = decode("utf-8", $func); 71 | 72 | # Convert codepoints that break XML to ? 73 | $func =~ s/[\x01-\x08\x0B-\x0C\x0E-\x1F\x7F-\x84\x86-\x9F]/\x3F/g; 74 | 75 | # turn it back into a string 76 | $func = encode("utf-8", $func); 77 | 78 | if ($depth ne '#' && $depth == 0) { 79 | $stacks{join(';', reverse @frames)} += 1 if @frames; 80 | @frames = (); 81 | } 82 | 83 | push @frames, $func if $line =~ /^\d/; 84 | } 85 | $stacks{join(';', reverse @frames)} += 1 if @frames; 86 | 87 | while ( my ($k, $v) = each %stacks ) { 88 | print "$k $v\n"; 89 | } 90 | -------------------------------------------------------------------------------- /phpspy_trace_tpl.c: -------------------------------------------------------------------------------- 1 | #define concat1(a, b) a ## b 2 | #define concat2(a, b) concat1(a, b) 3 | 4 | #define Bucket concat2(Bucket_, phpv) 5 | #define sapi_globals_struct concat2(sapi_globals_struct_, phpv) 6 | #define sapi_request_info concat2(sapi_request_info_, phpv) 7 | #define zend_alloc_globals concat2(zend_alloc_globals_, phpv) 8 | #define zend_array concat2(zend_array_, phpv) 9 | #define zend_class_entry concat2(zend_class_entry_, phpv) 10 | #define zend_execute_data concat2(zend_execute_data_, phpv) 11 | #define zend_executor_globals concat2(zend_executor_globals_, phpv) 12 | #define zend_function concat2(zend_function_, phpv) 13 | #define zend_mm_heap concat2(zend_mm_heap_, phpv) 14 | #define zend_op concat2(zend_op_, phpv) 15 | #define zend_op_array concat2(zend_op_array_, phpv) 16 | #define zend_string concat2(zend_string_, phpv) 17 | #define zval concat2(zval_, phpv) 18 | 19 | #define do_trace concat2(do_trace_, phpv) 20 | #define trace_stack concat2(trace_stack_, phpv) 21 | #define trace_request_info concat2(trace_request_info_, phpv) 22 | #define trace_memory_info concat2(trace_memory_info_, phpv) 23 | #define trace_globals concat2(trace_globals_, phpv) 24 | #define trace_locals concat2(trace_locals_, phpv) 25 | #define copy_executor_globals concat2(copy_executor_globals_, phpv) 26 | #define copy_zarray_bucket concat2(copy_zarray_bucket_, phpv) 27 | #define sprint_zstring concat2(sprint_zstring_, phpv) 28 | #define sprint_zval concat2(sprint_zval_, phpv) 29 | #define sprint_zarray concat2(sprint_zarray_, phpv) 30 | #define sprint_zarray_val concat2(sprint_zarray_val, phpv) 31 | #define sprint_zarray_bucket concat2(sprint_zarray_bucket_, phpv) 32 | 33 | #include "phpspy_trace.c" 34 | 35 | #undef concat1 36 | #undef concat2 37 | 38 | #undef Bucket 39 | #undef sapi_globals_struct 40 | #undef sapi_request_info 41 | #undef zend_alloc_globals 42 | #undef zend_array 43 | #undef zend_class_entry 44 | #undef zend_execute_data 45 | #undef zend_executor_globals 46 | #undef zend_function 47 | #undef zend_mm_heap 48 | #undef zend_op 49 | #undef zend_op_array 50 | #undef zend_string 51 | #undef zval 52 | 53 | #undef do_trace 54 | #undef trace_stack 55 | #undef trace_request_info 56 | #undef trace_memory_info 57 | #undef trace_globals 58 | #undef trace_locals 59 | #undef copy_executor_globals 60 | #undef copy_zarray_bucket 61 | #undef sprint_zstring 62 | #undef sprint_zval 63 | #undef sprint_zarray 64 | #undef sprint_zarray_val 65 | #undef sprint_zarray_bucket 66 | -------------------------------------------------------------------------------- /struct_dump.gdb: -------------------------------------------------------------------------------- 1 | macro define offsetof(st, f) ((size_t)&(((st *)0)->f)) 2 | macro define typeof(st, f) ((st *)0).f 3 | 4 | define fieldof 5 | printf " " 6 | whatis typeof($arg0, $arg1) 7 | printf " $arg1 %lu +%lu\n", offsetof($arg0, $arg1), sizeof(typeof($arg0, $arg1)) 8 | end 9 | 10 | printf "zend_array\n" 11 | whatis zend_array 12 | fieldof zend_array nTableMask 13 | fieldof zend_array arData 14 | fieldof zend_array nNumUsed 15 | fieldof zend_array nNumOfElements 16 | fieldof zend_array nTableSize 17 | printf "\n" 18 | 19 | printf "zend_executor_globals\n" 20 | whatis zend_executor_globals 21 | fieldof zend_executor_globals symbol_table 22 | fieldof zend_executor_globals current_execute_data 23 | printf "\n" 24 | 25 | printf "zend_execute_data\n" 26 | whatis zend_execute_data 27 | fieldof zend_execute_data opline 28 | fieldof zend_execute_data func 29 | fieldof zend_execute_data prev_execute_data 30 | fieldof zend_execute_data symbol_table 31 | printf "\n" 32 | 33 | printf "zend_op_array\n" 34 | whatis zend_op_array 35 | fieldof zend_op_array last_var 36 | fieldof zend_op_array vars 37 | fieldof zend_op_array filename 38 | fieldof zend_op_array line_start 39 | printf "\n" 40 | 41 | printf "zend_function\n" 42 | whatis zend_function 43 | fieldof zend_function type 44 | fieldof zend_function common 45 | fieldof zend_function common.function_name 46 | fieldof zend_function common.scope 47 | fieldof zend_function op_array 48 | printf "\n" 49 | 50 | printf "zend_class_entry\n" 51 | whatis zend_class_entry 52 | fieldof zend_class_entry name 53 | printf "\n" 54 | 55 | printf "zend_string\n" 56 | whatis zend_string 57 | fieldof zend_string len 58 | fieldof zend_string val 59 | printf "\n" 60 | 61 | printf "zend_op\n" 62 | whatis zend_op 63 | fieldof zend_op lineno 64 | printf "\n" 65 | 66 | printf "sapi_request_info\n" 67 | whatis sapi_request_info 68 | fieldof sapi_request_info query_string 69 | fieldof sapi_request_info cookie_data 70 | fieldof sapi_request_info path_translated 71 | fieldof sapi_request_info request_uri 72 | printf "\n" 73 | 74 | printf "sapi_globals_struct\n" 75 | whatis sapi_globals_struct 76 | fieldof sapi_globals_struct request_info 77 | fieldof sapi_globals_struct global_request_time 78 | printf "\n" 79 | 80 | printf "zend_value\n" 81 | whatis zend_value 82 | fieldof zend_value lval 83 | fieldof zend_value dval 84 | fieldof zend_value str 85 | fieldof zend_value arr 86 | printf "\n" 87 | 88 | printf "zval\n" 89 | whatis zval 90 | fieldof zval value 91 | fieldof zval u1 92 | fieldof zval u1.v 93 | fieldof zval u1.v.type 94 | fieldof zval u2 95 | fieldof zval u2.next 96 | printf "\n" 97 | 98 | printf "Bucket\n" 99 | whatis Bucket 100 | fieldof Bucket val 101 | fieldof Bucket h 102 | fieldof Bucket key 103 | printf "\n" 104 | 105 | printf "zend_alloc_globals\n" 106 | whatis zend_alloc_globals 107 | fieldof zend_alloc_globals mm_heap 108 | printf "\n" 109 | 110 | printf "zend_mm_heap\n" 111 | whatis zend_mm_heap 112 | fieldof zend_mm_heap size 113 | fieldof zend_mm_heap peak 114 | printf "\n" 115 | -------------------------------------------------------------------------------- /addr_objdump.c: -------------------------------------------------------------------------------- 1 | #include "phpspy.h" 2 | #include 3 | 4 | static int get_php_bin_path(pid_t pid, char *path_root, char *path); 5 | static int get_php_base_addr(pid_t pid, char *path_root, char *path, uint64_t *raddr); 6 | static int get_symbol_offset(char *path_root, const char *symbol, uint64_t *raddr); 7 | static int popen_read_line(char *buf, size_t buf_size, char *cmd_fmt, ...); 8 | 9 | int shell_escape(const char *arg, char *buf, size_t buf_size, const char *what) { 10 | int rv = 0; 11 | char *const buf_end = buf + buf_size; 12 | assert(buf_size >= 1); 13 | *buf++ = '\''; 14 | 15 | while (*arg) { 16 | /* logic based on https://stackoverflow.com/a/3669819 */ 17 | if (*arg == '\'') { 18 | if (buf_end - buf < 4) { 19 | rv = 1; 20 | goto shell_escape_end; 21 | } 22 | 23 | *buf++ = '\''; /* close quoting */ 24 | *buf++ = '\\'; /* escape ... */ 25 | *buf++ = '\''; /* ... a single quote */ 26 | *buf++ = '\''; /* reopen quoting */ 27 | arg++; 28 | } else { 29 | if (buf_end - buf < 1) { 30 | rv = 1; 31 | goto shell_escape_end; 32 | } 33 | 34 | *buf++ = *arg++; 35 | } 36 | } 37 | 38 | if (buf_end - buf < 2) { 39 | rv = 1; 40 | goto shell_escape_end; 41 | } 42 | 43 | *buf++ = '\''; 44 | *buf = '\0'; 45 | 46 | shell_escape_end: 47 | if (rv != 0) { 48 | log_error("shell_escape: Buffer too small to escape %s: %s\n", what, arg); 49 | } 50 | 51 | return rv; 52 | } 53 | 54 | int get_symbol_addr(addr_memo_t *memo, pid_t pid, const char *symbol, uint64_t *raddr) { 55 | char *php_bin_path, *php_bin_path_root; 56 | uint64_t *php_base_addr; 57 | uint64_t addr_offset; 58 | php_bin_path = memo->php_bin_path; 59 | php_bin_path_root = memo->php_bin_path_root; 60 | php_base_addr = &memo->php_base_addr; 61 | if (*php_bin_path == '\0' && get_php_bin_path(pid, php_bin_path_root, php_bin_path) != 0) { 62 | return 1; 63 | } else if (*php_base_addr == 0 && get_php_base_addr(pid, php_bin_path_root, php_bin_path, php_base_addr) != 0) { 64 | return 1; 65 | } else if (get_symbol_offset(php_bin_path_root, symbol, &addr_offset) != 0) { 66 | return 1; 67 | } 68 | *raddr = *php_base_addr + addr_offset; 69 | return 0; 70 | } 71 | 72 | static int get_php_bin_path(pid_t pid, char *path_root, char *path) { 73 | char buf[PHPSPY_STR_SIZE]; 74 | char libname[PHPSPY_STR_SIZE]; 75 | if (shell_escape(opt_libname_awk_patt, libname, sizeof(libname), "opt_libname_awk_patt")) { 76 | return 1; 77 | } 78 | char *cmd_fmt = "awk -ve=1 -vp=%s '$0~p{print $NF; e=0; exit} END{exit e}' /proc/%d/maps" 79 | " || readlink /proc/%d/exe"; 80 | if (popen_read_line(buf, sizeof(buf), cmd_fmt, libname, (int)pid, (int)pid) != 0) { 81 | log_error("get_php_bin_path: Failed\n"); 82 | return 1; 83 | } 84 | if (snprintf(path_root, PHPSPY_STR_SIZE, "/proc/%d/root/%s", (int)pid, buf) > PHPSPY_STR_SIZE - 1) { 85 | log_error("get_php_bin_path: snprintf overflow\n"); 86 | return 1; 87 | } 88 | if (access(path_root, F_OK) != 0) { 89 | snprintf(path_root, PHPSPY_STR_SIZE, "/proc/%d/exe", (int)pid); 90 | } 91 | strcpy(path, buf); 92 | return 0; 93 | } 94 | 95 | static int get_php_base_addr(pid_t pid, char *path_root, char *path, uint64_t *raddr) { 96 | /** 97 | * This is very likely to be incorrect/incomplete. I thought the base 98 | * address from `/proc//maps` + the symbol address from `readelf` would 99 | * lead to the actual memory address, but on at least one system I tested on 100 | * this is not the case. On that system, working backwards from the address 101 | * printed in `gdb`, it seems the missing piece was the 'virtual address' of 102 | * the LOAD section in ELF headers. I suspect this may have to do with 103 | * address relocation and/or a feature called 'prelinking', but not sure. 104 | */ 105 | char buf[PHPSPY_STR_SIZE]; 106 | char arg_buf[PHPSPY_STR_SIZE]; 107 | uint64_t start_addr; 108 | uint64_t virt_addr; 109 | char *cmd_fmt = "grep -m1 ' '%s\\$ /proc/%d/maps"; 110 | if (shell_escape(path, arg_buf, sizeof(arg_buf), "path")) { 111 | return 1; 112 | } 113 | if (popen_read_line(buf, sizeof(buf), cmd_fmt, arg_buf, (int)pid) != 0) { 114 | log_error("get_php_base_addr: Failed to get start_addr\n"); 115 | return 1; 116 | } 117 | start_addr = strtoull(buf, NULL, 16); 118 | if (shell_escape(path_root, arg_buf, sizeof(arg_buf), "path_root")) { 119 | return 1; 120 | } 121 | cmd_fmt = "objdump -p %s | awk '/LOAD/{print $5; exit}'"; 122 | if (popen_read_line(buf, sizeof(buf), cmd_fmt, arg_buf) != 0) { 123 | log_error("get_php_base_addr: Failed to get virt_addr\n"); 124 | return 1; 125 | } 126 | virt_addr = strtoull(buf, NULL, 16); 127 | *raddr = start_addr - virt_addr; 128 | return 0; 129 | } 130 | 131 | static int get_symbol_offset(char *path_root, const char *symbol, uint64_t *raddr) { 132 | char buf[PHPSPY_STR_SIZE]; 133 | char arg_buf[PHPSPY_STR_SIZE]; 134 | char *cmd_fmt = "objdump -Tt %s | awk '/ %s$/{print $1; exit}'"; 135 | if (shell_escape(path_root, arg_buf, sizeof(arg_buf), "path_root")) { 136 | return 1; 137 | } 138 | if (popen_read_line(buf, sizeof(buf), cmd_fmt, arg_buf, symbol) != 0) { 139 | log_error("get_symbol_offset: Failed\n"); 140 | return 1; 141 | } 142 | *raddr = strtoull(buf, NULL, 16); 143 | return 0; 144 | } 145 | 146 | static int popen_read_line(char *buf, size_t buf_size, char *cmd_fmt, ...) { 147 | FILE *fp; 148 | char cmd[PHPSPY_STR_SIZE]; 149 | int buf_len; 150 | va_list cmd_args; 151 | va_start(cmd_args, cmd_fmt); 152 | if (vsnprintf(cmd, sizeof(cmd), cmd_fmt, cmd_args) >= (int)(sizeof(cmd) - 1)) { 153 | log_error("vsnprintf overflow\n"); 154 | return 1; 155 | } 156 | va_end(cmd_args); 157 | if (!(fp = popen(cmd, "r"))) { 158 | perror("popen"); 159 | return 1; 160 | } 161 | if (fgets(buf, buf_size-1, fp) == NULL) { 162 | log_error("popen_read_line: No stdout; cmd=%s\n", cmd); 163 | pclose(fp); 164 | return 1; 165 | } 166 | pclose(fp); 167 | buf_len = strlen(buf); 168 | while (buf_len > 0 && buf[buf_len-1] == '\n') { 169 | --buf_len; 170 | } 171 | if (buf_len < 1) { 172 | log_error("popen_read_line: Expected strlen(buf)>0; cmd=%s\n", cmd); 173 | return 1; 174 | } 175 | buf[buf_len] = '\0'; 176 | return 0; 177 | } 178 | -------------------------------------------------------------------------------- /php_structs_70.h: -------------------------------------------------------------------------------- 1 | #ifndef __php_structs_70_h 2 | #define __php_structs_70_h 3 | 4 | #include 5 | 6 | typedef struct _zend_executor_globals_70 zend_executor_globals_70; 7 | typedef struct _zend_execute_data_70 zend_execute_data_70; 8 | typedef struct _zend_op_array_70 zend_op_array_70; 9 | typedef union _zend_function_70 zend_function_70; 10 | typedef struct _zend_class_entry_70 zend_class_entry_70; 11 | typedef struct _zend_string_70 zend_string_70; 12 | typedef struct _zend_op_70 zend_op_70; 13 | typedef struct _sapi_request_info_70 sapi_request_info_70; 14 | typedef struct _sapi_globals_struct_70 sapi_globals_struct_70; 15 | typedef union _zend_value_70 zend_value_70; 16 | typedef struct _zval_70 zval_70; 17 | typedef struct _Bucket_70 Bucket_70; 18 | typedef struct _zend_array_70 zend_array_70; 19 | typedef struct _zend_alloc_globals_70 zend_alloc_globals_70; 20 | typedef struct _zend_mm_heap_70 zend_mm_heap_70; 21 | 22 | /* Assumes 8-byte pointers */ 23 | /* offset length */ 24 | struct __attribute__((__packed__)) _zend_array_70 { 25 | uint8_t pad0[12]; /* 0 +12 */ 26 | uint32_t nTableMask; /* 12 +4 */ 27 | Bucket_70 *arData; /* 16 +8 */ 28 | uint32_t nNumUsed; /* 24 +4 */ 29 | uint32_t nNumOfElements; /* 28 +4 */ 30 | uint32_t nTableSize; /* 32 +4 */ 31 | }; 32 | 33 | struct __attribute__((__packed__)) _zend_executor_globals_70 { 34 | uint8_t pad0[304]; /* 0 +304 */ 35 | zend_array_70 symbol_table; /* 304 +36 */ 36 | uint8_t pad1[140]; /* 340 +140 */ 37 | zend_execute_data_70 *current_execute_data; /* 480 +8 */ 38 | }; 39 | 40 | struct __attribute__((__packed__)) _zend_execute_data_70 { 41 | zend_op_70 *opline; /* 0 +8 */ 42 | uint8_t pad0[16]; /* 8 +16 */ 43 | zend_function_70 *func; /* 24 +8 */ 44 | uint8_t pad1[24]; /* 32 +24 */ 45 | zend_execute_data_70 *prev_execute_data; /* 56 +8 */ 46 | zend_array_70 *symbol_table; /* 64 +8 */ 47 | }; 48 | 49 | struct __attribute__((__packed__)) _zend_op_array_70 { 50 | uint8_t pad0[72]; /* 0 +72 */ 51 | int last_var; /* 72 +4 */ 52 | uint8_t pad1[4]; /* 76 +4 */ 53 | zend_string_70 **vars; /* 80 +8 */ 54 | uint8_t pad2[32]; /* 88 +32 */ 55 | zend_string_70 *filename; /* 120 +8 */ 56 | uint32_t line_start; /* 128 +4 */ 57 | }; 58 | 59 | union __attribute__((__packed__)) _zend_function_70 { 60 | uint8_t type; /* 0 +1 */ 61 | struct { 62 | uint8_t pad0[8]; /* 0 +8 */ 63 | zend_string_70 *function_name; /* 8 +8 */ 64 | zend_class_entry_70 *scope; /* 16 +8 */ 65 | } common; 66 | zend_op_array_70 op_array; /* 0 +208 */ 67 | }; 68 | 69 | struct __attribute__((__packed__)) _zend_class_entry_70 { 70 | uint8_t pad0[8]; /* 0 +8 */ 71 | zend_string_70 *name; /* 8 +8 */ 72 | }; 73 | 74 | struct __attribute__((__packed__)) _zend_string_70 { 75 | uint8_t pad0[16]; /* 0 +16 */ 76 | size_t len; /* 16 +8 */ 77 | char val[1]; /* 24 +1 */ 78 | }; 79 | 80 | struct __attribute__((__packed__)) _zend_op_70 { 81 | uint8_t pad0[24]; /* 0 +24 */ 82 | uint32_t lineno; /* 24 +4 */ 83 | }; 84 | 85 | struct __attribute__((__packed__)) _sapi_request_info_70 { 86 | uint8_t pad0[8]; /* 0 +8 */ 87 | char *query_string; /* 8 +8 */ 88 | char *cookie_data; /* 16 +8 */ 89 | uint8_t pad1[8]; /* 24 +8 */ 90 | char *path_translated; /* 32 +8 */ 91 | char *request_uri; /* 40 +8 */ 92 | }; 93 | 94 | struct __attribute__((__packed__)) _sapi_globals_struct_70 { 95 | uint8_t pad0[8]; /* 0 +8 */ 96 | sapi_request_info_70 request_info; /* 8 +48 */ 97 | uint8_t pad1[384]; /* 56 +384 */ 98 | double global_request_time; /* 440 +8 */ 99 | }; 100 | 101 | union __attribute__((__packed__)) _zend_value_70 { 102 | long lval; /* 0 +8 */ 103 | double dval; /* 0 +8 */ 104 | zend_string_70 *str; /* 0 +8 */ 105 | zend_array_70 *arr; /* 0 +8 */ 106 | }; 107 | 108 | struct __attribute__((__packed__)) _zval_70 { 109 | zend_value_70 value; /* 0 +8 */ 110 | union { 111 | struct { 112 | uint8_t type; /* 8 +1 */ 113 | uint8_t pad0[3]; /* 9 +3 */ 114 | } v; 115 | } u1; 116 | union { 117 | uint32_t next; /* 12 +4 */ 118 | } u2; 119 | }; 120 | 121 | struct __attribute__((__packed__)) _Bucket_70 { 122 | zval_70 val; /* 0 +16 */ 123 | uint64_t h; /* 16 +8 */ 124 | zend_string_70 *key; /* 24 +8 */ 125 | }; 126 | 127 | struct __attribute__((__packed__)) _zend_alloc_globals_70 { 128 | zend_mm_heap_70 *mm_heap; /* 0 +8 */ 129 | }; 130 | 131 | struct __attribute__((__packed__)) _zend_mm_heap_70 { 132 | uint8_t pad0[16]; /* 0 +16 */ 133 | size_t size; /* 16 +8 */ 134 | size_t peak; /* 24 +8 */ 135 | }; 136 | 137 | #endif 138 | -------------------------------------------------------------------------------- /php_structs_71.h: -------------------------------------------------------------------------------- 1 | #ifndef __php_structs_71_h 2 | #define __php_structs_71_h 3 | 4 | #include 5 | 6 | typedef struct _zend_executor_globals_71 zend_executor_globals_71; 7 | typedef struct _zend_execute_data_71 zend_execute_data_71; 8 | typedef struct _zend_op_array_71 zend_op_array_71; 9 | typedef union _zend_function_71 zend_function_71; 10 | typedef struct _zend_class_entry_71 zend_class_entry_71; 11 | typedef struct _zend_string_71 zend_string_71; 12 | typedef struct _zend_op_71 zend_op_71; 13 | typedef struct _sapi_request_info_71 sapi_request_info_71; 14 | typedef struct _sapi_globals_struct_71 sapi_globals_struct_71; 15 | typedef union _zend_value_71 zend_value_71; 16 | typedef struct _zval_71 zval_71; 17 | typedef struct _Bucket_71 Bucket_71; 18 | typedef struct _zend_array_71 zend_array_71; 19 | typedef struct _zend_alloc_globals_71 zend_alloc_globals_71; 20 | typedef struct _zend_mm_heap_71 zend_mm_heap_71; 21 | 22 | /* Assumes 8-byte pointers */ 23 | /* offset length */ 24 | struct __attribute__((__packed__)) _zend_array_71 { 25 | uint8_t pad0[12]; /* 0 +12 */ 26 | uint32_t nTableMask; /* 12 +4 */ 27 | Bucket_71 *arData; /* 16 +8 */ 28 | uint32_t nNumUsed; /* 24 +4 */ 29 | uint32_t nNumOfElements; /* 28 +4 */ 30 | uint32_t nTableSize; /* 32 +4 */ 31 | }; 32 | 33 | struct __attribute__((__packed__)) _zend_executor_globals_71 { 34 | uint8_t pad0[304]; /* 0 +304 */ 35 | zend_array_71 symbol_table; /* 304 +36 */ 36 | uint8_t pad1[140]; /* 340 +140 */ 37 | zend_execute_data_71 *current_execute_data; /* 480 +8 */ 38 | }; 39 | 40 | struct __attribute__((__packed__)) _zend_execute_data_71 { 41 | zend_op_71 *opline; /* 0 +8 */ 42 | uint8_t pad0[16]; /* 8 +16 */ 43 | zend_function_71 *func; /* 24 +8 */ 44 | uint8_t pad1[16]; /* 32 +16 */ 45 | zend_execute_data_71 *prev_execute_data; /* 48 +8 */ 46 | zend_array_71 *symbol_table; /* 56 +8 */ 47 | }; 48 | 49 | struct __attribute__((__packed__)) _zend_op_array_71 { 50 | uint8_t pad0[72]; /* 0 +72 */ 51 | int last_var; /* 72 +4 */ 52 | uint8_t pad1[4]; /* 76 +4 */ 53 | zend_string_71 **vars; /* 80 +8 */ 54 | uint8_t pad2[32]; /* 88 +32 */ 55 | zend_string_71 *filename; /* 120 +8 */ 56 | uint32_t line_start; /* 128 +4 */ 57 | }; 58 | 59 | union __attribute__((__packed__)) _zend_function_71 { 60 | uint8_t type; /* 0 +1 */ 61 | struct { 62 | uint8_t pad0[8]; /* 0 +8 */ 63 | zend_string_71 *function_name; /* 8 +8 */ 64 | zend_class_entry_71 *scope; /* 16 +8 */ 65 | } common; 66 | zend_op_array_71 op_array; /* 0 +208 */ 67 | }; 68 | 69 | struct __attribute__((__packed__)) _zend_class_entry_71 { 70 | uint8_t pad0[8]; /* 0 +8 */ 71 | zend_string_71 *name; /* 8 +8 */ 72 | }; 73 | 74 | struct __attribute__((__packed__)) _zend_string_71 { 75 | uint8_t pad0[16]; /* 0 +16 */ 76 | size_t len; /* 16 +8 */ 77 | char val[1]; /* 24 +1 */ 78 | }; 79 | 80 | struct __attribute__((__packed__)) _zend_op_71 { 81 | uint8_t pad0[24]; /* 0 +24 */ 82 | uint32_t lineno; /* 24 +4 */ 83 | }; 84 | 85 | struct __attribute__((__packed__)) _sapi_request_info_71 { 86 | uint8_t pad0[8]; /* 0 +8 */ 87 | char *query_string; /* 8 +8 */ 88 | char *cookie_data; /* 16 +8 */ 89 | uint8_t pad1[8]; /* 24 +8 */ 90 | char *path_translated; /* 32 +8 */ 91 | char *request_uri; /* 40 +8 */ 92 | }; 93 | 94 | struct __attribute__((__packed__)) _sapi_globals_struct_71 { 95 | uint8_t pad0[8]; /* 0 +8 */ 96 | sapi_request_info_71 request_info; /* 8 +48 */ 97 | uint8_t pad1[384]; /* 56 +384 */ 98 | double global_request_time; /* 440 +8 */ 99 | }; 100 | 101 | union __attribute__((__packed__)) _zend_value_71 { 102 | long lval; /* 0 +8 */ 103 | double dval; /* 0 +8 */ 104 | zend_string_71 *str; /* 0 +8 */ 105 | zend_array_71 *arr; /* 0 +8 */ 106 | }; 107 | 108 | struct __attribute__((__packed__)) _zval_71 { 109 | zend_value_71 value; /* 0 +8 */ 110 | union { 111 | struct { 112 | uint8_t type; /* 8 +1 */ 113 | uint8_t pad0[3]; /* 9 +3 */ 114 | } v; 115 | } u1; 116 | union { 117 | uint32_t next; /* 12 +4 */ 118 | } u2; 119 | }; 120 | 121 | struct __attribute__((__packed__)) _Bucket_71 { 122 | zval_71 val; /* 0 +16 */ 123 | uint64_t h; /* 16 +8 */ 124 | zend_string_71 *key; /* 24 +8 */ 125 | }; 126 | 127 | struct __attribute__((__packed__)) _zend_alloc_globals_71 { 128 | zend_mm_heap_71 *mm_heap; /* 0 +8 */ 129 | }; 130 | 131 | struct __attribute__((__packed__)) _zend_mm_heap_71 { 132 | uint8_t pad0[16]; /* 0 +16 */ 133 | size_t size; /* 16 +8 */ 134 | size_t peak; /* 24 +8 */ 135 | }; 136 | 137 | #endif 138 | -------------------------------------------------------------------------------- /php_structs_72.h: -------------------------------------------------------------------------------- 1 | #ifndef __php_structs_72_h 2 | #define __php_structs_72_h 3 | 4 | #include 5 | 6 | typedef struct _zend_executor_globals_72 zend_executor_globals_72; 7 | typedef struct _zend_execute_data_72 zend_execute_data_72; 8 | typedef struct _zend_op_array_72 zend_op_array_72; 9 | typedef union _zend_function_72 zend_function_72; 10 | typedef struct _zend_class_entry_72 zend_class_entry_72; 11 | typedef struct _zend_string_72 zend_string_72; 12 | typedef struct _zend_op_72 zend_op_72; 13 | typedef struct _sapi_request_info_72 sapi_request_info_72; 14 | typedef struct _sapi_globals_struct_72 sapi_globals_struct_72; 15 | typedef union _zend_value_72 zend_value_72; 16 | typedef struct _zval_72 zval_72; 17 | typedef struct _Bucket_72 Bucket_72; 18 | typedef struct _zend_array_72 zend_array_72; 19 | typedef struct _zend_alloc_globals_72 zend_alloc_globals_72; 20 | typedef struct _zend_mm_heap_72 zend_mm_heap_72; 21 | 22 | /* Assumes 8-byte pointers */ 23 | /* offset length */ 24 | struct __attribute__((__packed__)) _zend_array_72 { 25 | uint8_t pad0[12]; /* 0 +12 */ 26 | uint32_t nTableMask; /* 12 +4 */ 27 | Bucket_72 *arData; /* 16 +8 */ 28 | uint32_t nNumUsed; /* 24 +4 */ 29 | uint32_t nNumOfElements; /* 28 +4 */ 30 | uint32_t nTableSize; /* 32 +4 */ 31 | }; 32 | 33 | struct __attribute__((__packed__)) _zend_executor_globals_72 { 34 | uint8_t pad0[304]; /* 0 +304 */ 35 | zend_array_72 symbol_table; /* 304 +36 */ 36 | uint8_t pad1[140]; /* 340 +140 */ 37 | zend_execute_data_72 *current_execute_data; /* 480 +8 */ 38 | }; 39 | 40 | struct __attribute__((__packed__)) _zend_execute_data_72 { 41 | zend_op_72 *opline; /* 0 +8 */ 42 | uint8_t pad0[16]; /* 8 +16 */ 43 | zend_function_72 *func; /* 24 +8 */ 44 | uint8_t pad1[16]; /* 32 +16 */ 45 | zend_execute_data_72 *prev_execute_data; /* 48 +8 */ 46 | zend_array_72 *symbol_table; /* 56 +8 */ 47 | }; 48 | 49 | struct __attribute__((__packed__)) _zend_op_array_72 { 50 | uint8_t pad0[72]; /* 0 +72 */ 51 | int last_var; /* 72 +4 */ 52 | uint8_t pad1[4]; /* 76 +4 */ 53 | zend_string_72 **vars; /* 80 +8 */ 54 | uint8_t pad2[32]; /* 88 +32 */ 55 | zend_string_72 *filename; /* 120 +8 */ 56 | uint32_t line_start; /* 128 +4 */ 57 | }; 58 | 59 | union __attribute__((__packed__)) _zend_function_72 { 60 | uint8_t type; /* 0 +1 */ 61 | struct { 62 | uint8_t pad0[8]; /* 0 +8 */ 63 | zend_string_72 *function_name; /* 8 +8 */ 64 | zend_class_entry_72 *scope; /* 16 +8 */ 65 | } common; 66 | zend_op_array_72 op_array; /* 0 +224 */ 67 | }; 68 | 69 | struct __attribute__((__packed__)) _zend_class_entry_72 { 70 | uint8_t pad0[8]; /* 0 +8 */ 71 | zend_string_72 *name; /* 8 +8 */ 72 | }; 73 | 74 | struct __attribute__((__packed__)) _zend_string_72 { 75 | uint8_t pad0[16]; /* 0 +16 */ 76 | size_t len; /* 16 +8 */ 77 | char val[1]; /* 24 +1 */ 78 | }; 79 | 80 | struct __attribute__((__packed__)) _zend_op_72 { 81 | uint8_t pad0[24]; /* 0 +24 */ 82 | uint32_t lineno; /* 24 +4 */ 83 | }; 84 | 85 | struct __attribute__((__packed__)) _sapi_request_info_72 { 86 | uint8_t pad0[8]; /* 0 +8 */ 87 | char *query_string; /* 8 +8 */ 88 | char *cookie_data; /* 16 +8 */ 89 | uint8_t pad1[8]; /* 24 +8 */ 90 | char *path_translated; /* 32 +8 */ 91 | char *request_uri; /* 40 +8 */ 92 | }; 93 | 94 | struct __attribute__((__packed__)) _sapi_globals_struct_72 { 95 | uint8_t pad0[8]; /* 0 +8 */ 96 | sapi_request_info_72 request_info; /* 8 +48 */ 97 | uint8_t pad1[384]; /* 56 +384 */ 98 | double global_request_time; /* 440 +8 */ 99 | }; 100 | 101 | union __attribute__((__packed__)) _zend_value_72 { 102 | long lval; /* 0 +8 */ 103 | double dval; /* 0 +8 */ 104 | zend_string_72 *str; /* 0 +8 */ 105 | zend_array_72 *arr; /* 0 +8 */ 106 | }; 107 | 108 | struct __attribute__((__packed__)) _zval_72 { 109 | zend_value_72 value; /* 0 +8 */ 110 | union { 111 | struct { 112 | uint8_t type; /* 8 +1 */ 113 | uint8_t pad0[3]; /* 9 +3 */ 114 | } v; 115 | } u1; 116 | union { 117 | uint32_t next; /* 12 +4 */ 118 | } u2; 119 | }; 120 | 121 | struct __attribute__((__packed__)) _Bucket_72 { 122 | zval_72 val; /* 0 +16 */ 123 | uint64_t h; /* 16 +8 */ 124 | zend_string_72 *key; /* 24 +8 */ 125 | }; 126 | 127 | struct __attribute__((__packed__)) _zend_alloc_globals_72 { 128 | zend_mm_heap_72 *mm_heap; /* 0 +8 */ 129 | }; 130 | 131 | struct __attribute__((__packed__)) _zend_mm_heap_72 { 132 | uint8_t pad0[16]; /* 0 +16 */ 133 | size_t size; /* 16 +8 */ 134 | size_t peak; /* 24 +8 */ 135 | }; 136 | 137 | #endif 138 | -------------------------------------------------------------------------------- /php_structs_73.h: -------------------------------------------------------------------------------- 1 | #ifndef __php_structs_73_h 2 | #define __php_structs_73_h 3 | 4 | #include 5 | 6 | typedef struct _zend_executor_globals_73 zend_executor_globals_73; 7 | typedef struct _zend_execute_data_73 zend_execute_data_73; 8 | typedef struct _zend_op_array_73 zend_op_array_73; 9 | typedef union _zend_function_73 zend_function_73; 10 | typedef struct _zend_class_entry_73 zend_class_entry_73; 11 | typedef struct _zend_string_73 zend_string_73; 12 | typedef struct _zend_op_73 zend_op_73; 13 | typedef struct _sapi_request_info_73 sapi_request_info_73; 14 | typedef struct _sapi_globals_struct_73 sapi_globals_struct_73; 15 | typedef union _zend_value_73 zend_value_73; 16 | typedef struct _zval_73 zval_73; 17 | typedef struct _Bucket_73 Bucket_73; 18 | typedef struct _zend_array_73 zend_array_73; 19 | typedef struct _zend_alloc_globals_73 zend_alloc_globals_73; 20 | typedef struct _zend_mm_heap_73 zend_mm_heap_73; 21 | 22 | /* Assumes 8-byte pointers */ 23 | /* offset length */ 24 | struct __attribute__((__packed__)) _zend_array_73 { 25 | uint8_t pad0[12]; /* 0 +12 */ 26 | uint32_t nTableMask; /* 12 +4 */ 27 | Bucket_73 *arData; /* 16 +8 */ 28 | uint32_t nNumUsed; /* 24 +4 */ 29 | uint32_t nNumOfElements; /* 28 +4 */ 30 | uint32_t nTableSize; /* 32 +4 */ 31 | }; 32 | 33 | struct __attribute__((__packed__)) _zend_executor_globals_73 { 34 | uint8_t pad0[304]; /* 0 +304 */ 35 | zend_array_73 symbol_table; /* 304 +36 */ 36 | uint8_t pad1[148]; /* 340 +148 */ 37 | zend_execute_data_73 *current_execute_data; /* 488 +8 */ 38 | }; 39 | 40 | struct __attribute__((__packed__)) _zend_execute_data_73 { 41 | zend_op_73 *opline; /* 0 +8 */ 42 | uint8_t pad0[16]; /* 8 +16 */ 43 | zend_function_73 *func; /* 24 +8 */ 44 | uint8_t pad1[16]; /* 32 +16 */ 45 | zend_execute_data_73 *prev_execute_data; /* 48 +8 */ 46 | zend_array_73 *symbol_table; /* 56 +8 */ 47 | }; 48 | 49 | struct __attribute__((__packed__)) _zend_op_array_73 { 50 | uint8_t pad0[52]; /* 0 +52 */ 51 | int last_var; /* 52 +4 */ 52 | uint8_t pad1[32]; /* 56 +32 */ 53 | zend_string_73 **vars; /* 88 +8 */ 54 | uint8_t pad2[32]; /* 96 +32 */ 55 | zend_string_73 *filename; /* 128 +8 */ 56 | uint32_t line_start; /* 136 +4 */ 57 | }; 58 | 59 | union __attribute__((__packed__)) _zend_function_73 { 60 | uint8_t type; /* 0 +1 */ 61 | struct { 62 | uint8_t pad0[8]; /* 0 +8 */ 63 | zend_string_73 *function_name; /* 8 +8 */ 64 | zend_class_entry_73 *scope; /* 16 +8 */ 65 | } common; 66 | zend_op_array_73 op_array; /* 0 +216 */ 67 | }; 68 | 69 | struct __attribute__((__packed__)) _zend_class_entry_73 { 70 | uint8_t pad0[8]; /* 0 +8 */ 71 | zend_string_73 *name; /* 8 +8 */ 72 | }; 73 | 74 | struct __attribute__((__packed__)) _zend_string_73 { 75 | uint8_t pad0[16]; /* 0 +16 */ 76 | size_t len; /* 16 +8 */ 77 | char val[1]; /* 24 +1 */ 78 | }; 79 | 80 | struct __attribute__((__packed__)) _zend_op_73 { 81 | uint8_t pad0[24]; /* 0 +24 */ 82 | uint32_t lineno; /* 24 +4 */ 83 | }; 84 | 85 | struct __attribute__((__packed__)) _sapi_request_info_73 { 86 | uint8_t pad0[8]; /* 0 +8 */ 87 | char *query_string; /* 8 +8 */ 88 | char *cookie_data; /* 16 +8 */ 89 | uint8_t pad1[8]; /* 24 +8 */ 90 | char *path_translated; /* 32 +8 */ 91 | char *request_uri; /* 40 +8 */ 92 | }; 93 | 94 | struct __attribute__((__packed__)) _sapi_globals_struct_73 { 95 | uint8_t pad0[8]; /* 0 +8 */ 96 | sapi_request_info_73 request_info; /* 8 +48 */ 97 | uint8_t pad1[384]; /* 56 +384 */ 98 | double global_request_time; /* 440 +8 */ 99 | }; 100 | 101 | union __attribute__((__packed__)) _zend_value_73 { 102 | long lval; /* 0 +8 */ 103 | double dval; /* 0 +8 */ 104 | zend_string_73 *str; /* 0 +8 */ 105 | zend_array_73 *arr; /* 0 +8 */ 106 | }; 107 | 108 | struct __attribute__((__packed__)) _zval_73 { 109 | zend_value_73 value; /* 0 +8 */ 110 | union { 111 | struct { 112 | uint8_t type; /* 8 +1 */ 113 | uint8_t pad0[3]; /* 9 +3 */ 114 | } v; 115 | } u1; 116 | union { 117 | uint32_t next; /* 12 +4 */ 118 | } u2; 119 | }; 120 | 121 | struct __attribute__((__packed__)) _Bucket_73 { 122 | zval_73 val; /* 0 +16 */ 123 | uint64_t h; /* 16 +8 */ 124 | zend_string_73 *key; /* 24 +8 */ 125 | }; 126 | 127 | struct __attribute__((__packed__)) _zend_alloc_globals_73 { 128 | zend_mm_heap_73 *mm_heap; /* 0 +8 */ 129 | }; 130 | 131 | struct __attribute__((__packed__)) _zend_mm_heap_73 { 132 | uint8_t pad0[16]; /* 0 +16 */ 133 | size_t size; /* 16 +8 */ 134 | size_t peak; /* 24 +8 */ 135 | }; 136 | 137 | #endif 138 | -------------------------------------------------------------------------------- /php_structs_74.h: -------------------------------------------------------------------------------- 1 | #ifndef __php_structs_74_h 2 | #define __php_structs_74_h 3 | 4 | #include 5 | 6 | typedef struct _zend_executor_globals_74 zend_executor_globals_74; 7 | typedef struct _zend_execute_data_74 zend_execute_data_74; 8 | typedef struct _zend_op_array_74 zend_op_array_74; 9 | typedef union _zend_function_74 zend_function_74; 10 | typedef struct _zend_class_entry_74 zend_class_entry_74; 11 | typedef struct _zend_string_74 zend_string_74; 12 | typedef struct _zend_op_74 zend_op_74; 13 | typedef struct _sapi_request_info_74 sapi_request_info_74; 14 | typedef struct _sapi_globals_struct_74 sapi_globals_struct_74; 15 | typedef union _zend_value_74 zend_value_74; 16 | typedef struct _zval_74 zval_74; 17 | typedef struct _Bucket_74 Bucket_74; 18 | typedef struct _zend_array_74 zend_array_74; 19 | typedef struct _zend_alloc_globals_74 zend_alloc_globals_74; 20 | typedef struct _zend_mm_heap_74 zend_mm_heap_74; 21 | 22 | /* Assumes 8-byte pointers */ 23 | /* offset length */ 24 | struct __attribute__((__packed__)) _zend_array_74 { 25 | uint8_t pad0[12]; /* 0 +12 */ 26 | uint32_t nTableMask; /* 12 +4 */ 27 | Bucket_74 *arData; /* 16 +8 */ 28 | uint32_t nNumUsed; /* 24 +4 */ 29 | uint32_t nNumOfElements; /* 28 +4 */ 30 | uint32_t nTableSize; /* 32 +4 */ 31 | }; 32 | 33 | struct __attribute__((__packed__)) _zend_executor_globals_74 { 34 | uint8_t pad0[304]; /* 0 +304 */ 35 | zend_array_74 symbol_table; /* 304 +36 */ 36 | uint8_t pad1[148]; /* 340 +148 */ 37 | zend_execute_data_74 *current_execute_data; /* 488 +8 */ 38 | }; 39 | 40 | struct __attribute__((__packed__)) _zend_execute_data_74 { 41 | zend_op_74 *opline; /* 0 +8 */ 42 | uint8_t pad0[16]; /* 8 +16 */ 43 | zend_function_74 *func; /* 24 +8 */ 44 | uint8_t pad1[16]; /* 32 +16 */ 45 | zend_execute_data_74 *prev_execute_data; /* 48 +8 */ 46 | zend_array_74 *symbol_table; /* 56 +8 */ 47 | }; 48 | 49 | struct __attribute__((__packed__)) _zend_op_array_74 { 50 | uint8_t pad0[52]; /* 0 +52 */ 51 | int last_var; /* 52 +4 */ 52 | uint8_t pad1[40]; /* 56 +40 */ 53 | zend_string_74 **vars; /* 96 +8 */ 54 | uint8_t pad2[32]; /* 104 +32 */ 55 | zend_string_74 *filename; /* 136 +8 */ 56 | uint32_t line_start; /* 144 +4 */ 57 | }; 58 | 59 | union __attribute__((__packed__)) _zend_function_74 { 60 | uint8_t type; /* 0 +1 */ 61 | struct { 62 | uint8_t pad0[8]; /* 0 +8 */ 63 | zend_string_74 *function_name; /* 8 +8 */ 64 | zend_class_entry_74 *scope; /* 16 +8 */ 65 | } common; 66 | zend_op_array_74 op_array; /* 0 +224 */ 67 | }; 68 | 69 | struct __attribute__((__packed__)) _zend_class_entry_74 { 70 | uint8_t pad0[8]; /* 0 +8 */ 71 | zend_string_74 *name; /* 8 +8 */ 72 | }; 73 | 74 | struct __attribute__((__packed__)) _zend_string_74 { 75 | uint8_t pad0[16]; /* 0 +16 */ 76 | size_t len; /* 16 +8 */ 77 | char val[1]; /* 24 +1 */ 78 | }; 79 | 80 | struct __attribute__((__packed__)) _zend_op_74 { 81 | uint8_t pad0[24]; /* 0 +24 */ 82 | uint32_t lineno; /* 24 +4 */ 83 | }; 84 | 85 | struct __attribute__((__packed__)) _sapi_request_info_74 { 86 | uint8_t pad0[8]; /* 0 +8 */ 87 | char *query_string; /* 8 +8 */ 88 | char *cookie_data; /* 16 +8 */ 89 | uint8_t pad1[8]; /* 24 +8 */ 90 | char *path_translated; /* 32 +8 */ 91 | char *request_uri; /* 40 +8 */ 92 | }; 93 | 94 | struct __attribute__((__packed__)) _sapi_globals_struct_74 { 95 | uint8_t pad0[8]; /* 0 +8 */ 96 | sapi_request_info_74 request_info; /* 8 +48 */ 97 | uint8_t pad1[384]; /* 56 +384 */ 98 | double global_request_time; /* 440 +8 */ 99 | }; 100 | 101 | union __attribute__((__packed__)) _zend_value_74 { 102 | long lval; /* 0 +8 */ 103 | double dval; /* 0 +8 */ 104 | zend_string_74 *str; /* 0 +8 */ 105 | zend_array_74 *arr; /* 0 +8 */ 106 | }; 107 | 108 | struct __attribute__((__packed__)) _zval_74 { 109 | zend_value_74 value; /* 0 +8 */ 110 | union { 111 | struct { 112 | uint8_t type; /* 8 +1 */ 113 | uint8_t pad0[3]; /* 9 +3 */ 114 | } v; 115 | } u1; 116 | union { 117 | uint32_t next; /* 12 +4 */ 118 | } u2; 119 | }; 120 | 121 | struct __attribute__((__packed__)) _Bucket_74 { 122 | zval_74 val; /* 0 +16 */ 123 | uint64_t h; /* 16 +8 */ 124 | zend_string_74 *key; /* 24 +8 */ 125 | }; 126 | 127 | struct __attribute__((__packed__)) _zend_alloc_globals_74 { 128 | zend_mm_heap_74 *mm_heap; /* 0 +8 */ 129 | }; 130 | 131 | struct __attribute__((__packed__)) _zend_mm_heap_74 { 132 | uint8_t pad0[16]; /* 0 +16 */ 133 | size_t size; /* 16 +8 */ 134 | size_t peak; /* 24 +8 */ 135 | }; 136 | 137 | #endif 138 | -------------------------------------------------------------------------------- /php_structs_80.h: -------------------------------------------------------------------------------- 1 | #ifndef __php_structs_80_h 2 | #define __php_structs_80_h 3 | 4 | #include 5 | 6 | typedef struct _zend_executor_globals_80 zend_executor_globals_80; 7 | typedef struct _zend_execute_data_80 zend_execute_data_80; 8 | typedef struct _zend_op_array_80 zend_op_array_80; 9 | typedef union _zend_function_80 zend_function_80; 10 | typedef struct _zend_class_entry_80 zend_class_entry_80; 11 | typedef struct _zend_string_80 zend_string_80; 12 | typedef struct _zend_op_80 zend_op_80; 13 | typedef struct _sapi_request_info_80 sapi_request_info_80; 14 | typedef struct _sapi_globals_struct_80 sapi_globals_struct_80; 15 | typedef union _zend_value_80 zend_value_80; 16 | typedef struct _zval_80 zval_80; 17 | typedef struct _Bucket_80 Bucket_80; 18 | typedef struct _zend_array_80 zend_array_80; 19 | typedef struct _zend_alloc_globals_80 zend_alloc_globals_80; 20 | typedef struct _zend_mm_heap_80 zend_mm_heap_80; 21 | 22 | /* Assumes 8-byte pointers */ 23 | /* offset length */ 24 | struct __attribute__((__packed__)) _zend_array_80 { 25 | uint8_t pad0[12]; /* 0 +12 */ 26 | uint32_t nTableMask; /* 12 +4 */ 27 | Bucket_80 *arData; /* 16 +8 */ 28 | uint32_t nNumUsed; /* 24 +4 */ 29 | uint32_t nNumOfElements; /* 28 +4 */ 30 | uint32_t nTableSize; /* 32 +4 */ 31 | }; 32 | 33 | struct __attribute__((__packed__)) _zend_executor_globals_80 { 34 | uint8_t pad0[304]; /* 0 +304 */ 35 | zend_array_80 symbol_table; /* 304 +36 */ 36 | uint8_t pad1[148]; /* 340 +148 */ 37 | zend_execute_data_80 *current_execute_data; /* 488 +8 */ 38 | }; 39 | 40 | struct __attribute__((__packed__)) _zend_execute_data_80 { 41 | zend_op_80 *opline; /* 0 +8 */ 42 | uint8_t pad0[16]; /* 8 +16 */ 43 | zend_function_80 *func; /* 24 +8 */ 44 | uint8_t pad1[16]; /* 32 +16 */ 45 | zend_execute_data_80 *prev_execute_data; /* 48 +8 */ 46 | zend_array_80 *symbol_table; /* 56 +8 */ 47 | }; 48 | 49 | struct __attribute__((__packed__)) _zend_op_array_80 { 50 | uint8_t pad0[60]; /* 0 +60 */ 51 | int last_var; /* 60 +4 */ 52 | uint8_t pad1[40]; /* 64 +40 */ 53 | zend_string_80 **vars; /* 104 +8 */ 54 | uint8_t pad2[32]; /* 112 +32 */ 55 | zend_string_80 *filename; /* 144 +8 */ 56 | uint32_t line_start; /* 152 +4 */ 57 | }; 58 | 59 | union __attribute__((__packed__)) _zend_function_80 { 60 | uint8_t type; /* 0 +1 */ 61 | struct { 62 | uint8_t pad0[8]; /* 0 +8 */ 63 | zend_string_80 *function_name; /* 8 +8 */ 64 | zend_class_entry_80 *scope; /* 16 +8 */ 65 | } common; 66 | zend_op_array_80 op_array; /* 0 +232 */ 67 | }; 68 | 69 | struct __attribute__((__packed__)) _zend_class_entry_80 { 70 | uint8_t pad0[8]; /* 0 +8 */ 71 | zend_string_80 *name; /* 8 +8 */ 72 | }; 73 | 74 | struct __attribute__((__packed__)) _zend_string_80 { 75 | uint8_t pad0[16]; /* 0 +16 */ 76 | size_t len; /* 16 +8 */ 77 | char val[1]; /* 24 +1 */ 78 | }; 79 | 80 | struct __attribute__((__packed__)) _zend_op_80 { 81 | uint8_t pad0[24]; /* 0 +24 */ 82 | uint32_t lineno; /* 24 +4 */ 83 | }; 84 | 85 | struct __attribute__((__packed__)) _sapi_request_info_80 { 86 | uint8_t pad0[8]; /* 0 +8 */ 87 | char *query_string; /* 8 +8 */ 88 | char *cookie_data; /* 16 +8 */ 89 | uint8_t pad1[8]; /* 24 +8 */ 90 | char *path_translated; /* 32 +8 */ 91 | char *request_uri; /* 40 +8 */ 92 | }; 93 | 94 | struct __attribute__((__packed__)) _sapi_globals_struct_80 { 95 | uint8_t pad0[8]; /* 0 +8 */ 96 | sapi_request_info_80 request_info; /* 8 +48 */ 97 | uint8_t pad1[384]; /* 56 +384 */ 98 | double global_request_time; /* 440 +8 */ 99 | }; 100 | 101 | union __attribute__((__packed__)) _zend_value_80 { 102 | long lval; /* 0 +8 */ 103 | double dval; /* 0 +8 */ 104 | zend_string_80 *str; /* 0 +8 */ 105 | zend_array_80 *arr; /* 0 +8 */ 106 | }; 107 | 108 | struct __attribute__((__packed__)) _zval_80 { 109 | zend_value_80 value; /* 0 +8 */ 110 | union { 111 | struct { 112 | uint8_t type; /* 8 +1 */ 113 | uint8_t pad0[3]; /* 9 +3 */ 114 | } v; 115 | } u1; 116 | union { 117 | uint32_t next; /* 12 +4 */ 118 | } u2; 119 | }; 120 | 121 | struct __attribute__((__packed__)) _Bucket_80 { 122 | zval_80 val; /* 0 +16 */ 123 | uint64_t h; /* 16 +8 */ 124 | zend_string_80 *key; /* 24 +8 */ 125 | }; 126 | 127 | struct __attribute__((__packed__)) _zend_alloc_globals_80 { 128 | zend_mm_heap_80 *mm_heap; /* 0 +8 */ 129 | }; 130 | 131 | struct __attribute__((__packed__)) _zend_mm_heap_80 { 132 | uint8_t pad0[16]; /* 0 +16 */ 133 | size_t size; /* 16 +8 */ 134 | size_t peak; /* 24 +8 */ 135 | }; 136 | 137 | #endif 138 | -------------------------------------------------------------------------------- /php_structs_81.h: -------------------------------------------------------------------------------- 1 | #ifndef __php_structs_81_h 2 | #define __php_structs_81_h 3 | 4 | #include 5 | 6 | typedef struct _zend_executor_globals_81 zend_executor_globals_81; 7 | typedef struct _zend_execute_data_81 zend_execute_data_81; 8 | typedef struct _zend_op_array_81 zend_op_array_81; 9 | typedef union _zend_function_81 zend_function_81; 10 | typedef struct _zend_class_entry_81 zend_class_entry_81; 11 | typedef struct _zend_string_81 zend_string_81; 12 | typedef struct _zend_op_81 zend_op_81; 13 | typedef struct _sapi_request_info_81 sapi_request_info_81; 14 | typedef struct _sapi_globals_struct_81 sapi_globals_struct_81; 15 | typedef union _zend_value_81 zend_value_81; 16 | typedef struct _zval_81 zval_81; 17 | typedef struct _Bucket_81 Bucket_81; 18 | typedef struct _zend_array_81 zend_array_81; 19 | typedef struct _zend_alloc_globals_81 zend_alloc_globals_81; 20 | typedef struct _zend_mm_heap_81 zend_mm_heap_81; 21 | 22 | /* Assumes 8-byte pointers */ 23 | /* offset length */ 24 | struct __attribute__((__packed__)) _zend_array_81 { 25 | uint8_t pad0[12]; /* 0 +12 */ 26 | uint32_t nTableMask; /* 12 +4 */ 27 | Bucket_81 *arData; /* 16 +8 */ 28 | uint32_t nNumUsed; /* 24 +4 */ 29 | uint32_t nNumOfElements; /* 28 +4 */ 30 | uint32_t nTableSize; /* 32 +4 */ 31 | }; 32 | 33 | struct __attribute__((__packed__)) _zend_executor_globals_81 { 34 | uint8_t pad0[304]; /* 0 +304 */ 35 | zend_array_81 symbol_table; /* 304 +36 */ 36 | uint8_t pad1[148]; /* 340 +148 */ 37 | zend_execute_data_81 *current_execute_data; /* 488 +8 */ 38 | }; 39 | 40 | struct __attribute__((__packed__)) _zend_execute_data_81 { 41 | zend_op_81 *opline; /* 0 +8 */ 42 | uint8_t pad0[16]; /* 8 +16 */ 43 | zend_function_81 *func; /* 24 +8 */ 44 | uint8_t pad1[16]; /* 32 +16 */ 45 | zend_execute_data_81 *prev_execute_data; /* 48 +8 */ 46 | zend_array_81 *symbol_table; /* 56 +8 */ 47 | }; 48 | 49 | struct __attribute__((__packed__)) _zend_op_array_81 { 50 | uint8_t pad0[60]; /* 0 +60 */ 51 | int last_var; /* 60 +4 */ 52 | uint8_t pad1[40]; /* 64 +40 */ 53 | zend_string_81 **vars; /* 104 +8 */ 54 | uint8_t pad2[32]; /* 112 +32 */ 55 | zend_string_81 *filename; /* 144 +8 */ 56 | uint32_t line_start; /* 152 +4 */ 57 | }; 58 | 59 | union __attribute__((__packed__)) _zend_function_81 { 60 | uint8_t type; /* 0 +1 */ 61 | struct { 62 | uint8_t pad0[8]; /* 0 +8 */ 63 | zend_string_81 *function_name; /* 8 +8 */ 64 | zend_class_entry_81 *scope; /* 16 +8 */ 65 | } common; 66 | zend_op_array_81 op_array; /* 0 +240 */ 67 | }; 68 | 69 | struct __attribute__((__packed__)) _zend_class_entry_81 { 70 | uint8_t pad0[8]; /* 0 +8 */ 71 | zend_string_81 *name; /* 8 +8 */ 72 | }; 73 | 74 | struct __attribute__((__packed__)) _zend_string_81 { 75 | uint8_t pad0[16]; /* 0 +16 */ 76 | size_t len; /* 16 +8 */ 77 | char val[1]; /* 24 +1 */ 78 | }; 79 | 80 | struct __attribute__((__packed__)) _zend_op_81 { 81 | uint8_t pad0[24]; /* 0 +24 */ 82 | uint32_t lineno; /* 24 +4 */ 83 | }; 84 | 85 | struct __attribute__((__packed__)) _sapi_request_info_81 { 86 | uint8_t pad0[8]; /* 0 +8 */ 87 | char *query_string; /* 8 +8 */ 88 | char *cookie_data; /* 16 +8 */ 89 | uint8_t pad1[8]; /* 24 +8 */ 90 | char *path_translated; /* 32 +8 */ 91 | char *request_uri; /* 40 +8 */ 92 | }; 93 | 94 | struct __attribute__((__packed__)) _sapi_globals_struct_81 { 95 | uint8_t pad0[8]; /* 0 +8 */ 96 | sapi_request_info_81 request_info; /* 8 +48 */ 97 | uint8_t pad1[384]; /* 56 +384 */ 98 | double global_request_time; /* 440 +8 */ 99 | }; 100 | 101 | union __attribute__((__packed__)) _zend_value_81 { 102 | long lval; /* 0 +8 */ 103 | double dval; /* 0 +8 */ 104 | zend_string_81 *str; /* 0 +8 */ 105 | zend_array_81 *arr; /* 0 +8 */ 106 | }; 107 | 108 | struct __attribute__((__packed__)) _zval_81 { 109 | zend_value_81 value; /* 0 +8 */ 110 | union { 111 | struct { 112 | uint8_t type; /* 8 +1 */ 113 | uint8_t pad0[3]; /* 9 +3 */ 114 | } v; 115 | } u1; 116 | union { 117 | uint32_t next; /* 12 +4 */ 118 | } u2; 119 | }; 120 | 121 | struct __attribute__((__packed__)) _Bucket_81 { 122 | zval_81 val; /* 0 +16 */ 123 | uint64_t h; /* 16 +8 */ 124 | zend_string_81 *key; /* 24 +8 */ 125 | }; 126 | 127 | struct __attribute__((__packed__)) _zend_alloc_globals_81 { 128 | zend_mm_heap_81 *mm_heap; /* 0 +8 */ 129 | }; 130 | 131 | struct __attribute__((__packed__)) _zend_mm_heap_81 { 132 | uint8_t pad0[16]; /* 0 +16 */ 133 | size_t size; /* 16 +8 */ 134 | size_t peak; /* 24 +8 */ 135 | }; 136 | 137 | #endif 138 | -------------------------------------------------------------------------------- /php_structs_82.h: -------------------------------------------------------------------------------- 1 | #ifndef __php_structs_82_h 2 | #define __php_structs_82_h 3 | 4 | #include 5 | 6 | typedef struct _zend_executor_globals_82 zend_executor_globals_82; 7 | typedef struct _zend_execute_data_82 zend_execute_data_82; 8 | typedef struct _zend_op_array_82 zend_op_array_82; 9 | typedef union _zend_function_82 zend_function_82; 10 | typedef struct _zend_class_entry_82 zend_class_entry_82; 11 | typedef struct _zend_string_82 zend_string_82; 12 | typedef struct _zend_op_82 zend_op_82; 13 | typedef struct _sapi_request_info_82 sapi_request_info_82; 14 | typedef struct _sapi_globals_struct_82 sapi_globals_struct_82; 15 | typedef union _zend_value_82 zend_value_82; 16 | typedef struct _zval_82 zval_82; 17 | typedef struct _Bucket_82 Bucket_82; 18 | typedef struct _zend_array_82 zend_array_82; 19 | typedef struct _zend_alloc_globals_82 zend_alloc_globals_82; 20 | typedef struct _zend_mm_heap_82 zend_mm_heap_82; 21 | 22 | /* Assumes 8-byte pointers */ 23 | /* offset length */ 24 | struct __attribute__((__packed__)) _zend_array_82 { 25 | uint8_t pad0[12]; /* 0 +12 */ 26 | uint32_t nTableMask; /* 12 +4 */ 27 | Bucket_82 *arData; /* 16 +8 */ 28 | uint32_t nNumUsed; /* 24 +4 */ 29 | uint32_t nNumOfElements; /* 28 +4 */ 30 | uint32_t nTableSize; /* 32 +4 */ 31 | }; 32 | 33 | struct __attribute__((__packed__)) _zend_executor_globals_82 { 34 | uint8_t pad0[304]; /* 0 +304 */ 35 | zend_array_82 symbol_table; /* 304 +36 */ 36 | uint8_t pad1[148]; /* 340 +148 */ 37 | zend_execute_data_82 *current_execute_data; /* 488 +8 */ 38 | }; 39 | 40 | struct __attribute__((__packed__)) _zend_execute_data_82 { 41 | zend_op_82 *opline; /* 0 +8 */ 42 | uint8_t pad0[16]; /* 8 +16 */ 43 | zend_function_82 *func; /* 24 +8 */ 44 | uint8_t pad1[16]; /* 32 +16 */ 45 | zend_execute_data_82 *prev_execute_data; /* 48 +8 */ 46 | zend_array_82 *symbol_table; /* 56 +8 */ 47 | }; 48 | 49 | struct __attribute__((__packed__)) _zend_op_array_82 { 50 | uint8_t pad0[76]; /* 0 +76 */ 51 | int last_var; /* 76 +4 */ 52 | uint8_t pad1[32]; /* 80 +32 */ 53 | zend_string_82 **vars; /* 112 +8 */ 54 | uint8_t pad2[32]; /* 120 +32 */ 55 | zend_string_82 *filename; /* 152 +8 */ 56 | uint32_t line_start; /* 160 +4 */ 57 | }; 58 | 59 | union __attribute__((__packed__)) _zend_function_82 { 60 | uint8_t type; /* 0 +1 */ 61 | struct { 62 | uint8_t pad0[8]; /* 0 +8 */ 63 | zend_string_82 *function_name; /* 8 +8 */ 64 | zend_class_entry_82 *scope; /* 16 +8 */ 65 | } common; 66 | zend_op_array_82 op_array; /* 0 +248 */ 67 | }; 68 | 69 | struct __attribute__((__packed__)) _zend_class_entry_82 { 70 | uint8_t pad0[8]; /* 0 +8 */ 71 | zend_string_82 *name; /* 8 +8 */ 72 | }; 73 | 74 | struct __attribute__((__packed__)) _zend_string_82 { 75 | uint8_t pad0[16]; /* 0 +16 */ 76 | size_t len; /* 16 +8 */ 77 | char val[1]; /* 24 +1 */ 78 | }; 79 | 80 | struct __attribute__((__packed__)) _zend_op_82 { 81 | uint8_t pad0[24]; /* 0 +24 */ 82 | uint32_t lineno; /* 24 +4 */ 83 | }; 84 | 85 | struct __attribute__((__packed__)) _sapi_request_info_82 { 86 | uint8_t pad0[8]; /* 0 +8 */ 87 | char *query_string; /* 8 +8 */ 88 | char *cookie_data; /* 16 +8 */ 89 | uint8_t pad1[8]; /* 24 +8 */ 90 | char *path_translated; /* 32 +8 */ 91 | char *request_uri; /* 40 +8 */ 92 | }; 93 | 94 | struct __attribute__((__packed__)) _sapi_globals_struct_82 { 95 | uint8_t pad0[8]; /* 0 +8 */ 96 | sapi_request_info_82 request_info; /* 8 +48 */ 97 | uint8_t pad1[384]; /* 56 +384 */ 98 | double global_request_time; /* 440 +8 */ 99 | }; 100 | 101 | union __attribute__((__packed__)) _zend_value_82 { 102 | long lval; /* 0 +8 */ 103 | double dval; /* 0 +8 */ 104 | zend_string_82 *str; /* 0 +8 */ 105 | zend_array_82 *arr; /* 0 +8 */ 106 | }; 107 | 108 | struct __attribute__((__packed__)) _zval_82 { 109 | zend_value_82 value; /* 0 +8 */ 110 | union { 111 | struct { 112 | uint8_t type; /* 8 +1 */ 113 | uint8_t pad0[3]; /* 9 +3 */ 114 | } v; 115 | } u1; 116 | union { 117 | uint32_t next; /* 12 +4 */ 118 | } u2; 119 | }; 120 | 121 | struct __attribute__((__packed__)) _Bucket_82 { 122 | zval_82 val; /* 0 +16 */ 123 | uint64_t h; /* 16 +8 */ 124 | zend_string_82 *key; /* 24 +8 */ 125 | }; 126 | 127 | struct __attribute__((__packed__)) _zend_alloc_globals_82 { 128 | zend_mm_heap_82 *mm_heap; /* 0 +8 */ 129 | }; 130 | 131 | struct __attribute__((__packed__)) _zend_mm_heap_82 { 132 | uint8_t pad0[16]; /* 0 +16 */ 133 | size_t size; /* 16 +8 */ 134 | size_t peak; /* 24 +8 */ 135 | }; 136 | 137 | #endif 138 | -------------------------------------------------------------------------------- /php_structs_83.h: -------------------------------------------------------------------------------- 1 | #ifndef __php_structs_83_h 2 | #define __php_structs_83_h 3 | 4 | #include 5 | 6 | typedef struct _zend_executor_globals_83 zend_executor_globals_83; 7 | typedef struct _zend_execute_data_83 zend_execute_data_83; 8 | typedef struct _zend_op_array_83 zend_op_array_83; 9 | typedef union _zend_function_83 zend_function_83; 10 | typedef struct _zend_class_entry_83 zend_class_entry_83; 11 | typedef struct _zend_string_83 zend_string_83; 12 | typedef struct _zend_op_83 zend_op_83; 13 | typedef struct _sapi_request_info_83 sapi_request_info_83; 14 | typedef struct _sapi_globals_struct_83 sapi_globals_struct_83; 15 | typedef union _zend_value_83 zend_value_83; 16 | typedef struct _zval_83 zval_83; 17 | typedef struct _Bucket_83 Bucket_83; 18 | typedef struct _zend_array_83 zend_array_83; 19 | typedef struct _zend_alloc_globals_83 zend_alloc_globals_83; 20 | typedef struct _zend_mm_heap_83 zend_mm_heap_83; 21 | 22 | /* Assumes 8-byte pointers */ 23 | /* offset length */ 24 | struct __attribute__((__packed__)) _zend_array_83 { 25 | uint8_t pad0[12]; /* 0 +12 */ 26 | uint32_t nTableMask; /* 12 +4 */ 27 | Bucket_83 *arData; /* 16 +8 */ 28 | uint32_t nNumUsed; /* 24 +4 */ 29 | uint32_t nNumOfElements; /* 28 +4 */ 30 | uint32_t nTableSize; /* 32 +4 */ 31 | }; 32 | 33 | struct __attribute__((__packed__)) _zend_executor_globals_83 { 34 | uint8_t pad0[304]; /* 0 +304 */ 35 | zend_array_83 symbol_table; /* 304 +36 */ 36 | uint8_t pad1[148]; /* 340 +148 */ 37 | zend_execute_data_83 *current_execute_data; /* 488 +8 */ 38 | }; 39 | 40 | struct __attribute__((__packed__)) _zend_execute_data_83 { 41 | zend_op_83 *opline; /* 0 +8 */ 42 | uint8_t pad0[16]; /* 8 +16 */ 43 | zend_function_83 *func; /* 24 +8 */ 44 | uint8_t pad1[16]; /* 32 +16 */ 45 | zend_execute_data_83 *prev_execute_data; /* 48 +8 */ 46 | zend_array_83 *symbol_table; /* 56 +8 */ 47 | }; 48 | 49 | struct __attribute__((__packed__)) _zend_op_array_83 { 50 | uint8_t pad0[72]; /* 0 +72 */ 51 | int last_var; /* 72 +4 */ 52 | uint8_t pad1[28]; /* 76 +28 */ 53 | zend_string_83 **vars; /* 104 +8 */ 54 | uint8_t pad2[32]; /* 112 +32 */ 55 | zend_string_83 *filename; /* 144 +8 */ 56 | uint32_t line_start; /* 152 +4 */ 57 | }; 58 | 59 | union __attribute__((__packed__)) _zend_function_83 { 60 | uint8_t type; /* 0 +1 */ 61 | struct { 62 | uint8_t pad0[8]; /* 0 +8 */ 63 | zend_string_83 *function_name; /* 8 +8 */ 64 | zend_class_entry_83 *scope; /* 16 +8 */ 65 | } common; 66 | zend_op_array_83 op_array; /* 0 +240 */ 67 | }; 68 | 69 | struct __attribute__((__packed__)) _zend_class_entry_83 { 70 | uint8_t pad0[8]; /* 0 +8 */ 71 | zend_string_83 *name; /* 8 +8 */ 72 | }; 73 | 74 | struct __attribute__((__packed__)) _zend_string_83 { 75 | uint8_t pad0[16]; /* 0 +16 */ 76 | size_t len; /* 16 +8 */ 77 | char val[1]; /* 24 +1 */ 78 | }; 79 | 80 | struct __attribute__((__packed__)) _zend_op_83 { 81 | uint8_t pad0[24]; /* 0 +24 */ 82 | uint32_t lineno; /* 24 +4 */ 83 | }; 84 | 85 | struct __attribute__((__packed__)) _sapi_request_info_83 { 86 | uint8_t pad0[8]; /* 0 +8 */ 87 | char *query_string; /* 8 +8 */ 88 | char *cookie_data; /* 16 +8 */ 89 | uint8_t pad1[8]; /* 24 +8 */ 90 | char *path_translated; /* 32 +8 */ 91 | char *request_uri; /* 40 +8 */ 92 | }; 93 | 94 | struct __attribute__((__packed__)) _sapi_globals_struct_83 { 95 | uint8_t pad0[8]; /* 0 +8 */ 96 | sapi_request_info_83 request_info; /* 8 +48 */ 97 | uint8_t pad1[384]; /* 56 +384 */ 98 | double global_request_time; /* 440 +8 */ 99 | }; 100 | 101 | union __attribute__((__packed__)) _zend_value_83 { 102 | long lval; /* 0 +8 */ 103 | double dval; /* 0 +8 */ 104 | zend_string_83 *str; /* 0 +8 */ 105 | zend_array_83 *arr; /* 0 +8 */ 106 | }; 107 | 108 | struct __attribute__((__packed__)) _zval_83 { 109 | zend_value_83 value; /* 0 +8 */ 110 | union { 111 | struct { 112 | uint8_t type; /* 8 +1 */ 113 | uint8_t pad0[3]; /* 9 +3 */ 114 | } v; 115 | } u1; 116 | union { 117 | uint32_t next; /* 12 +4 */ 118 | } u2; 119 | }; 120 | 121 | struct __attribute__((__packed__)) _Bucket_83 { 122 | zval_83 val; /* 0 +16 */ 123 | uint64_t h; /* 16 +8 */ 124 | zend_string_83 *key; /* 24 +8 */ 125 | }; 126 | 127 | struct __attribute__((__packed__)) _zend_alloc_globals_83 { 128 | zend_mm_heap_83 *mm_heap; /* 0 +8 */ 129 | }; 130 | 131 | struct __attribute__((__packed__)) _zend_mm_heap_83 { 132 | uint8_t pad0[16]; /* 0 +16 */ 133 | size_t size; /* 16 +8 */ 134 | size_t peak; /* 24 +8 */ 135 | }; 136 | 137 | #endif 138 | -------------------------------------------------------------------------------- /php_structs_84.h: -------------------------------------------------------------------------------- 1 | #ifndef __php_structs_84_h 2 | #define __php_structs_84_h 3 | 4 | #include 5 | 6 | typedef struct _zend_executor_globals_84 zend_executor_globals_84; 7 | typedef struct _zend_execute_data_84 zend_execute_data_84; 8 | typedef struct _zend_op_array_84 zend_op_array_84; 9 | typedef union _zend_function_84 zend_function_84; 10 | typedef struct _zend_class_entry_84 zend_class_entry_84; 11 | typedef struct _zend_string_84 zend_string_84; 12 | typedef struct _zend_op_84 zend_op_84; 13 | typedef struct _sapi_request_info_84 sapi_request_info_84; 14 | typedef struct _sapi_globals_struct_84 sapi_globals_struct_84; 15 | typedef union _zend_value_84 zend_value_84; 16 | typedef struct _zval_84 zval_84; 17 | typedef struct _Bucket_84 Bucket_84; 18 | typedef struct _zend_array_84 zend_array_84; 19 | typedef struct _zend_alloc_globals_84 zend_alloc_globals_84; 20 | typedef struct _zend_mm_heap_84 zend_mm_heap_84; 21 | 22 | /* Assumes 8-byte pointers */ 23 | /* offset length */ 24 | struct __attribute__((__packed__)) _zend_array_84 { 25 | uint8_t pad0[12]; /* 0 +12 */ 26 | uint32_t nTableMask; /* 12 +4 */ 27 | Bucket_84 *arData; /* 16 +8 */ 28 | uint32_t nNumUsed; /* 24 +4 */ 29 | uint32_t nNumOfElements; /* 28 +4 */ 30 | uint32_t nTableSize; /* 32 +4 */ 31 | }; 32 | 33 | struct __attribute__((__packed__)) _zend_executor_globals_84 { 34 | uint8_t pad0[304]; /* 0 +304 */ 35 | zend_array_84 symbol_table; /* 304 +36 */ 36 | uint8_t pad1[148]; /* 340 +148 */ 37 | zend_execute_data_84 *current_execute_data; /* 488 +8 */ 38 | }; 39 | 40 | struct __attribute__((__packed__)) _zend_execute_data_84 { 41 | zend_op_84 *opline; /* 0 +8 */ 42 | uint8_t pad0[16]; /* 8 +16 */ 43 | zend_function_84 *func; /* 24 +8 */ 44 | uint8_t pad1[16]; /* 32 +16 */ 45 | zend_execute_data_84 *prev_execute_data; /* 48 +8 */ 46 | zend_array_84 *symbol_table; /* 56 +8 */ 47 | }; 48 | 49 | struct __attribute__((__packed__)) _zend_op_array_84 { 50 | uint8_t pad0[92]; /* 0 +92 */ 51 | int last_var; /* 92 +4 */ 52 | uint8_t pad1[32]; /* 96 +32 */ 53 | zend_string_84 **vars; /* 128 +8 */ 54 | uint8_t pad2[32]; /* 136 +32 */ 55 | zend_string_84 *filename; /* 168 +8 */ 56 | uint32_t line_start; /* 176 +4 */ 57 | }; 58 | 59 | union __attribute__((__packed__)) _zend_function_84 { 60 | uint8_t type; /* 0 +1 */ 61 | struct { 62 | uint8_t pad0[8]; /* 0 +8 */ 63 | zend_string_84 *function_name; /* 8 +8 */ 64 | zend_class_entry_84 *scope; /* 16 +8 */ 65 | } common; 66 | zend_op_array_84 op_array; /* 0 +240 */ 67 | }; 68 | 69 | struct __attribute__((__packed__)) _zend_class_entry_84 { 70 | uint8_t pad0[8]; /* 0 +8 */ 71 | zend_string_84 *name; /* 8 +8 */ 72 | }; 73 | 74 | struct __attribute__((__packed__)) _zend_string_84 { 75 | uint8_t pad0[16]; /* 0 +16 */ 76 | size_t len; /* 16 +8 */ 77 | char val[1]; /* 24 +1 */ 78 | }; 79 | 80 | struct __attribute__((__packed__)) _zend_op_84 { 81 | uint8_t pad0[24]; /* 0 +24 */ 82 | uint32_t lineno; /* 24 +4 */ 83 | }; 84 | 85 | struct __attribute__((__packed__)) _sapi_request_info_84 { 86 | uint8_t pad0[8]; /* 0 +8 */ 87 | char *query_string; /* 8 +8 */ 88 | char *cookie_data; /* 16 +8 */ 89 | uint8_t pad1[8]; /* 24 +8 */ 90 | char *path_translated; /* 32 +8 */ 91 | char *request_uri; /* 40 +8 */ 92 | }; 93 | 94 | struct __attribute__((__packed__)) _sapi_globals_struct_84 { 95 | uint8_t pad0[8]; /* 0 +8 */ 96 | sapi_request_info_84 request_info; /* 8 +48 */ 97 | uint8_t pad1[384]; /* 56 +384 */ 98 | double global_request_time; /* 440 +8 */ 99 | }; 100 | 101 | union __attribute__((__packed__)) _zend_value_84 { 102 | long lval; /* 0 +8 */ 103 | double dval; /* 0 +8 */ 104 | zend_string_84 *str; /* 0 +8 */ 105 | zend_array_84 *arr; /* 0 +8 */ 106 | }; 107 | 108 | struct __attribute__((__packed__)) _zval_84 { 109 | zend_value_84 value; /* 0 +8 */ 110 | union { 111 | struct { 112 | uint8_t type; /* 8 +1 */ 113 | uint8_t pad0[3]; /* 9 +3 */ 114 | } v; 115 | } u1; 116 | union { 117 | uint32_t next; /* 12 +4 */ 118 | } u2; 119 | }; 120 | 121 | struct __attribute__((__packed__)) _Bucket_84 { 122 | zval_84 val; /* 0 +16 */ 123 | uint64_t h; /* 16 +8 */ 124 | zend_string_84 *key; /* 24 +8 */ 125 | }; 126 | 127 | struct __attribute__((__packed__)) _zend_alloc_globals_84 { 128 | zend_mm_heap_84 *mm_heap; /* 0 +8 */ 129 | }; 130 | 131 | struct __attribute__((__packed__)) _zend_mm_heap_84 { 132 | uint8_t pad0[16]; /* 0 +16 */ 133 | size_t size; /* 16 +8 */ 134 | size_t peak; /* 24 +8 */ 135 | }; 136 | 137 | #endif 138 | -------------------------------------------------------------------------------- /phpspy.h: -------------------------------------------------------------------------------- 1 | #ifndef __PHPSPY_H 2 | #define __PHPSPY_H 3 | 4 | #ifndef _GNU_SOURCE 5 | #define _GNU_SOURCE 6 | #endif 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | #ifdef USE_ZEND 34 | #include
35 | #undef ZEND_DEBUG 36 | #define ZEND_DEBUG 0 37 | #include
38 | #undef snprintf 39 | #undef vsnprintf 40 | #undef HASH_ADD 41 | #else 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include 47 | #include 48 | #include 49 | #include 50 | #include 51 | #include 52 | #endif 53 | 54 | #ifndef gettid 55 | #define gettid() syscall(SYS_gettid) 56 | #endif 57 | 58 | #include 59 | 60 | #define try(__rv, __call) do { if (((__rv) = (__call)) != 0) return (__rv); } while(0) 61 | #define try_break(__rv, __call) do { if (((__rv) = (__call)) != 0) break; } while(0) 62 | 63 | #define STR1(s) #s 64 | #define STR2(s) STR1(s) 65 | 66 | #define PHPSPY_VERSION "0.7.0" 67 | #define PHPSPY_MIN(a, b) ((a) < (b) ? (a) : (b)) 68 | #define PHPSPY_MAX(a, b) ((a) > (b) ? (a) : (b)) 69 | #define PHPSPY_STR_SIZE 256 70 | #define PHPSPY_MAX_ARRAY_BUCKETS 128 71 | #define PHPSPY_MAX_ARRAY_TABLE_SIZE 512 72 | 73 | #define PHPSPY_OK 0 74 | #define PHPSPY_ERR 1 75 | #define PHPSPY_ERR_PID_DEAD 2 76 | #define PHPSPY_ERR_BUF_FULL 4 77 | #define PHPSPY_ERR_SKIPPED 8 78 | 79 | #define PHPSPY_TRACE_EVENT_INIT 0 80 | #define PHPSPY_TRACE_EVENT_STACK_BEGIN 1 81 | #define PHPSPY_TRACE_EVENT_FRAME 2 82 | #define PHPSPY_TRACE_EVENT_VARPEEK 3 83 | #define PHPSPY_TRACE_EVENT_GLOPEEK 4 84 | #define PHPSPY_TRACE_EVENT_REQUEST 5 85 | #define PHPSPY_TRACE_EVENT_MEM 6 86 | #define PHPSPY_TRACE_EVENT_STACK_END 7 87 | #define PHPSPY_TRACE_EVENT_ERROR 8 88 | #define PHPSPY_TRACE_EVENT_DEINIT 9 89 | 90 | #ifndef USE_ZEND 91 | #define IS_UNDEF 0 92 | #define IS_NULL 1 93 | #define IS_FALSE 2 94 | #define IS_TRUE 3 95 | #define IS_LONG 4 96 | #define IS_DOUBLE 5 97 | #define IS_STRING 6 98 | #define IS_ARRAY 7 99 | #define IS_OBJECT 8 100 | #define IS_RESOURCE 9 101 | #define IS_REFERENCE 10 102 | #endif 103 | 104 | typedef struct varpeek_var_s { 105 | char name[PHPSPY_STR_SIZE]; 106 | UT_hash_handle hh; 107 | } varpeek_var_t; 108 | 109 | typedef struct varpeek_entry_s { 110 | char filename_lineno[PHPSPY_STR_SIZE]; 111 | varpeek_var_t *varmap; 112 | UT_hash_handle hh; 113 | } varpeek_entry_t; 114 | 115 | typedef struct glopeek_entry_s { 116 | char key[PHPSPY_STR_SIZE]; 117 | char gloname[PHPSPY_STR_SIZE]; /* The name of the superglobal array */ 118 | char varname[PHPSPY_STR_SIZE]; /* The name of the global variable within the superglobal array */ 119 | UT_hash_handle hh; 120 | } glopeek_entry_t; 121 | 122 | typedef struct trace_loc_s { 123 | char func[PHPSPY_STR_SIZE]; 124 | char class[PHPSPY_STR_SIZE]; 125 | char file[PHPSPY_STR_SIZE]; 126 | size_t func_len; 127 | size_t class_len; 128 | size_t file_len; 129 | int lineno; 130 | } trace_loc_t; 131 | 132 | typedef struct trace_frame_s { 133 | trace_loc_t loc; 134 | int depth; 135 | } trace_frame_t; 136 | 137 | typedef struct trace_request_s { 138 | char uri[PHPSPY_STR_SIZE]; 139 | char path[PHPSPY_STR_SIZE]; 140 | char qstring[PHPSPY_STR_SIZE]; 141 | char cookie[PHPSPY_STR_SIZE]; 142 | double ts; 143 | } trace_request_t; 144 | 145 | typedef struct trace_mem_s { 146 | size_t size; 147 | size_t peak; 148 | } trace_mem_t; 149 | 150 | typedef struct trace_varpeek_s { 151 | varpeek_entry_t *entry; 152 | varpeek_var_t *var; 153 | char *zval_str; 154 | } trace_varpeek_t; 155 | 156 | typedef struct trace_glopeek_s { 157 | glopeek_entry_t *gentry; 158 | char *zval_str; 159 | } trace_glopeek_t; 160 | 161 | typedef struct trace_target_s { 162 | pid_t pid; 163 | uint64_t executor_globals_addr; 164 | uint64_t sapi_globals_addr; 165 | uint64_t alloc_globals_addr; 166 | uint64_t basic_functions_module_addr; 167 | } trace_target_t; 168 | 169 | typedef struct trace_context_s { 170 | trace_target_t target; 171 | struct { 172 | trace_frame_t frame; 173 | trace_request_t request; 174 | trace_mem_t mem; 175 | trace_varpeek_t varpeek; 176 | trace_glopeek_t glopeek; 177 | } event; 178 | void *event_udata; 179 | int (*event_handler)(struct trace_context_s *context, int event_type); 180 | const char *event_handler_opts; 181 | char buf[PHPSPY_STR_SIZE]; 182 | size_t buf_len; 183 | } trace_context_t; 184 | 185 | typedef struct addr_memo_s { 186 | char php_bin_path[PHPSPY_STR_SIZE]; 187 | char php_bin_path_root[PHPSPY_STR_SIZE]; 188 | uint64_t php_base_addr; 189 | } addr_memo_t; 190 | 191 | #ifndef USE_ZEND 192 | struct __attribute__((__packed__)) _zend_module_entry { 193 | uint8_t pad0[88]; 194 | const char *version; 195 | }; 196 | #endif 197 | 198 | extern char *opt_pgrep_args; 199 | extern int done; 200 | extern int opt_num_workers; 201 | extern pid_t opt_pid; 202 | extern char opt_frame_delim; 203 | extern char opt_trace_delim; 204 | extern char *opt_path_output; 205 | extern regex_t *opt_filter_re; 206 | extern int opt_filter_negate; 207 | extern int opt_verbose_fields_pid; 208 | extern int opt_verbose_fields_ts; 209 | extern int opt_continue_on_error; 210 | extern int opt_fout_buffer_size; 211 | extern long opt_time_limit_ms; 212 | extern char *opt_libname_awk_patt; 213 | 214 | extern int main_pgrep(); 215 | extern int main_pid(pid_t pid); 216 | extern int main_top(int argc, char **argv); 217 | 218 | extern void usage(FILE *fp, int exit_code); 219 | extern int get_symbol_addr(addr_memo_t *memo, pid_t pid, const char *symbol, uint64_t *raddr); 220 | extern int event_handler_fout(struct trace_context_s *context, int event_type); 221 | extern int event_handler_callgrind(struct trace_context_s *context, int event_type); 222 | extern void write_done_pipe(); 223 | extern void log_error(const char *fmt, ...); 224 | extern uint64_t phpspy_zend_inline_hash_func(const char *str, size_t len); 225 | extern int shell_escape(const char *arg, char *buf, size_t buf_size, const char *what); 226 | 227 | #endif 228 | -------------------------------------------------------------------------------- /pgrep.c: -------------------------------------------------------------------------------- 1 | #include "phpspy.h" 2 | 3 | static int wait_for_turn(char producer_or_consumer); 4 | static void pgrep_for_pids(); 5 | static void *run_work_thread(void *arg); 6 | static int is_already_attached(int pid); 7 | static void init_work_threads(); 8 | static void deinit_work_threads(); 9 | static int block_all_signals(); 10 | static void handle_signal(int signum); 11 | static void *run_signal_thread(void *arg); 12 | 13 | static int *avail_pids = NULL; 14 | static int *attached_pids = NULL; 15 | static pthread_t *work_threads = NULL; 16 | static pthread_t signal_thread; 17 | static int avail_pids_count = 0; 18 | static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; 19 | static pthread_cond_t can_produce = PTHREAD_COND_INITIALIZER; 20 | static pthread_cond_t can_consume = PTHREAD_COND_INITIALIZER; 21 | static int done_pipe[2] = { -1, -1 }; 22 | 23 | int main_pgrep() { 24 | long i; 25 | 26 | if (opt_num_workers < 1) { 27 | log_error("Expected max concurrent workers (-T) > 0\n"); 28 | exit(1); 29 | } 30 | 31 | pthread_create(&signal_thread, NULL, run_signal_thread, NULL); 32 | block_all_signals(); 33 | 34 | init_work_threads(); 35 | 36 | for (i = 0; i < opt_num_workers; i++) { 37 | pthread_create(&work_threads[i], NULL, run_work_thread, (void*)i); 38 | } 39 | 40 | if (opt_time_limit_ms > 0) { 41 | alarm(PHPSPY_MAX(1, opt_time_limit_ms / 1000)); 42 | } 43 | 44 | pgrep_for_pids(); 45 | 46 | for (i = 0; i < opt_num_workers; i++) { 47 | pthread_join(work_threads[i], NULL); 48 | } 49 | pthread_join(signal_thread, NULL); 50 | 51 | deinit_work_threads(); 52 | 53 | log_error("main_pgrep finished gracefully\n"); 54 | return 0; 55 | } 56 | 57 | static int wait_for_turn(char producer_or_consumer) { 58 | struct timespec timeout; 59 | pthread_mutex_lock(&mutex); 60 | while (!done) { 61 | if (producer_or_consumer == 'p' && avail_pids_count < opt_num_workers) { 62 | break; 63 | } else if (avail_pids_count > 0) { 64 | break; 65 | } 66 | clock_gettime(CLOCK_REALTIME, &timeout); 67 | timeout.tv_sec += 2; 68 | pthread_cond_timedwait( 69 | producer_or_consumer == 'p' ? &can_produce : &can_consume, 70 | &mutex, 71 | &timeout 72 | ); 73 | } 74 | if (done) { 75 | pthread_mutex_unlock(&mutex); 76 | return 1; 77 | } 78 | return 0; 79 | } 80 | 81 | static void pgrep_for_pids() { 82 | FILE *pcmd; 83 | char *pgrep_cmd; 84 | char line[64]; 85 | int pid; 86 | int found; 87 | struct timespec timeout; 88 | if (asprintf(&pgrep_cmd, "pgrep %s", opt_pgrep_args) < 0) { 89 | errno = ENOMEM; 90 | perror("asprintf"); 91 | exit(1); 92 | } 93 | while (!done) { 94 | if (wait_for_turn('p')) break; 95 | found = 0; 96 | if ((pcmd = popen(pgrep_cmd, "r")) != NULL) { 97 | while (avail_pids_count < opt_num_workers && fgets(line, sizeof(line), pcmd) != NULL) { 98 | if (strlen(line) < 1 || *line == '\n') continue; 99 | pid = atoi(line); 100 | if (is_already_attached(pid)) continue; 101 | avail_pids[avail_pids_count++] = pid; 102 | found += 1; 103 | } 104 | pclose(pcmd); 105 | } 106 | if (found > 0) { 107 | pthread_cond_broadcast(&can_consume); 108 | } else { 109 | clock_gettime(CLOCK_REALTIME, &timeout); 110 | timeout.tv_sec += 2; 111 | pthread_cond_timedwait( 112 | &can_produce, 113 | &mutex, 114 | &timeout 115 | ); 116 | } 117 | pthread_mutex_unlock(&mutex); 118 | } 119 | free(pgrep_cmd); 120 | } 121 | 122 | static void *run_work_thread(void *arg) { 123 | int worker_num; 124 | worker_num = (long)arg; 125 | while (!done) { 126 | if (wait_for_turn('c')) break; 127 | attached_pids[worker_num] = avail_pids[--avail_pids_count]; 128 | pthread_cond_signal(&can_produce); 129 | pthread_mutex_unlock(&mutex); 130 | main_pid(attached_pids[worker_num]); 131 | attached_pids[worker_num] = 0; 132 | } 133 | return NULL; 134 | } 135 | 136 | static int is_already_attached(int pid) { 137 | int i; 138 | for (i = 0; i < opt_num_workers; i++) { 139 | if (attached_pids[i] == pid) { 140 | return 1; 141 | } else if (i < avail_pids_count && avail_pids[i] == pid) { 142 | return 1; 143 | } 144 | } 145 | return 0; 146 | } 147 | 148 | static void init_work_threads() { 149 | avail_pids = calloc(opt_num_workers, sizeof(int)); 150 | attached_pids = calloc(opt_num_workers, sizeof(int)); 151 | work_threads = calloc(opt_num_workers, sizeof(pthread_t)); 152 | if (!avail_pids || !attached_pids || !work_threads) { 153 | errno = ENOMEM; 154 | perror("calloc"); 155 | exit(1); 156 | } 157 | pthread_mutex_init(&mutex, NULL); 158 | pthread_cond_init(&can_produce, NULL); 159 | pthread_cond_init(&can_consume, NULL); 160 | } 161 | 162 | static void deinit_work_threads() { 163 | free(avail_pids); 164 | free(attached_pids); 165 | free(work_threads); 166 | pthread_mutex_destroy(&mutex); 167 | pthread_cond_destroy(&can_produce); 168 | pthread_cond_destroy(&can_consume); 169 | } 170 | 171 | static int block_all_signals() { 172 | int rv; 173 | sigset_t set; 174 | try(rv, sigfillset(&set)); 175 | try(rv, sigprocmask(SIG_BLOCK, &set, NULL)); 176 | return 0; 177 | } 178 | 179 | void write_done_pipe() { 180 | int rv, ignore; 181 | if (done_pipe[1] >= 0) { 182 | ignore = 1; 183 | rv = write(done_pipe[1], &ignore, sizeof(int)); 184 | } 185 | (void)rv; 186 | } 187 | 188 | static void handle_signal(int signum) { 189 | (void)signum; 190 | write_done_pipe(); 191 | } 192 | 193 | static void *run_signal_thread(void *arg) { 194 | int rv, ignore; 195 | fd_set rfds; 196 | struct timeval tv; 197 | struct sigaction sa; 198 | 199 | (void)arg; 200 | 201 | /* Create done_pipe */ 202 | rv = pipe(done_pipe); 203 | rv = fcntl(done_pipe[1], F_SETFL, O_NONBLOCK); 204 | 205 | /* Install signal handler */ 206 | memset(&sa, 0, sizeof(struct sigaction)); 207 | sa.sa_handler = handle_signal; 208 | sigaction(SIGINT, &sa, NULL); 209 | sigaction(SIGTERM, &sa, NULL); 210 | sigaction(SIGHUP, &sa, NULL); 211 | sigaction(SIGALRM, &sa, NULL); 212 | sa.sa_handler = SIG_IGN; 213 | sigaction(SIGPIPE, &sa, NULL); 214 | 215 | /* Wait for write on done_pipe from write_done_pipe */ 216 | do { 217 | FD_ZERO(&rfds); 218 | FD_SET(done_pipe[0], &rfds); 219 | tv.tv_sec = 1; 220 | tv.tv_usec = 0; 221 | rv = select(done_pipe[0]+1, &rfds, NULL, NULL, &tv); 222 | } while (rv < 1); 223 | 224 | /* Read pipe for fun */ 225 | rv = read(done_pipe[0], &ignore, sizeof(int)); 226 | 227 | /* Set done flag; wake up all threads */ 228 | done = 1; 229 | pthread_mutex_lock(&mutex); 230 | pthread_cond_broadcast(&can_consume); 231 | pthread_cond_broadcast(&can_produce); 232 | pthread_mutex_unlock(&mutex); 233 | 234 | return NULL; 235 | } 236 | -------------------------------------------------------------------------------- /event_fout.c: -------------------------------------------------------------------------------- 1 | #include "phpspy.h" 2 | 3 | typedef struct event_handler_fout_udata_s { 4 | int fd; 5 | char *buf; 6 | char *cur; 7 | size_t buf_size; 8 | size_t rem; 9 | int use_mutex; 10 | } event_handler_fout_udata_t; 11 | 12 | static int event_handler_fout_write(event_handler_fout_udata_t *udata); 13 | static int event_handler_fout_snprintf(char **s, size_t *n, size_t *ret_len, int repl_delim, const char *fmt, ...); 14 | static int event_handler_fout_open(int *fd); 15 | static pthread_mutex_t event_handler_fout_mutex = PTHREAD_MUTEX_INITIALIZER; 16 | 17 | int event_handler_fout(struct trace_context_s *context, int event_type) { 18 | int rv, fd; 19 | size_t len; 20 | trace_frame_t *frame; 21 | trace_request_t *request; 22 | event_handler_fout_udata_t *udata; 23 | struct timeval tv; 24 | 25 | udata = (event_handler_fout_udata_t*)context->event_udata; 26 | if (!udata && event_type != PHPSPY_TRACE_EVENT_INIT) { 27 | return PHPSPY_ERR; 28 | } 29 | len = 0; 30 | switch (event_type) { 31 | case PHPSPY_TRACE_EVENT_INIT: 32 | try(rv, event_handler_fout_open(&fd)); 33 | udata = calloc(1, sizeof(event_handler_fout_udata_t)); 34 | udata->fd = fd; 35 | udata->buf_size = opt_fout_buffer_size + 1; /* + 1 for null char */ 36 | udata->buf = malloc(udata->buf_size); 37 | udata->cur = udata->buf; 38 | udata->rem = udata->buf_size; 39 | udata->use_mutex = context->event_handler_opts != NULL 40 | && strchr(context->event_handler_opts, 'm') != NULL ? 1 : 0; 41 | context->event_udata = udata; 42 | break; 43 | case PHPSPY_TRACE_EVENT_STACK_BEGIN: 44 | udata->cur = udata->buf; 45 | udata->cur[0] = '\0'; 46 | udata->rem = udata->buf_size; 47 | break; 48 | case PHPSPY_TRACE_EVENT_FRAME: 49 | frame = &context->event.frame; 50 | try(rv, event_handler_fout_snprintf( 51 | &udata->cur, 52 | &udata->rem, 53 | &len, 54 | 1, 55 | "%d %.*s%s%.*s %.*s:%d", 56 | frame->depth, 57 | (int)frame->loc.class_len, frame->loc.class, 58 | frame->loc.class_len > 0 ? "::" : "", 59 | (int)frame->loc.func_len, frame->loc.func, 60 | (int)frame->loc.file_len, frame->loc.file, 61 | frame->loc.lineno 62 | )); 63 | try(rv, event_handler_fout_snprintf(&udata->cur, &udata->rem, &len, 0, "%c", opt_frame_delim)); 64 | break; 65 | case PHPSPY_TRACE_EVENT_VARPEEK: 66 | try(rv, event_handler_fout_snprintf( 67 | &udata->cur, 68 | &udata->rem, 69 | &len, 70 | 1, 71 | "# varpeek %s@%s = %s", 72 | context->event.varpeek.var->name, 73 | context->event.varpeek.entry->filename_lineno, 74 | context->event.varpeek.zval_str 75 | )); 76 | try(rv, event_handler_fout_snprintf(&udata->cur, &udata->rem, &len, 0, "%c", opt_frame_delim)); 77 | break; 78 | case PHPSPY_TRACE_EVENT_GLOPEEK: 79 | try(rv, event_handler_fout_snprintf( 80 | &udata->cur, 81 | &udata->rem, 82 | &len, 83 | 1, 84 | "# glopeek %s = %s", 85 | context->event.glopeek.gentry->key, 86 | context->event.glopeek.zval_str 87 | )); 88 | try(rv, event_handler_fout_snprintf(&udata->cur, &udata->rem, &len, 0, "%c", opt_frame_delim)); 89 | break; 90 | case PHPSPY_TRACE_EVENT_REQUEST: 91 | request = &context->event.request; 92 | try(rv, event_handler_fout_snprintf(&udata->cur, &udata->rem, &len, 1, "# uri = %s", request->uri)); 93 | try(rv, event_handler_fout_snprintf(&udata->cur, &udata->rem, &len, 0, "%c", opt_frame_delim)); 94 | try(rv, event_handler_fout_snprintf(&udata->cur, &udata->rem, &len, 1, "# path = %s", request->path)); 95 | try(rv, event_handler_fout_snprintf(&udata->cur, &udata->rem, &len, 0, "%c", opt_frame_delim)); 96 | try(rv, event_handler_fout_snprintf(&udata->cur, &udata->rem, &len, 1, "# qstring = %s", request->qstring)); 97 | try(rv, event_handler_fout_snprintf(&udata->cur, &udata->rem, &len, 0, "%c", opt_frame_delim)); 98 | try(rv, event_handler_fout_snprintf(&udata->cur, &udata->rem, &len, 1, "# cookie = %s", request->cookie)); 99 | try(rv, event_handler_fout_snprintf(&udata->cur, &udata->rem, &len, 0, "%c", opt_frame_delim)); 100 | try(rv, event_handler_fout_snprintf(&udata->cur, &udata->rem, &len, 1, "# ts = %f", request->ts)); 101 | try(rv, event_handler_fout_snprintf(&udata->cur, &udata->rem, &len, 0, "%c", opt_frame_delim)); 102 | break; 103 | case PHPSPY_TRACE_EVENT_MEM: 104 | try(rv, event_handler_fout_snprintf( 105 | &udata->cur, 106 | &udata->rem, 107 | &len, 108 | 1, 109 | "# mem %lu %lu", 110 | (uint64_t)context->event.mem.size, 111 | (uint64_t)context->event.mem.peak 112 | )); 113 | try(rv, event_handler_fout_snprintf(&udata->cur, &udata->rem, &len, 0, "%c", opt_frame_delim)); 114 | break; 115 | case PHPSPY_TRACE_EVENT_STACK_END: 116 | if (udata->cur == udata->buf) { 117 | /* buffer is empty */ 118 | break; 119 | } 120 | if (opt_filter_re) { 121 | rv = regexec(opt_filter_re, udata->buf, 0, NULL, 0); 122 | if (opt_filter_negate == 0 && rv != 0) return PHPSPY_ERR_SKIPPED; 123 | if (opt_filter_negate != 0 && rv == 0) return PHPSPY_ERR_SKIPPED; 124 | } 125 | do { 126 | if (opt_verbose_fields_ts) { 127 | gettimeofday(&tv, NULL); 128 | try_break(rv, event_handler_fout_snprintf(&udata->cur, &udata->rem, &len, 1, "# trace_ts = %f", (double)(tv.tv_sec + tv.tv_usec / 1000000.0))); 129 | try_break(rv, event_handler_fout_snprintf(&udata->cur, &udata->rem, &len, 0, "%c", opt_frame_delim)); 130 | } 131 | if (opt_verbose_fields_pid) { 132 | try_break(rv, event_handler_fout_snprintf(&udata->cur, &udata->rem, &len, 1, "# pid = %d", context->target.pid)); 133 | try_break(rv, event_handler_fout_snprintf(&udata->cur, &udata->rem, &len, 0, "%c", opt_frame_delim)); 134 | } 135 | try_break(rv, event_handler_fout_snprintf(&udata->cur, &udata->rem, &len, 0, "%c", opt_trace_delim)); 136 | } while (0); 137 | try(rv, event_handler_fout_write(udata)); 138 | break; 139 | case PHPSPY_TRACE_EVENT_DEINIT: 140 | close(udata->fd); 141 | free(udata->buf); 142 | free(udata); 143 | break; 144 | } 145 | return PHPSPY_OK; 146 | } 147 | 148 | static int event_handler_fout_write(event_handler_fout_udata_t *udata) { 149 | int rv; 150 | ssize_t write_len; 151 | 152 | rv = PHPSPY_OK; 153 | write_len = (udata->cur - udata->buf); 154 | 155 | if (write_len < 1) { 156 | /* nothing to write */ 157 | return rv; 158 | } 159 | 160 | if (udata->use_mutex) { 161 | pthread_mutex_lock(&event_handler_fout_mutex); 162 | } 163 | 164 | if (write(udata->fd, udata->buf, write_len) != write_len) { 165 | log_error("event_handler_fout: Write failed (%s)\n", errno != 0 ? strerror(errno) : "partial"); 166 | rv = PHPSPY_ERR; 167 | } 168 | 169 | if (udata->use_mutex) { 170 | pthread_mutex_unlock(&event_handler_fout_mutex); 171 | } 172 | 173 | return rv; 174 | } 175 | 176 | static int event_handler_fout_snprintf(char **s, size_t *n, size_t *ret_len, int repl_delim, const char *fmt, ...) { 177 | int len, i; 178 | va_list vl; 179 | char *c; 180 | 181 | va_start(vl, fmt); 182 | len = vsnprintf(*s, *n, fmt, vl); 183 | va_end(vl); 184 | 185 | if (len < 0 || (size_t)len >= *n) { 186 | log_error("event_handler_fout_snprintf: Not enough space in buffer; truncating\n"); 187 | return PHPSPY_ERR | PHPSPY_ERR_BUF_FULL; 188 | } 189 | 190 | if (repl_delim) { 191 | for (i = 0; i < len; i++) { /* TODO optimize */ 192 | c = *s + i; 193 | if (*c == opt_trace_delim || *c == opt_frame_delim) { 194 | *c = '?'; 195 | } 196 | } 197 | } 198 | 199 | *s += len; 200 | *n -= len; 201 | *ret_len = (size_t)len; 202 | 203 | return PHPSPY_OK; 204 | } 205 | 206 | static int event_handler_fout_open(int *fd) { 207 | int tfd = -1, errno_saved; 208 | char *path, *apath = NULL; 209 | 210 | if (strcmp(opt_path_output, "-") == 0) { 211 | tfd = dup(STDOUT_FILENO); 212 | if (tfd < 0) { 213 | perror("event_handler_fout_open: dup"); 214 | return PHPSPY_ERR; 215 | } 216 | *fd = tfd; 217 | return PHPSPY_OK; 218 | } 219 | 220 | if (strstr(opt_path_output, "%d") != NULL) { 221 | if (asprintf(&apath, opt_path_output, gettid()) < 0) { 222 | errno = ENOMEM; 223 | perror("event_handler_fout_open: asprintf"); 224 | return PHPSPY_ERR; 225 | } 226 | path = apath; 227 | } else { 228 | path = opt_path_output; 229 | } 230 | 231 | tfd = open(path, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); 232 | errno_saved = errno; 233 | 234 | if (apath) { 235 | free(apath); 236 | } 237 | 238 | if (tfd < 0) { 239 | errno = errno_saved; 240 | perror("event_handler_fout_open: open"); 241 | return PHPSPY_ERR; 242 | } 243 | 244 | *fd = tfd; 245 | return PHPSPY_OK; 246 | } 247 | -------------------------------------------------------------------------------- /event_callgrind.c: -------------------------------------------------------------------------------- 1 | #include "phpspy.h" 2 | 3 | /* 4 | * $ ./phpspy -j callgrind -o out.cg -- php -r 'for ($i=0; $i<5; $i++) { time_nanosleep(0, 1000 * 1000 * 500); usleep(500 * 1000); }' 5 | * process_vm_readv: No such process 6 | * $ ./gprof2dot.py -o out.dot -f callgrind out.cg && dot -Tsvg out.dot -o out.svg 7 | */ 8 | 9 | #define MAX_STACK_DEPTH 128 10 | 11 | typedef struct { 12 | char loc_str[PHPSPY_STR_SIZE]; 13 | trace_loc_t loc; 14 | uint64_t inclusive; 15 | uint64_t count; 16 | UT_hash_handle hh; 17 | } callgrind_callee_t; 18 | 19 | typedef struct { 20 | char loc_str[PHPSPY_STR_SIZE]; 21 | trace_loc_t loc; 22 | uint64_t exclusive; 23 | callgrind_callee_t *callees; 24 | UT_hash_handle hh; 25 | } callgrind_caller_t; 26 | 27 | typedef struct { 28 | trace_loc_t self[MAX_STACK_DEPTH]; 29 | trace_loc_t prev[MAX_STACK_DEPTH]; 30 | char self_str[MAX_STACK_DEPTH][PHPSPY_STR_SIZE]; 31 | char prev_str[MAX_STACK_DEPTH][PHPSPY_STR_SIZE]; 32 | int self_len; 33 | int prev_len; 34 | FILE *fout; 35 | callgrind_caller_t *callers; 36 | } callgrind_udata_t; 37 | 38 | static int callgrind_open(FILE **fout); 39 | static void callgrind_sprint_loc(char *str, trace_loc_t *loc); 40 | static void callgrind_ingest_frame(callgrind_udata_t *udata, struct trace_context_s *context); 41 | static void callgrind_digest_stack(callgrind_udata_t *udata); 42 | static void callgrind_dump(callgrind_udata_t *udata); 43 | static void callgrind_free(callgrind_udata_t *udata); 44 | static int callgrind_sort_callers(callgrind_caller_t *a, callgrind_caller_t *b); 45 | static int callgrind_sort_callees(callgrind_callee_t *a, callgrind_callee_t *b); 46 | 47 | /* 48 | * prev: (nil) 49 | * self: main a b c c.excl++ 50 | * root.calls[main].incl++ 51 | * main.calls[a].incl++ main.calls[a].count++ 52 | * a.calls[b].incl++ a.calls[b].count++ 53 | * b.calls[c].incl++ b.calls[c].count++ 54 | * 55 | * prev: main a b c 56 | * self: main a b b.excl++ 57 | * root.calls[main].incl++ 58 | * main.calls[a].incl++ 59 | * a.calls[b].incl++ 60 | * 61 | * prev: main a b c 62 | * self: main a b c d d.excl++ 63 | * root.calls[main].incl++ 64 | * main.calls[a].incl++ 65 | * a.calls[b].incl++ 66 | * b.calls[c].incl++ 67 | * c.calls[d].incl++ c.calls[d].count++ 68 | * 69 | * prev: main a b c d 70 | * self: main a b c d d.excl++ 71 | * root.calls[main].incl++ 72 | * main.calls[a].incl++ 73 | * a.calls[b].incl++ 74 | * b.calls[c].incl++ 75 | * c.calls[d].incl++ 76 | * 77 | * prev: main a b c d 78 | * self: main x y y.excl++ 79 | * root.calls[main].incl++ 80 | * main.calls[x].incl++ main.calls[x].count++ 81 | * x.calls[y].incl++ x.calls[y].count++ 82 | */ 83 | 84 | int event_handler_callgrind(struct trace_context_s *context, int event_type) { 85 | callgrind_udata_t *udata; 86 | 87 | udata = (callgrind_udata_t*)context->event_udata; 88 | if (!udata && event_type != PHPSPY_TRACE_EVENT_INIT) { 89 | return 1; 90 | } 91 | switch (event_type) { 92 | case PHPSPY_TRACE_EVENT_INIT: 93 | udata = calloc(1, sizeof(callgrind_udata_t)); 94 | if (callgrind_open(&udata->fout) != 0) { 95 | free(udata); 96 | return 1; 97 | } 98 | context->event_udata = udata; 99 | break; 100 | case PHPSPY_TRACE_EVENT_STACK_BEGIN: 101 | udata->self_len = 0; 102 | break; 103 | case PHPSPY_TRACE_EVENT_FRAME: 104 | callgrind_ingest_frame(udata, context); 105 | break; 106 | case PHPSPY_TRACE_EVENT_STACK_END: 107 | callgrind_digest_stack(udata); 108 | memcpy(udata->prev, udata->self, sizeof(trace_loc_t) * udata->self_len); 109 | memcpy(udata->prev_str, udata->self_str, PHPSPY_STR_SIZE * udata->self_len); 110 | udata->prev_len = udata->self_len; 111 | udata->self_len = 0; 112 | break; 113 | case PHPSPY_TRACE_EVENT_DEINIT: 114 | callgrind_dump(udata); 115 | callgrind_free(udata); 116 | free(udata); 117 | break; 118 | } 119 | return 0; 120 | } 121 | 122 | static void callgrind_ingest_frame(callgrind_udata_t *udata, struct trace_context_s *context) { 123 | if (udata->self_len >= MAX_STACK_DEPTH) { 124 | log_error("callgrind_ingest_frame: Exceeded max stack depth (%d); truncating\n", MAX_STACK_DEPTH); 125 | return; 126 | } 127 | memcpy(&udata->self[udata->self_len], &context->event.frame.loc, sizeof(trace_loc_t)); 128 | callgrind_sprint_loc(udata->self_str[udata->self_len], &context->event.frame.loc); 129 | udata->self_len += 1; 130 | } 131 | 132 | static void callgrind_sprint_loc(char *str, trace_loc_t *loc) { 133 | int len; 134 | len = snprintf(str, PHPSPY_STR_SIZE, 135 | "%.*s%s%.*s %.*s:%d", 136 | (int)loc->class_len, loc->class, 137 | loc->class_len > 0 ? "::" : "", 138 | (int)loc->func_len, loc->func, 139 | (int)loc->file_len, loc->file, 140 | loc->lineno 141 | ); 142 | if (len >= PHPSPY_STR_SIZE) { 143 | log_error("callgrind_sprint_loc: Exceeded max loc len (%d); truncating\n", PHPSPY_STR_SIZE); 144 | } 145 | } 146 | 147 | static void callgrind_digest_stack(callgrind_udata_t *udata) { 148 | callgrind_caller_t *caller, *prev_caller; 149 | callgrind_callee_t *callee; 150 | int i; 151 | 152 | prev_caller = NULL; 153 | 154 | for (i = udata->self_len - 1; i >= 0; i--) { 155 | 156 | /* Find or add caller */ 157 | HASH_FIND_STR(udata->callers, udata->self_str[i], caller); 158 | if (!caller) { 159 | caller = calloc(1, sizeof(callgrind_caller_t)); 160 | strcpy(caller->loc_str, udata->self_str[i]); 161 | memcpy(&caller->loc, &udata->self[i], sizeof(trace_loc_t)); 162 | HASH_ADD_STR(udata->callers, loc_str, caller); 163 | } 164 | 165 | /* Increment exclusive cost if top of call stack */ 166 | if (i == 0) { 167 | caller->exclusive += 1; 168 | } 169 | 170 | if (prev_caller) { 171 | /* Find or add callee in previous caller */ 172 | HASH_FIND_STR(prev_caller->callees, udata->self_str[i], callee); 173 | if (!callee) { 174 | callee = calloc(1, sizeof(callgrind_callee_t)); 175 | strcpy(callee->loc_str, udata->self_str[i]); 176 | memcpy(&callee->loc, &udata->self[i], sizeof(trace_loc_t)); 177 | HASH_ADD_STR(prev_caller->callees, loc_str, callee); 178 | } 179 | 180 | /* Increment inclusive cost */ 181 | callee->inclusive += 1; 182 | 183 | /* Increment call count if frame looks different from frame in prev trace */ 184 | if (i >= udata->prev_len 185 | || strcmp(udata->self_str[i], udata->prev_str[i]) != 0 186 | ) { 187 | callee->count += 1; 188 | } 189 | } 190 | /* Remember previous caller */ 191 | prev_caller = caller; 192 | } 193 | } 194 | 195 | static void callgrind_dump(callgrind_udata_t *udata) { 196 | callgrind_caller_t *caller, *caller_tmp; 197 | callgrind_callee_t *callee, *callee_tmp; 198 | 199 | fprintf(udata->fout, "# callgrind format\n"); 200 | fprintf(udata->fout, "version: 1\n"); 201 | fprintf(udata->fout, "creator: phpspy\n"); 202 | fprintf(udata->fout, "events: Samples\n"); 203 | 204 | HASH_SORT(udata->callers, callgrind_sort_callers); 205 | HASH_ITER(hh, udata->callers, caller, caller_tmp) { 206 | fprintf(udata->fout, "\n"); 207 | fprintf(udata->fout, "fl=%.*s\n", (int)caller->loc.file_len, caller->loc.file); 208 | fprintf(udata->fout, "fn=%.*s%s%.*s\n", (int)caller->loc.class_len, caller->loc.class, caller->loc.class_len > 0 ? "::" : "", (int)caller->loc.func_len, caller->loc.func); 209 | fprintf(udata->fout, "%d %ld\n", caller->loc.lineno, caller->exclusive); 210 | 211 | HASH_SORT(caller->callees, callgrind_sort_callees); 212 | HASH_ITER(hh, caller->callees, callee, callee_tmp) { 213 | fprintf(udata->fout, "\n"); 214 | fprintf(udata->fout, "cfl=%.*s\n", (int)callee->loc.file_len, callee->loc.file); 215 | fprintf(udata->fout, "cfn=%.*s%s%.*s\n", (int)callee->loc.class_len, callee->loc.class, callee->loc.class_len > 0 ? "::" : "", (int)callee->loc.func_len, callee->loc.func); 216 | fprintf(udata->fout, "calls=%ld %d\n", callee->count, callee->loc.lineno); 217 | fprintf(udata->fout, "%d %ld\n", caller->loc.lineno, callee->inclusive); 218 | } 219 | } 220 | } 221 | 222 | static void callgrind_free(callgrind_udata_t *udata) { 223 | callgrind_caller_t *caller, *caller_tmp; 224 | callgrind_callee_t *callee, *callee_tmp; 225 | 226 | HASH_ITER(hh, udata->callers, caller, caller_tmp) { 227 | HASH_ITER(hh, caller->callees, callee, callee_tmp) { 228 | HASH_DEL(caller->callees, callee); 229 | free(callee); 230 | } 231 | HASH_DEL(udata->callers, caller); 232 | free(caller); 233 | } 234 | } 235 | 236 | static int callgrind_sort_callers(callgrind_caller_t *a, callgrind_caller_t *b) { 237 | return strcmp(a->loc_str, b->loc_str); 238 | } 239 | 240 | static int callgrind_sort_callees(callgrind_callee_t *a, callgrind_callee_t *b) { 241 | return strcmp(a->loc_str, b->loc_str); 242 | } 243 | 244 | static int callgrind_open(FILE **fout) { 245 | /* TODO consolidate -o -O -E flags as `-o fn` == "fn[.pid].(out|err) */ 246 | int tfd; 247 | tfd = -1; 248 | if (strcmp(opt_path_output, "-") == 0) { 249 | tfd = dup(STDOUT_FILENO); 250 | *fout = fdopen(tfd, "w"); 251 | } else { 252 | *fout = fopen(opt_path_output, "w"); 253 | } 254 | if (!*fout) { 255 | perror("fopen"); 256 | if (tfd != -1) close(tfd); 257 | return 1; 258 | } 259 | setvbuf(*fout, NULL, _IOLBF, PIPE_BUF); 260 | return 0; 261 | } 262 | -------------------------------------------------------------------------------- /top.c: -------------------------------------------------------------------------------- 1 | #include "phpspy.h" 2 | 3 | #define FUNC_SIZE 256 4 | #define BUF_SIZE 512 5 | 6 | typedef struct func_entry_s { 7 | char func[FUNC_SIZE]; 8 | unsigned long count_excl; 9 | unsigned long count_incl; 10 | unsigned long total_count_excl; 11 | unsigned long total_count_incl; 12 | float percent_excl; 13 | UT_hash_handle hh; 14 | } func_entry_t; 15 | 16 | static int fork_child(int argc, char **argv, pid_t *pid, int *outfd, int *errfd); 17 | static void filter_child_args(int argc, char **argv); 18 | static void read_child_out(int fd); 19 | static void read_child_err(int fd); 20 | static void handle_line(char *line, int line_len); 21 | static void handle_event(struct tb_event *event); 22 | static void display(); 23 | 24 | static func_entry_t *func_map = NULL; 25 | static func_entry_t **func_list = NULL; 26 | static size_t func_list_len = 0; 27 | static size_t func_list_size = 0; 28 | static char buf[BUF_SIZE]; 29 | static size_t buf_len = 0; 30 | static unsigned long total_samp_count = 0; 31 | static unsigned long samp_count = 0; 32 | static unsigned long total_err_count = 0; 33 | static int is_paused = 0; 34 | static char phpspy_args[BUF_SIZE]; 35 | 36 | int main_top(int argc, char **argv) { 37 | fd_set readfds; 38 | int ttyfd, outfd, errfd, maxfd; 39 | pid_t pid; 40 | struct timeval timeout; 41 | struct timespec ts, last_display; 42 | struct tb_event event; 43 | int rc, i; 44 | /* TODO consider doing this by aggregating in-process instead of fork/stdout */ 45 | 46 | outfd = errfd = ttyfd = pid = -1; 47 | 48 | if (opt_pid == -1 && opt_pgrep_args == NULL && optind >= argc) { 49 | /* TODO DRY with main() */ 50 | log_error("Expected pid (-p), pgrep (-P), or command\n\n"); 51 | usage(stderr, 1); 52 | return 1; 53 | } 54 | 55 | /* TODO refactor this function */ 56 | filter_child_args(argc, argv); 57 | snprintf(phpspy_args, BUF_SIZE, "phpspy "); 58 | for (i = 1; i < argc; i++) { 59 | snprintf(phpspy_args+strlen(phpspy_args), BUF_SIZE-strlen(phpspy_args), "%s ", argv[i]); 60 | } 61 | 62 | if ((ttyfd = open("/dev/tty", O_RDONLY)) < 0) { 63 | perror("open"); 64 | return 1; 65 | } 66 | if ((fork_child(argc, argv, &pid, &outfd, &errfd)) < 0) { 67 | return 1; 68 | } 69 | maxfd = PHPSPY_MAX(PHPSPY_MAX(ttyfd, outfd), errfd); 70 | 71 | last_display.tv_sec = 0; 72 | last_display.tv_nsec = 0; 73 | 74 | tb_init(); /* TODO support for ncurses for portability sake? */ 75 | while (!done) { 76 | clock_gettime(CLOCK_MONOTONIC, &ts); 77 | if (last_display.tv_sec == 0 || ts.tv_sec - last_display.tv_sec >= 1) { 78 | display(); 79 | last_display = ts; 80 | } 81 | FD_ZERO(&readfds); 82 | FD_SET(ttyfd, &readfds); 83 | FD_SET(outfd, &readfds); 84 | FD_SET(errfd, &readfds); 85 | timeout.tv_sec = 1; 86 | timeout.tv_usec = 0; 87 | rc = select(maxfd + 1, &readfds, NULL, NULL, &timeout); 88 | if (rc < 0) { 89 | if (errno == EINTR) { 90 | /* Probably due to SIGWINCH (terminal resize) */ 91 | /* Call tb_peek_event to let termbox update dimensions */ 92 | tb_peek_event(&event, 0); 93 | last_display.tv_sec = 0; /* Immediately re-display */ 94 | continue; 95 | } 96 | perror("select"); 97 | break; 98 | } 99 | if (FD_ISSET(outfd, &readfds)) { 100 | read_child_out(outfd); 101 | } 102 | if (FD_ISSET(errfd, &readfds)) { 103 | read_child_err(errfd); 104 | } 105 | if (FD_ISSET(ttyfd, &readfds)) { 106 | event.type = 0; 107 | tb_peek_event(&event, 0); 108 | handle_event(&event); 109 | } 110 | } 111 | tb_shutdown(); 112 | 113 | close(outfd); 114 | close(errfd); 115 | close(ttyfd); 116 | kill(pid, SIGTERM); 117 | waitpid(pid, NULL, 0); 118 | return 0; 119 | } 120 | 121 | static int fork_child(int argc, char **argv, pid_t *pid, int *outfd, int *errfd) { 122 | int pout[2], perr[2]; 123 | (void)argc; 124 | if (pipe(pout) < 0 || pipe(perr) < 0) { 125 | perror("pipe"); 126 | return 1; 127 | } 128 | *pid = fork(); 129 | if (*pid == 0) { 130 | close(pout[0]); 131 | dup2(pout[1], STDOUT_FILENO); 132 | close(pout[1]); 133 | 134 | close(perr[0]); 135 | dup2(perr[1], STDERR_FILENO); 136 | close(perr[1]); 137 | 138 | execvp(argv[0], argv); 139 | perror("execvp"); 140 | exit(1); 141 | } else if (*pid < 0) { 142 | perror("fork"); 143 | return 1; 144 | } 145 | close(pout[1]); 146 | close(perr[1]); 147 | *outfd = pout[0]; 148 | *errfd = perr[0]; 149 | return 0; 150 | } 151 | 152 | static void filter_child_args(int argc, char **argv) { 153 | int i; 154 | for (i = 0; i < argc; i++) { 155 | if (strncmp(argv[i], "-o", sizeof("-o")-1) == 0 156 | || strncmp(argv[i], "--output", sizeof("--output")-1) == 0 157 | ) { 158 | argv[i][1] = '#'; 159 | argv[i][2] = '\0'; 160 | } 161 | if (strncmp(argv[i], "-1", sizeof("-1")-1) == 0 162 | || strncmp(argv[i], "--single-line", sizeof("--single-line")-1) == 0 163 | || strncmp(argv[i], "-t", sizeof("-t")-1) == 0 164 | || strncmp(argv[i], "--top", sizeof("--top")-1) == 0 165 | ) { 166 | argv[i][1] = '@'; 167 | argv[i][2] = '\0'; 168 | } 169 | } 170 | } 171 | 172 | static void read_child_out(int fd) { 173 | size_t rem, line_len; 174 | char *nl; 175 | 176 | ssize_t read_rv; 177 | rem = BUF_SIZE - buf_len; 178 | if (rem < 1) { 179 | buf_len = 0; 180 | rem = BUF_SIZE; 181 | } 182 | 183 | if ((read_rv = read(fd, buf + buf_len, rem)) < 0) { 184 | perror("read"); 185 | return; 186 | } else if (read_rv == 0) { 187 | done = 1; 188 | return; 189 | } 190 | buf_len += read_rv; 191 | 192 | while ((nl = memchr(buf, '\n', buf_len)) != NULL) { 193 | line_len = nl-buf; 194 | handle_line(buf, line_len); 195 | memmove(buf, nl+1, buf_len-(line_len+1)); 196 | buf_len -= line_len+1; 197 | } 198 | } 199 | 200 | static void read_child_err(int fd) { 201 | /* TODO DRY with read_child_out */ 202 | char buf[BUF_SIZE]; 203 | char *nl; 204 | size_t buf_pos; 205 | ssize_t read_rv; 206 | if ((read_rv = read(fd, buf, BUF_SIZE)) < 0) { 207 | perror("read"); 208 | return; 209 | } else if (read_rv == 0) { 210 | done = 1; 211 | return; 212 | } 213 | buf_pos = 0; 214 | while (read_rv > 0 && (nl = memchr(buf+buf_pos, '\n', read_rv)) != NULL) { 215 | total_err_count += 1; 216 | buf_pos += nl-buf+1; 217 | read_rv -= nl-buf+1; 218 | } 219 | } 220 | 221 | static void handle_line(char *line, int line_len) { 222 | unsigned long frame_num; 223 | char *func; 224 | size_t func_len; 225 | func_entry_t *func_el; 226 | 227 | if (line_len < 3) return; 228 | if (line[0] == '#') return; 229 | frame_num = strtoull(line, &func, 10); 230 | if (frame_num == 0 && line[0] != '0') return; 231 | if (*func != ' ') return; 232 | func += 1; 233 | func_len = line_len-(func-line); 234 | if (func_len < 1 || func_len >= FUNC_SIZE) return; 235 | 236 | HASH_FIND(hh, func_map, func, func_len, func_el); 237 | if (!func_el) { 238 | func_el = calloc(1, sizeof(func_entry_t)); 239 | snprintf(func_el->func, FUNC_SIZE, "%.*s", (int)func_len, func); 240 | HASH_ADD_STR(func_map, func, func_el); 241 | func_list_len += 1; 242 | if (func_list_len > func_list_size) { 243 | func_list = realloc(func_list, sizeof(func_entry_t*) * (func_list_size + 1024)); 244 | func_list_size += 1024; 245 | } 246 | func_list[func_list_len-1] = func_el; 247 | } 248 | 249 | if (frame_num == 0) { 250 | samp_count += 1; 251 | func_el->count_excl += 1; 252 | } 253 | func_el->count_incl += 1; 254 | } 255 | 256 | static void handle_event(struct tb_event *event) { 257 | if (event->type != TB_EVENT_KEY) { 258 | return; 259 | } else if (event->ch == 'q') { 260 | done = 1; 261 | } else if (event->ch == 'p') { 262 | is_paused = 1 - is_paused; 263 | } 264 | /* TODO other commands in top mode */ 265 | } 266 | 267 | static int func_list_compare(const void *a, const void *b) { 268 | func_entry_t *fa, *fb; 269 | fa = *((func_entry_t**)a); 270 | fb = *((func_entry_t**)b); 271 | if (fb->count_excl == fa->count_excl) { 272 | if (fb->count_incl == fa->count_incl) { 273 | if (fb->total_count_excl == fa->total_count_excl) { 274 | if (fb->total_count_incl == fa->total_count_incl) { 275 | return 0; 276 | } 277 | return fb->total_count_incl > fa->total_count_incl ? 1 : -1; 278 | } 279 | return fb->total_count_excl > fa->total_count_excl ? 1 : -1; 280 | } 281 | return fb->count_incl > fa->count_incl ? 1 : -1; 282 | } 283 | return fb->count_excl > fa->count_excl ? 1 : -1; 284 | } 285 | 286 | static void display() { 287 | int y, w, h; 288 | func_entry_t *el; 289 | size_t i; 290 | 291 | if (func_list_len > 0) { 292 | qsort(func_list, func_list_len, sizeof(func_entry_t*), func_list_compare); 293 | for (i = 0; i < func_list_len; i++) { 294 | func_list[i]->total_count_excl += func_list[i]->count_excl; 295 | func_list[i]->total_count_incl += func_list[i]->count_incl; 296 | func_list[i]->percent_excl = samp_count < 1 ? 0.f : (100.f * func_list[i]->count_excl / samp_count); 297 | } 298 | } 299 | total_samp_count += samp_count; 300 | 301 | tb_clear(); 302 | w = tb_width(); 303 | h = tb_height(); 304 | y = 0; 305 | tb_printf(0, y++, TB_BOLD, 0, "%s", phpspy_args); 306 | tb_printf(0, y++, 0, 0, "samp_count=%llu err_count=%llu func_count=%llu", total_samp_count, total_err_count, func_list_len); 307 | y++; 308 | tb_printf( 309 | 0, y, TB_BOLD | TB_REVERSE, 0, 310 | "%-10s %-10s %-10s %-10s %-7s ", 311 | "tincl", "texcl", "incl", "excl", "excl%" 312 | ); 313 | tb_printf(52, y++, TB_BOLD | TB_REVERSE, 0, "%-*s", w-52, "func"); 314 | i = 0; 315 | while (y < h && i < func_list_len) { 316 | el = func_list[i++]; 317 | tb_printf(0, y++, 0, 0, 318 | "%-9llu %-9llu %-9llu %-9llu %-6.2f %s", 319 | el->total_count_incl, 320 | el->total_count_excl, 321 | el->count_incl, 322 | el->count_excl, 323 | el->percent_excl, 324 | el->func 325 | ); 326 | } 327 | 328 | for (i = 0; i < func_list_len; i++) { 329 | func_list[i]->count_excl = 0; 330 | func_list[i]->count_incl = 0; 331 | } 332 | samp_count = 0; 333 | 334 | if (!is_paused) tb_present(); 335 | } 336 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # phpspy 2 | 3 | phpspy is a low-overhead sampling profiler for PHP. It works with non-ZTS PHP 4 | 7.0+ with CLI, Apache, and FPM SAPIs on 64-bit Linux 3.2+. 5 | 6 | [![Build Status](https://github.com/adsr/phpspy/actions/workflows/phpspy_test.yml/badge.svg)](https://github.com/adsr/phpspy/actions/workflows/phpspy_test.yml) 7 | 8 | ### Demos 9 | 10 | You can profile PHP scripts: 11 | 12 | ![child](https://i.imgur.com/QE8iJLA.gif) 13 | 14 | You can attach to running PHP processes: 15 | 16 | ![attach cli](https://i.imgur.com/HubBqo0.gif) 17 | 18 | ![attach httpd](https://i.imgur.com/KVY3KXq.gif) 19 | 20 | It has a top-like mode: 21 | 22 | ![top](https://i.imgur.com/Y0NJJ1C.gif) 23 | 24 | It can collect request info, memory usage, and variables: 25 | 26 | ![advanced](https://i.imgur.com/nlUfCWT.gif) 27 | 28 | You can also use it to make flamegraphs like this: 29 | 30 | ![FlameGraph example](https://i.imgur.com/e6P9Arp.png) 31 | 32 | All with no changes to your application and minimal overhead. 33 | 34 | ### Synopsis 35 | 36 | $ git clone https://github.com/adsr/phpspy.git 37 | Cloning into 'phpspy'... 38 | ... 39 | $ cd phpspy 40 | $ make 41 | ... 42 | $ sudo ./phpspy --limit=1000 --pid=$(pgrep -n httpd) >traces 43 | ... 44 | $ ./stackcollapse-phpspy.pl flame.svg 45 | $ google-chrome flame.svg # View flame.svg in browser 46 | 47 | ### Build options 48 | 49 | $ make # Use built-in structs 50 | $ # or 51 | $ USE_ZEND=1 make ... # Use Zend structs (requires PHP development headers) 52 | 53 | ### Usage 54 | $ ./phpspy -h 55 | Usage: 56 | phpspy [options] -p 57 | phpspy [options] -P 58 | phpspy [options] [--] 59 | 60 | Options: 61 | -h, --help Show this help 62 | -p, --pid= Trace PHP process at `pid` 63 | -P, --pgrep= Concurrently trace processes that 64 | match pgrep `args` (see also `-T`) 65 | -T, --threads= Set number of threads to use with `-P` 66 | (default: 16) 67 | -s, --sleep-ns= Sleep `ns` nanoseconds between traces 68 | (see also `-H`) (default: 10101010) 69 | -H, --rate-hz= Trace `hz` times per second 70 | (see also `-s`) (default: 99) 71 | -V, --php-version= Set PHP version 72 | (default: auto; 73 | supported: 70 71 72 73 74 80 81 82) 74 | -l, --limit= Limit total number of traces to capture 75 | (approximate limit in pgrep mode) 76 | (default: 0; 0=unlimited) 77 | -i, --time-limit-ms= Stop tracing after `ms` milliseconds 78 | (second granularity in pgrep mode) 79 | (default: 0; 0=unlimited) 80 | -n, --max-depth= Set max stack trace depth 81 | (default: -1; -1=unlimited) 82 | -r, --request-info= Set request info parts to capture 83 | (q=query c=cookie u=uri p=path 84 | capital=negation) 85 | (default: QCUP; none) 86 | -m, --memory-usage Capture peak and current memory usage 87 | with each trace (requires target PHP 88 | process to have debug symbols) 89 | -o, --output= Write phpspy output to `path` 90 | (default: -; -=stdout) 91 | -O, --child-stdout= Write child stdout to `path` 92 | (default: phpspy.%d.out) 93 | -E, --child-stderr= Write child stderr to `path` 94 | (default: phpspy.%d.err) 95 | -x, --addr-executor-globals= Set address of executor_globals in hex 96 | (default: 0; 0=find dynamically) 97 | -a, --addr-sapi-globals= Set address of sapi_globals in hex 98 | (default: 0; 0=find dynamically) 99 | -1, --single-line Output in single-line mode 100 | -b, --buffer-size= Set output buffer size to `size`. 101 | Note: In `-P` mode, setting this 102 | above PIPE_BUF (4096) may lead to 103 | interlaced writes across threads 104 | unless `-J m` is specified. 105 | (default: 4096) 106 | -f, --filter= Filter output by POSIX regex 107 | (default: none) 108 | -F, --filter-negate= Same as `-f` except negated 109 | -d, --verbose-fields= Set verbose output fields 110 | (p=pid t=timestamp 111 | capital=negation) 112 | (default: PT; none) 113 | -c, --continue-on-error Attempt to continue tracing after 114 | encountering an error 115 | -#, --comment= Ignored; intended for self-documenting 116 | commands 117 | -@, --nothing Ignored 118 | -v, --version Print phpspy version and exit 119 | 120 | Experimental options: 121 | -j, --event-handler= Set event handler (fout, callgrind) 122 | (default: fout) 123 | -J, --event-handler-opts= Set event handler options 124 | (fout: m=use mutex to prevent 125 | interlaced writes on stdout in `-P` 126 | mode) 127 | -S, --pause-process Pause process while reading stacktrace 128 | (unsafe for production!) 129 | -e, --peek-var= Peek at the contents of the var located 130 | at `varspec`, which has the format: 131 | @: 132 | @:- 133 | e.g., xyz@/path/to.php:10-20 134 | -g, --peek-global= Peek at the contents of a global var 135 | located at `glospec`, which has 136 | the format: . 137 | where is one of: 138 | post|get|cookie|server|files|globals 139 | e.g., server.REQUEST_TIME 140 | -t, --top Show dynamic top-like output 141 | 142 | ### Example (variable peek) 143 | 144 | $ sudo ./phpspy -e 'i@/var/www/test/lib/test.php:12' -p $(pgrep -n httpd) | grep varpeek 145 | # varpeek i@/var/www/test/lib/test.php:12 = 42 146 | # varpeek i@/var/www/test/lib/test.php:12 = 42 147 | # varpeek i@/var/www/test/lib/test.php:12 = 43 148 | # varpeek i@/var/www/test/lib/test.php:12 = 44 149 | ... 150 | 151 | ### Example (pgrep daemon mode) 152 | 153 | $ sudo ./phpspy -H1 -T4 -P '-x php' 154 | 0 proc_open :-1 155 | 1 system_with_timeout /home/adam/php-src/run-tests.php:1137 156 | 2 run_test /home/adam/php-src/run-tests.php:1937 157 | 3 run_all_tests /home/adam/php-src/run-tests.php:1215 158 | 4
/home/adam/php-src/run-tests.php:986 159 | # - - - - - 160 | ... 161 | ^C 162 | main_pgrep finished gracefully 163 | 164 | ### Example (httpd) 165 | 166 | $ sudo ./phpspy -p $(pgrep -n httpd) 167 | 0 Memcached::get :-1 168 | 1 Cache_MemcachedToggleable::get /foo/bar/lib/Cache/MemcachedToggleable.php:26 169 | 2 Cache_Memcached::get /foo/bar/lib/Cache/Memcached.php:251 170 | 3 IpDb_CacheBase::getFromCache /foo/bar/lib/IpDb/CacheBase.php:165 171 | 4 IpDb_CacheBase::get /foo/bar/lib/IpDb/CacheBase.php:107 172 | 5 IpDb_CacheBase::contains /foo/bar/lib/IpDb/CacheBase.php:70 173 | 6 IpDb_Botnet::has /foo/bar/lib/IpDb/Botnet.php:32 174 | 7 Security_Rule_IpAddr::__construct /foo/bar/lib/Security/Rule/IpAddr.php:53 175 | 8 Security_Rule_HttpRequestContext::initVariables /foo/bar/lib/Security/Rule/HttpRequestContext.php:22 176 | 9 Security_Rule_Context::__construct /foo/bar/lib/Security/Rule/Context.php:44 177 | 10 Security_Rule_Engine::getContextByName /foo/bar/lib/Security/Rule/Engine.php:225 178 | 11 Security_Rule_Engine::getContextByLocation /foo/bar/lib/Security/Rule/Engine.php:210 179 | 12 Security_Rule_Engine::evaluateActionRules /foo/bar/lib/Security/Rule/Engine.php:116 180 | 13
/foo/bar/lib/bootstrap/api.php:49 181 | 14
/foo/bar/htdocs/v3/public.php:5 182 | # - - - - - 183 | ... 184 | 185 | ### Example (cli child) 186 | 187 | $ ./phpspy -- php -r 'usleep(100000);' 188 | 0 usleep :-1 189 | 1
:-1 190 | 191 | 0 usleep :-1 192 | 1
:-1 193 | 194 | 0 usleep :-1 195 | 1
:-1 196 | 197 | 0 usleep :-1 198 | 1
:-1 199 | 200 | 0 usleep :-1 201 | 1
:-1 202 | 203 | 0 usleep :-1 204 | 1
:-1 205 | 206 | process_vm_readv: No such process 207 | 208 | ### Example (cli attach) 209 | 210 | $ php -r 'sleep(10);' & 211 | [1] 28586 212 | $ sudo ./phpspy -p 28586 213 | 0 sleep :-1 214 | 1
:-1 215 | ... 216 | 217 | ### Example (docker) 218 | $ docker build . -t phpspy 219 | $ docker run -it --cap-add SYS_PTRACE phpspy:latest ./phpspy/phpspy -V73 -r -- php -r 'sleep(1);' 220 | 0 sleep :-1 221 | 1
:-1 222 | ... 223 | 224 | ### Known bugs 225 | 226 | * phpspy may not work with a chrooted mod_php process whose binary lives inside overlayfs. (See [#109][8].) 227 | 228 | ### See also 229 | 230 | * [rbspy][0] for Ruby, the original inspiration for phpspy 231 | * [py-spy][1] for Python 232 | * [Xdebug profiler][2], instrumented profiler 233 | * [php-profiler][3], similar to phpspy but pure PHP 234 | * [sample_prof][4] 235 | * [php-trace][5] 236 | * [Blackfire][6], commercial 237 | * [Tideways][7], commercial 238 | 239 | ### TODO 240 | 241 | * See `grep -ri todo` 242 | * See https://github.com/adsr/phpspy/issues 243 | 244 | [0]: https://github.com/rbspy/rbspy 245 | [1]: https://github.com/benfred/py-spy 246 | [2]: http://www.xdebug.org/docs/profiler 247 | [3]: https://github.com/sj-i/php-profiler 248 | [4]: https://github.com/nikic/sample_prof 249 | [5]: https://github.com/krakjoe/trace 250 | [6]: https://blackfire.io/ 251 | [7]: https://tideways.io/ 252 | [8]: https://github.com/adsr/phpspy/issues/109 253 | -------------------------------------------------------------------------------- /phpspy_trace.c: -------------------------------------------------------------------------------- 1 | #define try_copy_proc_mem(__what, __raddr, __laddr, __size) \ 2 | try(rv, copy_proc_mem(context->target.pid, (__what), (__raddr), (__laddr), (__size))) 3 | 4 | static int trace_stack(trace_context_t *context, zend_execute_data *remote_execute_data, int *depth); 5 | static int trace_request_info(trace_context_t *context); 6 | static int trace_memory_info(trace_context_t *context); 7 | static int trace_globals(trace_context_t *context); 8 | static int trace_locals(trace_context_t *context, zend_op *zop, zend_execute_data *remote_execute_data, zend_op_array *op_array, char *file, int file_len); 9 | 10 | static int copy_executor_globals(trace_context_t *context, zend_executor_globals *executor_globals); 11 | static int copy_zarray_bucket(trace_context_t *context, zend_array *rzarray, const char *key, Bucket *lbucket); 12 | 13 | static int sprint_zstring(trace_context_t *context, const char *what, zend_string *lzstring, char *buf, size_t buf_size, size_t *buf_len); 14 | static int sprint_zval(trace_context_t *context, zval *lzval, char *buf, size_t buf_size, size_t *buf_len); 15 | static int sprint_zarray(trace_context_t *context, zend_array *rzarray, char *buf, size_t buf_size, size_t *buf_len); 16 | static int sprint_zarray_val(trace_context_t *context, zend_array *rzarray, const char *key, char *buf, size_t buf_size, size_t *buf_len); 17 | static int sprint_zarray_bucket(trace_context_t *context, Bucket *lbucket, char *buf, size_t buf_size, size_t *buf_len); 18 | 19 | /********************* 20 | Trace functions 21 | *********************/ 22 | 23 | /** 24 | * Sample a single execution trace. 25 | * 26 | * @param context Trace context 27 | * 28 | * @return int Status code 29 | */ 30 | static int do_trace(trace_context_t *context) { 31 | int rv, depth; 32 | zend_executor_globals executor_globals; 33 | 34 | try(rv, copy_executor_globals(context, &executor_globals)); 35 | try(rv, context->event_handler(context, PHPSPY_TRACE_EVENT_STACK_BEGIN)); 36 | 37 | rv = PHPSPY_OK; 38 | do { 39 | #define maybe_break_on_err() do { \ 40 | if ( (rv & PHPSPY_ERR_PID_DEAD) != 0 \ 41 | || (rv & PHPSPY_ERR_BUF_FULL) != 0 \ 42 | || (rv != PHPSPY_OK && !opt_continue_on_error) \ 43 | ) { \ 44 | goto do_trace_end; \ 45 | } \ 46 | } while(0) 47 | 48 | rv |= trace_stack(context, executor_globals.current_execute_data, &depth); 49 | maybe_break_on_err(); 50 | if (depth < 1) break; 51 | 52 | if (opt_capture_req) { 53 | rv |= trace_request_info(context); 54 | maybe_break_on_err(); 55 | } 56 | 57 | if (opt_capture_mem) { 58 | rv |= trace_memory_info(context); 59 | maybe_break_on_err(); 60 | } 61 | 62 | if (HASH_CNT(hh, glopeek_map) > 0) { 63 | rv |= trace_globals(context); 64 | maybe_break_on_err(); 65 | } 66 | 67 | #undef maybe_break_on_err 68 | } while (0); 69 | 70 | do_trace_end: 71 | if (rv == PHPSPY_OK || opt_continue_on_error) { 72 | try(rv, context->event_handler(context, PHPSPY_TRACE_EVENT_STACK_END)); 73 | } 74 | 75 | return rv; 76 | } 77 | 78 | /** 79 | * Iterate through the callstack and trace each frame with the PHPSPY_TRACE_EVENT_FRAME event. 80 | * 81 | * @param context Trace context 82 | * @param remote_execute_data Remote execute_data 83 | * @param depth Stack depth output 84 | * 85 | * @return int Status code 86 | */ 87 | static int trace_stack(trace_context_t *context, zend_execute_data *remote_execute_data, int *depth) { 88 | int rv; 89 | zend_execute_data execute_data; 90 | zend_function zfunc; 91 | zend_string zstring; 92 | zend_class_entry zce; 93 | zend_op zop; 94 | trace_target_t *target; 95 | trace_frame_t *frame; 96 | 97 | target = &context->target; 98 | frame = &context->event.frame; 99 | *depth = 0; 100 | 101 | while (remote_execute_data && *depth != opt_max_stack_depth) { /* TODO make options struct */ 102 | memset(&execute_data, 0, sizeof(execute_data)); 103 | memset(&zfunc, 0, sizeof(zfunc)); 104 | memset(&zstring, 0, sizeof(zstring)); 105 | memset(&zce, 0, sizeof(zce)); 106 | memset(&zop, 0, sizeof(zop)); 107 | 108 | /* TODO reduce number of copy calls */ 109 | try_copy_proc_mem("execute_data", remote_execute_data, &execute_data, sizeof(execute_data)); 110 | try_copy_proc_mem("zfunc", execute_data.func, &zfunc, sizeof(zfunc)); 111 | if (zfunc.common.function_name) { 112 | try(rv, sprint_zstring(context, "function_name", zfunc.common.function_name, frame->loc.func, sizeof(frame->loc.func), &frame->loc.func_len)); 113 | } else { 114 | frame->loc.func_len = snprintf(frame->loc.func, sizeof(frame->loc.func), "
"); 115 | } 116 | if (zfunc.common.scope) { 117 | try_copy_proc_mem("zce", zfunc.common.scope, &zce, sizeof(zce)); 118 | try(rv, sprint_zstring(context, "class_name", zce.name, frame->loc.class, sizeof(frame->loc.class), &frame->loc.class_len)); 119 | } else { 120 | frame->loc.class[0] = '\0'; 121 | frame->loc.class_len = 0; 122 | } 123 | if (zfunc.type == 2) { 124 | try(rv, sprint_zstring(context, "filename", zfunc.op_array.filename, frame->loc.file, sizeof(frame->loc.file), &frame->loc.file_len)); 125 | frame->loc.lineno = zfunc.op_array.line_start; 126 | /* TODO add comments */ 127 | if (HASH_CNT(hh, varpeek_map) > 0) { 128 | if (copy_proc_mem(target->pid, "opline", (void*)execute_data.opline, &zop, sizeof(zop)) == PHPSPY_OK) { 129 | trace_locals(context, &zop, remote_execute_data, &zfunc.op_array, frame->loc.file, frame->loc.file_len); 130 | } 131 | } 132 | } else { 133 | frame->loc.file_len = snprintf(frame->loc.file, sizeof(frame->loc.file), ""); 134 | frame->loc.lineno = -1; 135 | } 136 | frame->depth = *depth; 137 | try(rv, context->event_handler(context, PHPSPY_TRACE_EVENT_FRAME)); 138 | remote_execute_data = execute_data.prev_execute_data; 139 | *depth += 1; 140 | } 141 | 142 | return PHPSPY_OK; 143 | } 144 | 145 | /** 146 | * Trace request info with the PHPSPY_TRACE_EVENT_REQUEST event. 147 | * 148 | * @param context Trace context 149 | * 150 | * @return int Status code 151 | */ 152 | static int trace_request_info(trace_context_t *context) { 153 | int rv; 154 | sapi_globals_struct sapi_globals; 155 | trace_target_t *target; 156 | trace_request_t *request; 157 | 158 | memset(&sapi_globals, 0, sizeof(sapi_globals)); 159 | request = &context->event.request; 160 | target = &context->target; 161 | 162 | try_copy_proc_mem("sapi_globals", (void*)target->sapi_globals_addr, &sapi_globals, sizeof(sapi_globals)); 163 | #define try_copy_sapi_global_field(__field, __local) do { \ 164 | if ((opt_capture_req_ ## __local) && sapi_globals.request_info.__field) { \ 165 | try_copy_proc_mem(#__field, sapi_globals.request_info.__field, request->__local, PHPSPY_STR_SIZE); \ 166 | } else { \ 167 | request->__local[0] = '-'; \ 168 | request->__local[1] = '\0'; \ 169 | } \ 170 | } while (0) 171 | try_copy_sapi_global_field(query_string, qstring); 172 | try_copy_sapi_global_field(cookie_data, cookie); 173 | try_copy_sapi_global_field(request_uri, uri); 174 | try_copy_sapi_global_field(path_translated, path); 175 | #undef try_copy_sapi_global_field 176 | 177 | request->ts = sapi_globals.global_request_time; 178 | 179 | try(rv, context->event_handler(context, PHPSPY_TRACE_EVENT_REQUEST)); 180 | 181 | return PHPSPY_OK; 182 | } 183 | 184 | /** 185 | * Trace memory usage with the PHPSPY_TRACE_EVENT_MEM event. 186 | * 187 | * @param context Trace context 188 | * 189 | * @return int Status code 190 | */ 191 | static int trace_memory_info(trace_context_t *context) { 192 | #ifdef USE_ZEND 193 | (void)context; 194 | return PHPSPY_ERR; /* zend_alloc_globals is not public */ 195 | #else 196 | int rv; 197 | zend_mm_heap mm_heap; 198 | zend_alloc_globals alloc_globals; 199 | trace_target_t *target; 200 | 201 | memset(&mm_heap, 0, sizeof(mm_heap)); 202 | alloc_globals.mm_heap = NULL; 203 | target = &context->target; 204 | 205 | try_copy_proc_mem("alloc_globals", (void*)target->alloc_globals_addr, &alloc_globals, sizeof(alloc_globals)); 206 | try_copy_proc_mem("mm_heap", alloc_globals.mm_heap, &mm_heap, sizeof(mm_heap)); 207 | context->event.mem.size = mm_heap.size; 208 | context->event.mem.peak = mm_heap.peak; 209 | 210 | try(rv, context->event_handler(context, PHPSPY_TRACE_EVENT_MEM)); 211 | 212 | return PHPSPY_OK; 213 | 214 | #endif 215 | } 216 | 217 | /** 218 | * Trace global variable values with the PHPSPY_TRACE_EVENT_GLOPEEK event. 219 | * 220 | * @param context Trace context 221 | * 222 | * @return int Status code 223 | */ 224 | static int trace_globals(trace_context_t *context) { 225 | int rv; 226 | glopeek_entry_t *gentry, *gentry_tmp; 227 | zend_array *garray; 228 | zend_array *symtable; 229 | Bucket lbucket; 230 | 231 | /* Find the remote address of executor_globals.symbol_table */ 232 | symtable = (zend_array *)(context->target.executor_globals_addr + offsetof(zend_executor_globals, symbol_table)); 233 | 234 | HASH_ITER(hh, glopeek_map, gentry, gentry_tmp) { 235 | 236 | /* Point garray at the zend_array where this global variable resides */ 237 | if (gentry->gloname[0]) { 238 | try(rv, copy_zarray_bucket(context, symtable, gentry->gloname, &lbucket)); 239 | garray = lbucket.val.value.arr; 240 | } else { 241 | garray = symtable; 242 | } 243 | 244 | /* Print the element within the array */ 245 | 246 | rv = sprint_zarray_val(context, garray, gentry->varname, context->buf, sizeof(context->buf), &context->buf_len); 247 | 248 | if (rv == PHPSPY_OK) { 249 | context->event.glopeek.gentry = gentry; 250 | context->event.glopeek.zval_str = context->buf; 251 | try(rv, context->event_handler(context, PHPSPY_TRACE_EVENT_GLOPEEK)); 252 | } 253 | } 254 | 255 | return PHPSPY_OK; 256 | } 257 | 258 | /** 259 | * Trace local variable values with the PHPSPY_TRACE_EVENT_VARPEEK event. 260 | * 261 | * @param context Trace context 262 | * @param zop Local zend_op 263 | * @param remote_execute_data Remote execute_data 264 | * @param op_array Local zend_op_array 265 | * @param file Current filename 266 | * @param file_len Current filename length 267 | * 268 | * @return int Status code 269 | */ 270 | static int trace_locals(trace_context_t *context, zend_op *zop, zend_execute_data *remote_execute_data, zend_op_array *op_array, char *file, int file_len) { 271 | int rv, i, num_vars_found, num_vars_peeking; 272 | char tmp[PHPSPY_STR_SIZE]; 273 | size_t tmp_len; 274 | zend_string *zstrp; 275 | varpeek_entry_t *entry; 276 | varpeek_var_t *var; 277 | char varpeek_key[PHPSPY_STR_SIZE]; 278 | zval zv; 279 | 280 | snprintf(varpeek_key, sizeof(varpeek_key), "%.*s:%d", file_len, file, zop->lineno); 281 | HASH_FIND_STR(varpeek_map, varpeek_key, entry); 282 | if (!entry) return PHPSPY_OK; 283 | 284 | num_vars_found = 0; 285 | num_vars_peeking = HASH_CNT(hh, entry->varmap); 286 | 287 | for (i = 0; i < op_array->last_var; i++) { 288 | try_copy_proc_mem("var", op_array->vars + i, &zstrp, sizeof(zstrp)); 289 | try(rv, sprint_zstring(context, "var", zstrp, tmp, sizeof(tmp), &tmp_len)); 290 | HASH_FIND(hh, entry->varmap, tmp, tmp_len, var); 291 | if (!var) continue; 292 | num_vars_found += 1; 293 | /* See ZEND_CALL_VAR_NUM macro in php-src */ 294 | try_copy_proc_mem("zval", ((zval*)(remote_execute_data)) + ((int)(5 + i)), &zv, sizeof(zv)); 295 | try(rv, sprint_zval(context, &zv, tmp, sizeof(tmp), &tmp_len)); 296 | context->event.varpeek.entry = entry; 297 | context->event.varpeek.var = var; 298 | context->event.varpeek.zval_str = tmp; 299 | try(rv, context->event_handler(context, PHPSPY_TRACE_EVENT_VARPEEK)); 300 | if (num_vars_found >= num_vars_peeking) break; 301 | } 302 | 303 | return PHPSPY_OK; 304 | } 305 | 306 | /******************** 307 | Copy functions 308 | ********************/ 309 | 310 | /** 311 | * Copy executor_globals from the remote process to local memory. 312 | * 313 | * @param context Trace context 314 | * @param executor_globals Local destination executor_globals 315 | * 316 | * @return int Status code 317 | */ 318 | static int copy_executor_globals(trace_context_t *context, zend_executor_globals *executor_globals) { 319 | int rv; 320 | executor_globals->current_execute_data = NULL; 321 | try_copy_proc_mem("executor_globals", (void*)context->target.executor_globals_addr, executor_globals, sizeof(*executor_globals)); 322 | return PHPSPY_OK; 323 | } 324 | 325 | /** 326 | * Copy an element from a remote zend_array to a Bucket in local memory. 327 | * 328 | * @param context Trace context 329 | * @param rzarray Remote zend_array 330 | * @param key Array key of element 331 | * @param lbucket Local Bucket to write to 332 | * 333 | * @return int Status code 334 | */ 335 | static int copy_zarray_bucket(trace_context_t *context, zend_array *rzarray, const char *key, Bucket *lbucket) { 336 | int rv; 337 | zend_array lzarray; 338 | uint32_t hash_table_size; 339 | uint64_t hash_val; 340 | uint32_t hash_index; 341 | uint32_t hash_table_val; 342 | uint32_t *hash_bucket; 343 | char tmp_key[PHPSPY_STR_SIZE]; 344 | size_t tmp_len; 345 | 346 | try_copy_proc_mem("array", rzarray, &lzarray, sizeof(lzarray)); 347 | 348 | hash_val = phpspy_zend_inline_hash_func(key, strlen(key)); 349 | hash_table_size = (uint32_t)(-1 * (int32_t)lzarray.nTableMask); 350 | hash_index = hash_val % hash_table_size; 351 | 352 | try_copy_proc_mem("hash_table_val", ((uint32_t*)lzarray.arData) - hash_table_size + hash_index, &hash_table_val, sizeof(uint32_t)); 353 | 354 | hash_bucket = &hash_table_val; 355 | 356 | do { 357 | if (*hash_bucket == (uint32_t)-1) return PHPSPY_ERR; 358 | 359 | /* Copy the next bucket from array data */ 360 | try_copy_proc_mem("bucket", lzarray.arData + *hash_bucket, lbucket, sizeof(Bucket)); 361 | 362 | if (lbucket->key == NULL) { 363 | break; 364 | } 365 | 366 | /* On hash collision, advance to the next bucket */ 367 | try(rv, sprint_zstring(context, "array_key", lbucket->key, tmp_key, sizeof(tmp_key), &tmp_len)); 368 | 369 | if (strcmp(key, tmp_key) == 0) { 370 | hash_bucket = NULL; 371 | } else { 372 | hash_bucket = &lbucket->val.u2.next; 373 | } 374 | } while (hash_bucket); 375 | 376 | return PHPSPY_OK; 377 | } 378 | 379 | /********************* 380 | Print functions 381 | *********************/ 382 | 383 | /** 384 | * Print the value of a remote zend_string to a string buffer. 385 | * 386 | * @param context Trace context 387 | * @param what Memory copy message 388 | * @param rzstring Remote zstring 389 | * @param buf String buffer to write to 390 | * @param buf_size Size of string buffer 391 | * @param buf_len Length of written string 392 | * 393 | * @return int Status code 394 | */ 395 | static int sprint_zstring(trace_context_t *context, const char *what, zend_string *rzstring, char *buf, size_t buf_size, size_t *buf_len) { 396 | int rv; 397 | zend_string lzstring; 398 | 399 | *buf = '\0'; 400 | *buf_len = 0; 401 | try_copy_proc_mem(what, rzstring, &lzstring, sizeof(lzstring)); 402 | *buf_len = PHPSPY_MIN(lzstring.len, PHPSPY_MAX(1, buf_size)-1); 403 | try_copy_proc_mem(what, ((char*)rzstring) + offsetof(zend_string, val), buf, *buf_len); 404 | *(buf + (int)*buf_len) = '\0'; 405 | 406 | return PHPSPY_OK; 407 | } 408 | 409 | /** 410 | * Print the value of a local zval to a string buffer. For some types, this entails copying remote value data. 411 | * 412 | * @param context Trace context 413 | * @param lzval Local zval 414 | * @param buf String buffer to write to 415 | * @param buf_size Size of string buffer 416 | * @param buf_len Length of written string 417 | * 418 | * @return int Status code 419 | */ 420 | static int sprint_zval(trace_context_t *context, zval *lzval, char *buf, size_t buf_size, size_t *buf_len) { 421 | int rv; 422 | int type; 423 | type = (int)lzval->u1.v.type; 424 | switch (type) { 425 | case IS_LONG: 426 | snprintf(buf, buf_size, "%ld", lzval->value.lval); 427 | *buf_len = strlen(buf); 428 | break; 429 | case IS_DOUBLE: 430 | snprintf(buf, buf_size, "%f", lzval->value.dval); 431 | *buf_len = strlen(buf); 432 | break; 433 | case IS_STRING: 434 | try(rv, sprint_zstring(context, "zval", lzval->value.str, buf, buf_size, buf_len)); 435 | break; 436 | case IS_ARRAY: 437 | try(rv, sprint_zarray(context, lzval->value.arr, buf, buf_size, buf_len)); 438 | break; 439 | default: 440 | /* TODO handle other zval types */ 441 | /* fprintf(context->fout, "value not supported, found type: %d\n", type); */ 442 | return PHPSPY_ERR; 443 | } 444 | return PHPSPY_OK; 445 | } 446 | 447 | /** 448 | * Print a comma-separated list of zend_array values to a string buffer. 449 | * 450 | * @param context Trace context 451 | * @param rzarray Remote zend_array 452 | * @param buf String buffer to write to 453 | * @param buf_size Size of string buffer 454 | * @param buf_len Length of written string 455 | * 456 | * @return int Status code 457 | */ 458 | static int sprint_zarray(trace_context_t *context, zend_array *rzarray, char *buf, size_t buf_size, size_t *buf_len) { 459 | int rv; 460 | int i; 461 | int array_len; 462 | size_t tmp_len; 463 | Bucket buckets[PHPSPY_MAX_ARRAY_BUCKETS]; 464 | zend_array lzarray; 465 | char *obuf; 466 | 467 | obuf = buf; 468 | try_copy_proc_mem("array", rzarray, &lzarray, sizeof(lzarray)); 469 | 470 | array_len = PHPSPY_MIN(lzarray.nNumOfElements, PHPSPY_MAX_ARRAY_BUCKETS); 471 | try_copy_proc_mem("buckets", lzarray.arData, buckets, sizeof(Bucket) * array_len); 472 | 473 | for (i = 0; i < array_len; i++) { 474 | try(rv, sprint_zarray_bucket(context, buckets + i, buf, buf_size, &tmp_len)); 475 | buf_size -= tmp_len; 476 | buf += tmp_len; 477 | 478 | /* TODO Introduce a string class to clean this silliness up */ 479 | if (buf_size >= 2) { 480 | *buf = ','; 481 | --buf_size; 482 | ++buf; 483 | } 484 | } 485 | 486 | *buf_len = (size_t)(buf - obuf); 487 | 488 | return PHPSPY_OK; 489 | } 490 | 491 | /** 492 | * Print a single zend_array value to a string buffer. 493 | * 494 | * @param context Trace context 495 | * @param rzarray Remote zend_array 496 | * @param key Array key of element to print 497 | * @param buf String buffer to write to 498 | * @param buf_size Size of string buffer 499 | * @param buf_len Length of written string 500 | * 501 | * @return int Status code 502 | */ 503 | static int sprint_zarray_val(trace_context_t *context, zend_array *rzarray, const char *key, char *buf, size_t buf_size, size_t *buf_len) { 504 | int rv; 505 | Bucket bucket; 506 | 507 | try(rv, copy_zarray_bucket(context, rzarray, key, &bucket)); 508 | try(rv, sprint_zval(context, &bucket.val, buf, buf_size, buf_len)); 509 | 510 | return PHPSPY_OK; 511 | } 512 | 513 | /** 514 | * Print a zend_array Bucket, with its key, to a string buffer. 515 | * 516 | * @param context Trace context 517 | * @param lbucket Local Bucket 518 | * @param buf String buffer to write to 519 | * @param buf_size Size of string buffer 520 | * @param buf_len Length of written string 521 | * 522 | * @return int Status code 523 | */ 524 | static int sprint_zarray_bucket(trace_context_t *context, Bucket *lbucket, char *buf, size_t buf_size, size_t *buf_len) { 525 | int rv; 526 | char tmp_key[PHPSPY_STR_SIZE]; 527 | size_t tmp_len; 528 | char *obuf; 529 | 530 | obuf = buf; 531 | 532 | if (lbucket->key != NULL) { 533 | try(rv, sprint_zstring(context, "array_key", lbucket->key, tmp_key, sizeof(tmp_key), &tmp_len)); 534 | 535 | /* TODO Introduce a string class to clean this silliness up */ 536 | if (buf_size > tmp_len + 1 + 1) { 537 | snprintf(buf, buf_size, "%s=", tmp_key); 538 | buf_size -= tmp_len + 1; 539 | buf += tmp_len + 1; 540 | } 541 | } 542 | 543 | try(rv, sprint_zval(context, &lbucket->val, buf, buf_size, &tmp_len)); 544 | buf += tmp_len; 545 | 546 | *buf_len = (size_t)(buf - obuf); 547 | return PHPSPY_OK; 548 | } 549 | -------------------------------------------------------------------------------- /phpspy.c: -------------------------------------------------------------------------------- 1 | #include "phpspy.h" 2 | 3 | #define TB_IMPL 4 | #define TB_OPT_V1_COMPAT 5 | #include 6 | #undef TB_IMPL 7 | 8 | pid_t opt_pid = -1; 9 | char *opt_pgrep_args = NULL; 10 | int opt_num_workers = 16; 11 | int opt_top_mode = 0; 12 | long opt_sleep_ns = 10101010; /* ~99Hz */ 13 | uint64_t opt_executor_globals_addr = 0; 14 | uint64_t opt_sapi_globals_addr = 0; 15 | int opt_capture_req = 0; 16 | int opt_capture_req_qstring = 0; 17 | int opt_capture_req_cookie = 0; 18 | int opt_capture_req_uri = 0; 19 | int opt_capture_req_path = 0; 20 | int opt_capture_mem = 0; 21 | int opt_max_stack_depth = -1; 22 | char opt_frame_delim = '\n'; 23 | char opt_trace_delim = '\n'; 24 | uint64_t opt_trace_limit = 0; 25 | long opt_time_limit_ms = 0; 26 | char *opt_path_output = "-"; 27 | char *opt_path_child_out = "phpspy.%d.out"; 28 | char *opt_path_child_err = "phpspy.%d.err"; 29 | char *opt_phpv = "auto"; 30 | int opt_pause = 0; 31 | regex_t *opt_filter_re = NULL; 32 | int opt_filter_negate = 0; 33 | int opt_verbose_fields_pid = 0; 34 | int opt_verbose_fields_ts = 0; 35 | int (*opt_event_handler)(struct trace_context_s *context, int event_type) = event_handler_fout; 36 | char *opt_event_handler_opts = NULL; 37 | int opt_continue_on_error = 0; 38 | int opt_fout_buffer_size = 4096; 39 | char *opt_libname_awk_patt = "libphp[78]?"; 40 | 41 | int done = 0; 42 | int (*do_trace_ptr)(trace_context_t *context) = NULL; 43 | varpeek_entry_t *varpeek_map = NULL; 44 | glopeek_entry_t *glopeek_map = NULL; 45 | regex_t filter_re; 46 | int log_error_enabled = 1; 47 | int in_pgrep_mode = 0; 48 | uint64_t trace_count = 0; 49 | 50 | static void parse_opts(int argc, char **argv); 51 | static int main_fork(int argc, char **argv); 52 | static void cleanup(); 53 | static int pause_pid(pid_t pid); 54 | static int unpause_pid(pid_t pid); 55 | static void redirect_child_stdio(int proc_fd, char *opt_path); 56 | static int find_addresses(trace_target_t *target); 57 | static void clock_get(struct timespec *ts); 58 | static void clock_add(struct timespec *a, struct timespec *b, struct timespec *res); 59 | static int clock_diff(struct timespec *a, struct timespec *b); 60 | static void calc_sleep_time(struct timespec *end, struct timespec *start, struct timespec *sleep); 61 | static void varpeek_add(char *varspec); 62 | static void glopeek_add(char *glospec); 63 | static int copy_proc_mem(pid_t pid, const char *what, void *raddr, void *laddr, size_t size); 64 | 65 | #ifdef USE_ZEND 66 | static int do_trace(trace_context_t *context); 67 | #else 68 | static int get_php_version(trace_target_t *target); 69 | static int do_trace_70(trace_context_t *context); 70 | static int do_trace_71(trace_context_t *context); 71 | static int do_trace_72(trace_context_t *context); 72 | static int do_trace_73(trace_context_t *context); 73 | static int do_trace_74(trace_context_t *context); 74 | static int do_trace_80(trace_context_t *context); 75 | static int do_trace_81(trace_context_t *context); 76 | static int do_trace_82(trace_context_t *context); 77 | static int do_trace_83(trace_context_t *context); 78 | static int do_trace_84(trace_context_t *context); 79 | #endif 80 | 81 | int main(int argc, char **argv) { 82 | int rv; 83 | parse_opts(argc, argv); 84 | 85 | if (opt_top_mode != 0) { 86 | rv = main_top(argc, argv); 87 | } else if (opt_pid != -1) { 88 | rv = main_pid(opt_pid); 89 | } else if (opt_pgrep_args != NULL) { 90 | in_pgrep_mode = 1; 91 | rv = main_pgrep(); 92 | } else if (optind < argc) { 93 | rv = main_fork(argc, argv); 94 | } else { 95 | log_error("Expected pid (-p), pgrep (-P), or command\n\n"); 96 | usage(stderr, 1); 97 | rv = 1; 98 | } 99 | cleanup(); 100 | return rv; 101 | } 102 | 103 | void usage(FILE *fp, int exit_code) { 104 | fprintf(fp, "Usage:\n"); 105 | fprintf(fp, " phpspy [options] -p \n"); 106 | fprintf(fp, " phpspy [options] -P \n"); 107 | fprintf(fp, " phpspy [options] [--] \n"); 108 | fprintf(fp, "\n"); 109 | fprintf(fp, "Options:\n"); 110 | fprintf(fp, " -h, --help Show this help\n"); 111 | fprintf(fp, " -p, --pid= Trace PHP process at `pid`\n"); 112 | fprintf(fp, " -P, --pgrep= Concurrently trace processes that\n"); 113 | fprintf(fp, " match pgrep `args` (see also `-T`)\n"); 114 | fprintf(fp, " -T, --threads= Set number of threads to use with `-P`\n"); 115 | fprintf(fp, " (default: %d)\n", opt_num_workers); 116 | fprintf(fp, " -s, --sleep-ns= Sleep `ns` nanoseconds between traces\n"); 117 | fprintf(fp, " (see also `-H`) (default: %ld)\n", opt_sleep_ns); 118 | fprintf(fp, " -H, --rate-hz= Trace `hz` times per second\n"); 119 | fprintf(fp, " (see also `-s`) (default: %lu)\n", 1000000000UL/opt_sleep_ns); 120 | fprintf(fp, " -V, --php-version= Set PHP version\n"); 121 | fprintf(fp, " (default: %s;\n", opt_phpv); 122 | fprintf(fp, " supported: 70 71 72 73 74 80 81 82 83)\n"); 123 | fprintf(fp, " -l, --limit= Limit total number of traces to capture\n"); 124 | fprintf(fp, " (approximate limit in pgrep mode)\n"); 125 | fprintf(fp, " (default: %lu; 0=unlimited)\n", opt_trace_limit); 126 | fprintf(fp, " -i, --time-limit-ms= Stop tracing after `ms` milliseconds\n"); 127 | fprintf(fp, " (second granularity in pgrep mode)\n"); 128 | fprintf(fp, " (default: %lu; 0=unlimited)\n", opt_time_limit_ms); 129 | fprintf(fp, " -n, --max-depth= Set max stack trace depth\n"); 130 | fprintf(fp, " (default: %d; -1=unlimited)\n", opt_max_stack_depth); 131 | fprintf(fp, " -r, --request-info= Set request info parts to capture\n"); 132 | fprintf(fp, " (q=query c=cookie u=uri p=path\n"); 133 | fprintf(fp, " capital=negation)\n"); 134 | fprintf(fp, " (default: QCUP; none)\n"); 135 | fprintf(fp, " -m, --memory-usage Capture peak and current memory usage\n"); 136 | fprintf(fp, " with each trace (requires target PHP\n"); 137 | fprintf(fp, " process to have debug symbols)\n"); 138 | fprintf(fp, " -o, --output= Write phpspy output to `path`\n"); 139 | fprintf(fp, " (default: %s; -=stdout)\n", opt_path_output); 140 | fprintf(fp, " -O, --child-stdout= Write child stdout to `path`\n"); 141 | fprintf(fp, " (default: %s)\n", opt_path_child_out); 142 | fprintf(fp, " -E, --child-stderr= Write child stderr to `path`\n"); 143 | fprintf(fp, " (default: %s)\n", opt_path_child_err); 144 | fprintf(fp, " -x, --addr-executor-globals= Set address of executor_globals in hex\n"); 145 | fprintf(fp, " (default: %lu; 0=find dynamically)\n", opt_sapi_globals_addr); 146 | fprintf(fp, " -a, --addr-sapi-globals= Set address of sapi_globals in hex\n"); 147 | fprintf(fp, " (default: %lu; 0=find dynamically)\n", opt_executor_globals_addr); 148 | fprintf(fp, " -1, --single-line Output in single-line mode\n"); 149 | fprintf(fp, " -b, --buffer-size= Set output buffer size to `size`.\n"); 150 | fprintf(fp, " Note: In `-P` mode, setting this\n"); 151 | fprintf(fp, " above PIPE_BUF (4096) may lead to\n"); 152 | fprintf(fp, " interlaced writes across threads\n"); 153 | fprintf(fp, " unless `-J m` is specified.\n"); 154 | fprintf(fp, " (default: %d)\n", opt_fout_buffer_size); 155 | fprintf(fp, " -f, --filter= Filter output by POSIX regex\n"); 156 | fprintf(fp, " (default: none)\n"); 157 | fprintf(fp, " -F, --filter-negate= Same as `-f` except negated\n"); 158 | fprintf(fp, " -d, --verbose-fields= Set verbose output fields\n"); 159 | fprintf(fp, " (p=pid t=timestamp\n"); 160 | fprintf(fp, " capital=negation)\n"); 161 | fprintf(fp, " (default: PT; none)\n"); 162 | fprintf(fp, " -c, --continue-on-error Attempt to continue tracing after\n"); 163 | fprintf(fp, " encountering an error\n"); 164 | fprintf(fp, " -w, --libname-awk-patt= Awk pattern to match name of PHP lib\n"); 165 | fprintf(fp, " (default: %s)\n", opt_libname_awk_patt); 166 | fprintf(fp, " -#, --comment= Ignored; intended for self-documenting\n"); 167 | fprintf(fp, " commands\n"); 168 | fprintf(fp, " -@, --nothing Ignored\n"); 169 | fprintf(fp, " -v, --version Print phpspy version and exit\n"); 170 | fprintf(fp, "\n"); 171 | fprintf(fp, "Experimental options:\n"); 172 | fprintf(fp, " -j, --event-handler= Set event handler (fout, callgrind)\n"); 173 | fprintf(fp, " (default: fout)\n"); 174 | fprintf(fp, " -J, --event-handler-opts= Set event handler options\n"); 175 | fprintf(fp, " (fout: m=use mutex to prevent\n"); 176 | fprintf(fp, " interlaced writes on stdout in `-P`\n"); 177 | fprintf(fp, " mode)\n"); 178 | fprintf(fp, " -S, --pause-process Pause process while reading stacktrace\n"); 179 | fprintf(fp, " (unsafe for production!)\n"); 180 | fprintf(fp, " -e, --peek-var= Peek at the contents of the var located\n"); 181 | fprintf(fp, " at `varspec`, which has the format:\n"); 182 | fprintf(fp, " @:\n"); 183 | fprintf(fp, " @:-\n"); 184 | fprintf(fp, " e.g., xyz@/path/to.php:10-20\n"); 185 | fprintf(fp, " -g, --peek-global= Peek at the contents of a global var\n"); 186 | fprintf(fp, " located at `glospec`, which has\n"); 187 | fprintf(fp, " the format: .\n"); 188 | fprintf(fp, " where is one of:\n"); 189 | fprintf(fp, " post|get|cookie|server|files|globals\n"); 190 | fprintf(fp, " e.g., server.REQUEST_TIME\n"); 191 | fprintf(fp, " -t, --top Show dynamic top-like output\n"); 192 | cleanup(); 193 | exit(exit_code); 194 | } 195 | 196 | static long strtol_with_min_or_exit(const char *name, const char *str, int min) { 197 | long result; 198 | char *end; 199 | errno = 0; 200 | result = strtol(str, &end, 10); 201 | if (end <= str || *end != '\0') { 202 | /* e.g. reject -H '', -H invalid, -H 5000suffix */ 203 | log_error("Expected integer for %s, got '%s'\n", name, str); 204 | usage(stderr, 1); 205 | } 206 | if (result < min) { 207 | /* e.g. reject -H 0 */ 208 | log_error("Expected integer >= %d for %s, got '%s'\n", min, name, str); 209 | usage(stderr, 1); 210 | } 211 | return result; 212 | } 213 | 214 | static int atoi_with_min_or_exit(const char *name, const char *str, int min) { 215 | long result = strtol_with_min_or_exit(name, str, min); 216 | #if LONG_MAX > INT_MAX 217 | if (result > INT_MAX) { 218 | log_error("Expected value that could fit in a C int for %s, got '%s'\n", name, str); 219 | usage(stderr, 1); 220 | } 221 | #endif 222 | return (int)result; 223 | } 224 | 225 | static void parse_opts(int argc, char **argv) { 226 | int c; 227 | size_t i; 228 | struct option long_opts[] = { 229 | { "help", no_argument, NULL, 'h' }, 230 | { "pid", required_argument, NULL, 'p' }, 231 | { "pgrep", required_argument, NULL, 'P' }, 232 | { "threads", required_argument, NULL, 'T' }, 233 | { "sleep-ns", required_argument, NULL, 's' }, 234 | { "rate-hz", required_argument, NULL, 'H' }, 235 | { "php-version", required_argument, NULL, 'V' }, 236 | { "limit", required_argument, NULL, 'l' }, 237 | { "time-limit-ms", required_argument, NULL, 'i' }, 238 | { "max-depth", required_argument, NULL, 'n' }, 239 | { "request-info", required_argument, NULL, 'r' }, 240 | { "memory-usage", no_argument, NULL, 'm' }, 241 | { "output", required_argument, NULL, 'o' }, 242 | { "child-stdout", required_argument, NULL, 'O' }, 243 | { "child-stderr", required_argument, NULL, 'E' }, 244 | { "addr-executor-globals", required_argument, NULL, 'x' }, 245 | { "addr-sapi-globals", required_argument, NULL, 'a' }, 246 | { "single-line", no_argument, NULL, '1' }, 247 | { "buffer-size", required_argument, NULL, 'b' }, 248 | { "filter", required_argument, NULL, 'f' }, 249 | { "filter-negate", required_argument, NULL, 'F' }, 250 | { "verbose-fields", required_argument, NULL, 'd' }, 251 | { "continue-on-error", no_argument, NULL, 'c' }, 252 | { "event-handler", required_argument, NULL, 'j' }, 253 | { "event-handler-opts", required_argument, NULL, 'J' }, 254 | { "comment", required_argument, NULL, '#' }, 255 | { "nothing", no_argument, NULL, '@' }, 256 | { "version", no_argument, NULL, 'v' }, 257 | { "pause-process", no_argument, NULL, 'S' }, 258 | { "peek-var", required_argument, NULL, 'e' }, 259 | { "peek-global", required_argument, NULL, 'g' }, 260 | { "top", no_argument, NULL, 't' }, 261 | { "libname-awk-patt", required_argument, NULL, 'w' }, 262 | { 0, 0, 0, 0 } 263 | }; 264 | /* Parse options until the first non-option argument is reached. Effectively 265 | * this makes the end-of-options-delimiter (`--`) optional. The following 266 | * invocations are equivalent: 267 | * phpspy --rate-hz=2 php -r 'sleep(1);' 268 | * phpspy --rate-hz=2 -- php -r 'sleep(1);' 269 | */ 270 | optind = 1; 271 | while ( 272 | optind < argc 273 | && argv[optind][0] == '-' 274 | && (c = getopt_long(argc, argv, "hp:P:T:te:s:H:V:l:i:n:r:mo:O:E:x:a:1b:f:F:d:cj:J:#:@vSe:g:tw:", long_opts, NULL)) != -1 275 | ) { 276 | switch (c) { 277 | case 'h': usage(stdout, 0); break; 278 | case 'p': opt_pid = atoi_with_min_or_exit("-p", optarg, 1); break; 279 | case 'P': opt_pgrep_args = optarg; break; 280 | case 'T': opt_num_workers = atoi_with_min_or_exit("-T", optarg, 1); break; 281 | case 's': opt_sleep_ns = strtol_with_min_or_exit("-s", optarg, 1); break; 282 | case 'H': opt_sleep_ns = (1000000000UL / strtol_with_min_or_exit("-H", optarg, 1)); break; 283 | case 'V': opt_phpv = optarg; break; 284 | case 'l': opt_trace_limit = strtoull(optarg, NULL, 10); break; 285 | case 'i': opt_time_limit_ms = strtol_with_min_or_exit("-i", optarg, 0); break; 286 | case 'n': opt_max_stack_depth = atoi_with_min_or_exit("-n", optarg, -1); break; 287 | case 'r': 288 | for (i = 0; i < strlen(optarg); i++) { 289 | switch (optarg[i]) { 290 | case 'q': opt_capture_req_qstring = 1; break; 291 | case 'c': opt_capture_req_cookie = 1; break; 292 | case 'u': opt_capture_req_uri = 1; break; 293 | case 'p': opt_capture_req_path = 1; break; 294 | case 'Q': opt_capture_req_qstring = 0; break; 295 | case 'C': opt_capture_req_cookie = 0; break; 296 | case 'U': opt_capture_req_uri = 0; break; 297 | case 'P': opt_capture_req_path = 0; break; 298 | } 299 | } 300 | opt_capture_req = opt_capture_req_qstring | opt_capture_req_cookie | opt_capture_req_uri | opt_capture_req_path; 301 | break; 302 | case 'm': opt_capture_mem = 1; break; 303 | case 'o': opt_path_output = optarg; break; 304 | case 'O': opt_path_child_out = optarg; break; 305 | case 'E': opt_path_child_err = optarg; break; 306 | case 'x': opt_executor_globals_addr = strtoull(optarg, NULL, 16); break; 307 | case 'a': opt_sapi_globals_addr = strtoull(optarg, NULL, 16); break; 308 | case '1': opt_frame_delim = '\t'; opt_trace_delim = '\n'; break; 309 | case 'b': opt_fout_buffer_size = atoi_with_min_or_exit("-b", optarg, 1); break; 310 | case 'f': 311 | case 'F': 312 | if (opt_filter_re) { 313 | regfree(opt_filter_re); 314 | } 315 | if (regcomp(&filter_re, optarg, REG_EXTENDED | REG_NOSUB | REG_NEWLINE) == 0) { 316 | opt_filter_re = &filter_re; 317 | } else { 318 | log_error("parse_opts: Failed to compile filter regex\n\n"); /* TODO regerror */ 319 | usage(stderr, 1); 320 | } 321 | opt_filter_negate = c == 'F' ? 1 : 0; 322 | break; 323 | case 'd': 324 | for (i = 0; i < strlen(optarg); i++) { 325 | switch (optarg[i]) { 326 | case 'p': opt_verbose_fields_pid = 1; break; 327 | case 't': opt_verbose_fields_ts = 1; break; 328 | case 'P': opt_verbose_fields_pid = 0; break; 329 | case 'T': opt_verbose_fields_ts = 0; break; 330 | } 331 | } 332 | break; 333 | case 'c': opt_continue_on_error = 1; break; 334 | case 'j': 335 | if (strcmp(optarg, "fout") == 0) { 336 | opt_event_handler = event_handler_fout; 337 | } else if (strcmp(optarg, "callgrind") == 0) { 338 | opt_event_handler = event_handler_callgrind; 339 | } else { 340 | log_error("parse_opts: Expected 'fout' or 'callgrind' for `--event-handler`\n\n"); 341 | usage(stderr, 1); 342 | } 343 | break; 344 | case 'J': opt_event_handler_opts = optarg; break; 345 | case '#': break; 346 | case '@': break; 347 | case 'v': 348 | printf( 349 | "phpspy v%s USE_ZEND=%s COMMIT=%s\n", 350 | PHPSPY_VERSION, 351 | #ifdef USE_ZEND 352 | "y" 353 | #else 354 | "n" 355 | #endif 356 | , 357 | #ifdef COMMIT 358 | STR2(COMMIT) 359 | #else 360 | "-" 361 | #endif 362 | ); 363 | exit(0); 364 | case 'S': opt_pause = 1; break; 365 | case 'e': varpeek_add(optarg); break; 366 | case 'g': glopeek_add(optarg); break; 367 | case 't': opt_top_mode = 1; break; 368 | case 'w': opt_libname_awk_patt = optarg; break; 369 | } 370 | } 371 | } 372 | 373 | int main_pid(pid_t pid) { 374 | int rv; 375 | trace_context_t context; 376 | struct timespec start_time, end_time, sleep_time, _stop_time, limit_time; 377 | struct timespec *stop_time; 378 | 379 | memset(&context, 0, sizeof(trace_context_t)); 380 | context.target.pid = pid; 381 | context.event_handler = opt_event_handler; 382 | context.event_handler_opts = opt_event_handler_opts; 383 | try(rv, find_addresses(&context.target)); 384 | try(rv, context.event_handler(&context, PHPSPY_TRACE_EVENT_INIT)); 385 | 386 | #ifdef USE_ZEND 387 | do_trace_ptr = do_trace; 388 | #else 389 | 390 | if (strcmp(opt_phpv, "auto") == 0) { 391 | try(rv, get_php_version(&context.target)); 392 | } 393 | 394 | if (strcmp("70", opt_phpv) == 0) { 395 | do_trace_ptr = do_trace_70; 396 | } else if (strcmp("71", opt_phpv) == 0) { 397 | do_trace_ptr = do_trace_71; 398 | } else if (strcmp("72", opt_phpv) == 0) { 399 | do_trace_ptr = do_trace_72; 400 | } else if (strcmp("73", opt_phpv) == 0) { 401 | do_trace_ptr = do_trace_73; 402 | } else if (strcmp("74", opt_phpv) == 0) { 403 | do_trace_ptr = do_trace_74; 404 | } else if (strcmp("80", opt_phpv) == 0) { 405 | do_trace_ptr = do_trace_80; 406 | } else if (strcmp("81", opt_phpv) == 0) { 407 | do_trace_ptr = do_trace_81; 408 | } else if (strcmp("82", opt_phpv) == 0) { 409 | do_trace_ptr = do_trace_82; 410 | } else if (strcmp("83", opt_phpv) == 0) { 411 | do_trace_ptr = do_trace_83; 412 | } else if (strcmp("84", opt_phpv) == 0) { 413 | do_trace_ptr = do_trace_84; /* TODO verify 8.4 structs */ 414 | } else { 415 | log_error("main_pid: Unrecognized PHP version (%s)\n", opt_phpv); 416 | return PHPSPY_ERR; 417 | } 418 | #endif 419 | 420 | /* calc stop_time */ 421 | stop_time = NULL; 422 | if (in_pgrep_mode) { 423 | /* in pgrep mode, we use a SIGALRM (see main_pgrep) */ 424 | } else if (opt_time_limit_ms > 0) { 425 | stop_time = &_stop_time; 426 | limit_time.tv_sec = opt_time_limit_ms / 1000L; 427 | limit_time.tv_nsec = (opt_time_limit_ms % 1000L) * 1000000L; 428 | clock_get(stop_time); 429 | clock_add(stop_time, &limit_time, stop_time); 430 | } 431 | 432 | while (!done) { 433 | /* record start_time */ 434 | clock_get(&start_time); 435 | 436 | /* trace (maybe with PTRACE_ATTACH) */ 437 | rv = 0; 438 | if (opt_pause) rv |= pause_pid(pid); 439 | rv |= do_trace_ptr(&context); 440 | if (opt_pause) rv |= unpause_pid(pid); 441 | 442 | /* bail if pid died */ 443 | if ((rv & PHPSPY_ERR_PID_DEAD) != 0) break; 444 | 445 | /* maybe apply trace limit */ 446 | if (opt_trace_limit > 0 && rv == PHPSPY_OK) { 447 | if (in_pgrep_mode) { 448 | __atomic_add_fetch(&trace_count, 1, __ATOMIC_SEQ_CST); 449 | } else { 450 | trace_count += 1; 451 | } 452 | /* in pgrep mode, it is possible for each thread to sneak in one 453 | extra trace even after the limit is reached but it should exit 454 | after that. */ 455 | if (trace_count >= opt_trace_limit) break; 456 | } 457 | 458 | /* maybe apply time limit */ 459 | if (stop_time && clock_diff(&start_time, stop_time) >= 1) { 460 | break; 461 | } 462 | 463 | /* calc sleep_time and sleep */ 464 | clock_get(&end_time); 465 | calc_sleep_time(&end_time, &start_time, &sleep_time); 466 | nanosleep(&sleep_time, NULL); 467 | } 468 | 469 | context.event_handler(&context, PHPSPY_TRACE_EVENT_DEINIT); 470 | 471 | /* in pgrep mode, trigger done condition if we went over the trace limit. 472 | it is ok for multiple threads to call this. */ 473 | if (in_pgrep_mode && opt_trace_limit > 0 && trace_count >= opt_trace_limit) { 474 | write_done_pipe(); 475 | } 476 | 477 | /* TODO proper signal handling for non-pgrep modes */ 478 | return PHPSPY_OK; 479 | } 480 | 481 | static int main_fork(int argc, char **argv) { 482 | int rv, status; 483 | pid_t fork_pid; 484 | (void)argc; 485 | fork_pid = fork(); 486 | if (fork_pid == 0) { 487 | redirect_child_stdio(STDOUT_FILENO, opt_path_child_out); 488 | redirect_child_stdio(STDERR_FILENO, opt_path_child_err); 489 | ptrace(PTRACE_TRACEME, 0, NULL, NULL); 490 | execvp(argv[optind], argv + optind); 491 | perror("execvp"); 492 | exit(1); 493 | } else if (fork_pid < 0) { 494 | perror("fork"); 495 | exit(1); 496 | } 497 | waitpid(fork_pid, &status, 0); 498 | if (!WIFSTOPPED(status) || WSTOPSIG(status) != SIGTRAP) { 499 | log_error("main_fork: Expected SIGTRAP from child\n"); 500 | } 501 | ptrace(PTRACE_DETACH, fork_pid, NULL, NULL); 502 | rv = main_pid(fork_pid); 503 | waitpid(fork_pid, NULL, 0); 504 | return rv; 505 | } 506 | 507 | static void cleanup() { 508 | varpeek_entry_t *entry, *entry_tmp; 509 | varpeek_var_t *var, *var_tmp; 510 | glopeek_entry_t *gentry, *gentry_tmp; 511 | 512 | if (opt_filter_re) { 513 | regfree(opt_filter_re); 514 | } 515 | 516 | HASH_ITER(hh, varpeek_map, entry, entry_tmp) { 517 | HASH_ITER(hh, entry->varmap, var, var_tmp) { 518 | HASH_DEL(entry->varmap, var); 519 | free(var); 520 | } 521 | HASH_DEL(varpeek_map, entry); 522 | free(entry); 523 | } 524 | 525 | HASH_ITER(hh, glopeek_map, gentry, gentry_tmp) { 526 | HASH_DEL(glopeek_map, gentry); 527 | free(gentry); 528 | } 529 | } 530 | 531 | static int pause_pid(pid_t pid) { 532 | int rv; 533 | if (ptrace(PTRACE_ATTACH, pid, 0, 0) == -1) { 534 | rv = errno; 535 | perror("ptrace"); 536 | return PHPSPY_ERR + (rv == ESRCH ? PHPSPY_ERR_PID_DEAD : 0); 537 | } 538 | if (waitpid(pid, NULL, 0) < 0) { 539 | perror("waitpid"); 540 | return PHPSPY_ERR; 541 | } 542 | return PHPSPY_OK; 543 | } 544 | 545 | static int unpause_pid(pid_t pid) { 546 | int rv; 547 | if (ptrace(PTRACE_DETACH, pid, 0, 0) == -1) { 548 | rv = errno; 549 | perror("ptrace"); 550 | return PHPSPY_ERR + (rv == ESRCH ? PHPSPY_ERR_PID_DEAD : 0); 551 | } 552 | return PHPSPY_OK; 553 | } 554 | 555 | static void redirect_child_stdio(int proc_fd, char *opt_path) { 556 | char *redir_path; 557 | FILE *redir_file; 558 | if (strcmp(opt_path, "-") == 0) { 559 | return; 560 | } else if (strstr(opt_path, "%d") != NULL) { 561 | if (asprintf(&redir_path, opt_path, getpid()) < 0) { 562 | errno = ENOMEM; 563 | perror("asprintf"); 564 | exit(1); 565 | } 566 | } else { 567 | if ((redir_path = strdup(opt_path)) == NULL) { 568 | perror("strdup"); 569 | exit(1); 570 | } 571 | } 572 | if ((redir_file = fopen(redir_path, "w")) == NULL) { 573 | perror("fopen"); 574 | free(redir_path); 575 | exit(1); 576 | } 577 | dup2(fileno(redir_file), proc_fd); 578 | fclose(redir_file); 579 | free(redir_path); 580 | } 581 | 582 | static int find_addresses(trace_target_t *target) { 583 | int rv; 584 | addr_memo_t memo; 585 | 586 | memset(&memo, 0, sizeof(addr_memo_t)); 587 | 588 | if (opt_executor_globals_addr != 0) { 589 | target->executor_globals_addr = opt_executor_globals_addr; 590 | } else { 591 | try(rv, get_symbol_addr(&memo, target->pid, "executor_globals", &target->executor_globals_addr)); 592 | } 593 | if (opt_sapi_globals_addr != 0) { 594 | target->sapi_globals_addr = opt_sapi_globals_addr; 595 | } else if (opt_capture_req) { 596 | try(rv, get_symbol_addr(&memo, target->pid, "sapi_globals", &target->sapi_globals_addr)); 597 | } 598 | if (opt_capture_mem) { 599 | try(rv, get_symbol_addr(&memo, target->pid, "alloc_globals", &target->alloc_globals_addr)); 600 | } 601 | log_error_enabled = 0; 602 | if (get_symbol_addr(&memo, target->pid, "basic_functions_module", &target->basic_functions_module_addr) != 0) { 603 | target->basic_functions_module_addr = 0; 604 | } 605 | log_error_enabled = 1; 606 | return PHPSPY_OK; 607 | } 608 | 609 | static void clock_get(struct timespec *ts) { 610 | if (clock_gettime(CLOCK_MONOTONIC_RAW, ts) == -1) { 611 | perror("clock_gettime"); 612 | ts->tv_sec = 0; 613 | ts->tv_nsec = 0; 614 | } 615 | } 616 | 617 | static void clock_add(struct timespec *a, struct timespec *b, struct timespec *res) { 618 | res->tv_sec = a->tv_sec + b->tv_sec; 619 | res->tv_nsec = a->tv_nsec + b->tv_nsec; 620 | if (res->tv_nsec >= 1000000000L) { 621 | res->tv_sec += res->tv_nsec / 1000000000L; 622 | res->tv_nsec = res->tv_nsec % 1000000000L; 623 | } 624 | } 625 | 626 | static int clock_diff(struct timespec *a, struct timespec *b) { 627 | if (a->tv_sec == b->tv_sec) { 628 | if (a->tv_nsec == b->tv_nsec) { 629 | return 0; 630 | } 631 | return a->tv_nsec > b->tv_nsec ? 1 : -1; 632 | } 633 | return a->tv_sec > b->tv_sec ? 1 : -1; 634 | } 635 | 636 | static void calc_sleep_time(struct timespec *end, struct timespec *start, struct timespec *sleep) { 637 | long end_ns, start_ns, sleep_ns; 638 | if (end->tv_sec == start->tv_sec) { 639 | sleep_ns = opt_sleep_ns - (end->tv_nsec - start->tv_nsec); 640 | } else { 641 | end_ns = (end->tv_sec * 1000000000UL) + (end->tv_nsec * 1UL); 642 | start_ns = (start->tv_sec * 1000000000UL) + (start->tv_nsec * 1UL); 643 | sleep_ns = opt_sleep_ns - (end_ns - start_ns); 644 | } 645 | if (sleep_ns < 0) { 646 | log_error("calc_sleep_time: Expected sleep_ns>0; decrease sample rate\n"); 647 | sleep_ns = 0; 648 | } 649 | if (sleep_ns < 1000000000L) { 650 | sleep->tv_sec = 0; 651 | sleep->tv_nsec = (long)sleep_ns; 652 | } else { 653 | sleep->tv_sec = (long)sleep_ns / 1000000000L; 654 | sleep->tv_nsec = (long)sleep_ns - (sleep->tv_sec * 1000000000L); 655 | } 656 | } 657 | 658 | static void varpeek_add(char *varspec) { 659 | char *at_sign, *colon, *dash; 660 | uint32_t line_start, line_end, lineno; 661 | varpeek_entry_t *varpeek; 662 | varpeek_var_t *var; 663 | char varpeek_key[PHPSPY_STR_SIZE]; 664 | /* varspec: var@/path/file.php:line */ 665 | /* -or- var@/path/file.php:start-end */ 666 | at_sign = strchr(varspec, '@'); 667 | colon = strrchr(varspec, ':'); 668 | dash = strrchr(varspec, '-'); 669 | if (!at_sign || !colon) { 670 | log_error("varpeek_add: Malformed varspec: %s\n\n", varspec); 671 | usage(stderr, 1); 672 | } 673 | line_start = strtoul(colon+1, NULL, 10); 674 | line_end = dash ? strtoul(dash+1, NULL, 10) : line_start; 675 | for (lineno = line_start; lineno <= line_end; lineno++) { 676 | snprintf(varpeek_key, sizeof(varpeek_key), "%.*s:%d", (int)(colon-at_sign-1), at_sign+1, lineno); 677 | HASH_FIND_STR(varpeek_map, varpeek_key, varpeek); 678 | if (!varpeek) { 679 | varpeek = calloc(1, sizeof(varpeek_entry_t)); 680 | strncpy(varpeek->filename_lineno, varpeek_key, sizeof(varpeek->filename_lineno)); 681 | HASH_ADD_STR(varpeek_map, filename_lineno, varpeek); 682 | } 683 | var = calloc(1, sizeof(varpeek_var_t)); 684 | snprintf(var->name, sizeof(var->name), "%.*s", (int)(at_sign-varspec), varspec); 685 | HASH_ADD_STR(varpeek->varmap, name, var); 686 | } 687 | } 688 | 689 | static void glopeek_add(char *glospec) { 690 | char *dot; 691 | char *gloname; 692 | glopeek_entry_t *gentry; 693 | dot = strchr(glospec, '.'); 694 | if (!dot) { 695 | log_error("glopeek_add: Malformed glospec: %s\n\n", glospec); 696 | usage(stderr, 1); 697 | } 698 | HASH_FIND_STR(glopeek_map, glospec, gentry); 699 | if (gentry) return; 700 | if (strncmp("post.", glospec, dot-glospec) == 0) { 701 | gloname = "_POST"; 702 | } else if (strncmp("get.", glospec, dot-glospec) == 0) { 703 | gloname = "_GET"; 704 | } else if (strncmp("cookie.", glospec, dot-glospec) == 0) { 705 | gloname = "_COOKIE"; 706 | } else if (strncmp("server.", glospec, dot-glospec) == 0) { 707 | gloname = "_SERVER"; 708 | } else if (strncmp("files.", glospec, dot-glospec) == 0) { 709 | gloname = "_FILES"; 710 | } else if (strncmp("globals.", glospec, dot-glospec) == 0) { 711 | gloname = ""; /* $GLOBAL variables are stored directly in the symbol table */ 712 | } else { 713 | log_error("glopeek_add: Invalid global: %s\n\n", glospec); 714 | usage(stderr, 1); 715 | return; 716 | } 717 | gentry = calloc(1, sizeof(glopeek_entry_t)); 718 | snprintf(gentry->key, sizeof(gentry->key), "%s", glospec); 719 | snprintf(gentry->gloname, sizeof(gentry->gloname), "%s", gloname); 720 | snprintf(gentry->varname, sizeof(gentry->varname), "%s", dot+1); 721 | HASH_ADD_STR(glopeek_map, key, gentry); 722 | } 723 | 724 | static int copy_proc_mem(pid_t pid, const char *what, void *raddr, void *laddr, size_t size) { 725 | struct iovec local[1]; 726 | struct iovec remote[1]; 727 | 728 | if (raddr == NULL) { 729 | log_error("copy_proc_mem: Not copying %s; raddr is NULL\n", what); 730 | return PHPSPY_ERR; 731 | } 732 | 733 | local[0].iov_base = laddr; 734 | local[0].iov_len = size; 735 | remote[0].iov_base = raddr; 736 | remote[0].iov_len = size; 737 | 738 | if (process_vm_readv(pid, local, 1, remote, 1, 0) == -1) { 739 | if (errno == ESRCH) { /* No such process */ 740 | perror("process_vm_readv"); 741 | return PHPSPY_ERR | PHPSPY_ERR_PID_DEAD; 742 | } 743 | log_error("copy_proc_mem: Failed to copy %s; err=%s raddr=%p size=%lu\n", what, strerror(errno), raddr, size); 744 | return PHPSPY_ERR; 745 | } 746 | 747 | return PHPSPY_OK; 748 | } 749 | 750 | #ifndef USE_ZEND 751 | static int get_php_version(trace_target_t *target) { 752 | struct _zend_module_entry basic_functions_module; 753 | char version_cmd[1024]; 754 | char phpv[4]; 755 | pid_t pid; 756 | FILE *pcmd; 757 | 758 | pid = target->pid; 759 | phpv[0] = '\0'; 760 | 761 | /* Try reading basic_functions_module */ 762 | if (target->basic_functions_module_addr) { 763 | if (copy_proc_mem(pid, "basic_functions_module", (void*)target->basic_functions_module_addr, &basic_functions_module, sizeof(basic_functions_module)) == 0) { 764 | copy_proc_mem(pid, "basic_functions_module.version", (void*)basic_functions_module.version, phpv, 3); 765 | } 766 | } 767 | 768 | /* Try greping binary */ 769 | if (phpv[0] == '\0') { 770 | char libname[PHPSPY_STR_SIZE]; 771 | if (shell_escape(opt_libname_awk_patt, libname, sizeof(libname), "opt_libname_awk_patt")) { 772 | return PHPSPY_ERR; 773 | } 774 | int n = snprintf( 775 | version_cmd, 776 | sizeof(version_cmd), 777 | "{ echo -n /proc/%d/root/; " 778 | " awk -ve=1 -vp=%s '$0~p{print $NF; e=0; exit} END{exit e}' /proc/%d/maps " 779 | " || readlink /proc/%d/exe; } " 780 | "| { xargs stat --printf=%%n 2>/dev/null || echo /proc/%d/exe; } " 781 | "| xargs strings " 782 | "| grep -Eo 'X-Powered-By: PHP/[0-9]+\\.[0-9]+' " 783 | "| grep -Eo '[0-9]+\\.[0-9]+' ", 784 | pid, libname, pid, pid, pid 785 | ); 786 | if ((size_t)n >= sizeof(version_cmd) - 1) { 787 | log_error("get_php_version: snprintf overflow\n"); 788 | return PHPSPY_ERR; 789 | } 790 | 791 | if ((pcmd = popen(version_cmd, "r")) == NULL) { 792 | perror("get_php_version: popen"); 793 | return PHPSPY_ERR; 794 | } else if (fread(&phpv, sizeof(char), 3, pcmd) != 3) { 795 | log_error("get_php_version: Could not detect PHP version\n"); 796 | pclose(pcmd); 797 | return PHPSPY_ERR; 798 | } 799 | pclose(pcmd); 800 | } 801 | 802 | if (strncmp(phpv, "7.0", 3) == 0) opt_phpv = "70"; 803 | else if (strncmp(phpv, "7.1", 3) == 0) opt_phpv = "71"; 804 | else if (strncmp(phpv, "7.2", 3) == 0) opt_phpv = "72"; 805 | else if (strncmp(phpv, "7.3", 3) == 0) opt_phpv = "73"; 806 | else if (strncmp(phpv, "7.4", 3) == 0) opt_phpv = "74"; 807 | else if (strncmp(phpv, "8.0", 3) == 0) opt_phpv = "80"; 808 | else if (strncmp(phpv, "8.1", 3) == 0) opt_phpv = "81"; 809 | else if (strncmp(phpv, "8.2", 3) == 0) opt_phpv = "82"; 810 | else if (strncmp(phpv, "8.3", 3) == 0) opt_phpv = "83"; 811 | else if (strncmp(phpv, "8.4", 3) == 0) opt_phpv = "84"; 812 | else { 813 | log_error("get_php_version: Unrecognized PHP version (%s)\n", phpv); 814 | return PHPSPY_ERR; 815 | } 816 | 817 | return PHPSPY_OK; 818 | } 819 | #endif 820 | 821 | uint64_t phpspy_zend_inline_hash_func(const char *str, size_t len) { 822 | /* Adapted from zend_string.h */ 823 | uint64_t hash; 824 | hash = 5381UL; 825 | 826 | for (; len >= 8; len -= 8) { 827 | hash = ((hash << 5) + hash) + *str++; 828 | hash = ((hash << 5) + hash) + *str++; 829 | hash = ((hash << 5) + hash) + *str++; 830 | hash = ((hash << 5) + hash) + *str++; 831 | hash = ((hash << 5) + hash) + *str++; 832 | hash = ((hash << 5) + hash) + *str++; 833 | hash = ((hash << 5) + hash) + *str++; 834 | hash = ((hash << 5) + hash) + *str++; 835 | } 836 | switch (len) { 837 | case 7: hash = ((hash << 5) + hash) + *str++; /* fallthrough... */ 838 | case 6: hash = ((hash << 5) + hash) + *str++; /* fallthrough... */ 839 | case 5: hash = ((hash << 5) + hash) + *str++; /* fallthrough... */ 840 | case 4: hash = ((hash << 5) + hash) + *str++; /* fallthrough... */ 841 | case 3: hash = ((hash << 5) + hash) + *str++; /* fallthrough... */ 842 | case 2: hash = ((hash << 5) + hash) + *str++; /* fallthrough... */ 843 | case 1: hash = ((hash << 5) + hash) + *str++; break; 844 | case 0: break; 845 | } 846 | 847 | return hash | 0x8000000000000000UL; 848 | } 849 | 850 | void log_error(const char *fmt, ...) { 851 | va_list args; 852 | if (log_error_enabled) { 853 | va_start(args, fmt); 854 | vfprintf(stderr, fmt, args); 855 | } 856 | } 857 | 858 | /* TODO figure out a way to make this cleaner */ 859 | #ifdef USE_ZEND 860 | #include "phpspy_trace.c" 861 | #else 862 | #define phpv 70 863 | #include "phpspy_trace_tpl.c" 864 | #undef phpv 865 | #define phpv 71 866 | #include "phpspy_trace_tpl.c" 867 | #undef phpv 868 | #define phpv 72 869 | #include "phpspy_trace_tpl.c" 870 | #undef phpv 871 | #define phpv 73 872 | #include "phpspy_trace_tpl.c" 873 | #undef phpv 874 | #define phpv 74 875 | #include "phpspy_trace_tpl.c" 876 | #undef phpv 877 | #define phpv 80 878 | #include "phpspy_trace_tpl.c" 879 | #undef phpv 880 | #define phpv 81 881 | #include "phpspy_trace_tpl.c" 882 | #undef phpv 883 | #define phpv 82 884 | #include "phpspy_trace_tpl.c" 885 | #undef phpv 886 | #define phpv 83 887 | #include "phpspy_trace_tpl.c" 888 | #undef phpv 889 | #define phpv 84 890 | #include "phpspy_trace_tpl.c" 891 | #undef phpv 892 | #endif 893 | --------------------------------------------------------------------------------