diff --git a/.github/workflows/dump_structs.yml b/.github/workflows/dump_structs.yml new file mode 100644 index 0000000..d91ab78 --- /dev/null +++ b/.github/workflows/dump_structs.yml @@ -0,0 +1,63 @@ +name: dump_structs + +# One-off, manually-triggered probe: builds php-src from source for every PHP +# version phpspy ships struct mirrors for, on both x86_64 and aarch64, and +# uploads the raw struct_dump.gdb output as build artifacts. This is how real +# (not guessed) aarch64 offsets get produced -- see struct_dump.sh/.gdb and +# CLAUDE.md. Not run on push/pull_request: 12 versions x 2 arches, each a full +# ./buildconf && ./configure && make, is far too slow for routine CI. + +on: + workflow_dispatch: + +jobs: + dump: + strategy: + fail-fast: false + matrix: + runs_on: [ubuntu-24.04, ubuntu-24.04-arm] + phpv: + - php-7.0.33 + - php-7.1.33 + - php-7.2.34 + - php-7.3.33 + - php-7.4.33 + - php-8.0.30 + - php-8.1.28 + - php-8.2.18 + - php-8.3.6 + - php-8.4.25 + - php-8.5.10 + - master + + runs-on: ${{ matrix.runs_on }} + + steps: + - name: checkout phpspy + uses: actions/checkout@v4 + with: + path: phpspy + + - name: checkout php-src + uses: actions/checkout@v4 + with: + repository: php/php-src + path: php-src + fetch-depth: 0 + + - name: deps + run: | + sudo apt-get update + sudo apt-get install -y \ + autoconf bison re2c pkg-config build-essential gdb \ + libxml2-dev libsqlite3-dev libssl-dev + + - name: dump + run: ./phpspy/struct_dump.sh "$GITHUB_WORKSPACE/php-src" "${{ matrix.phpv }}" + + - name: upload + uses: actions/upload-artifact@v4 + with: + name: struct_dump-${{ matrix.runs_on }}-${{ matrix.phpv }} + path: phpspy/struct_dump.${{ matrix.phpv }}.out + if-no-files-found: warn diff --git a/phpspy_trace.c b/phpspy_trace.c index 2711d22..40285ba 100644 --- a/phpspy_trace.c +++ b/phpspy_trace.c @@ -340,8 +340,11 @@ static int trace_locals(trace_context *context, zend_op *zop, zend_execute_data HASH_FIND(hh, entry->varmap, tmp, tmp_len, var); if (!var) continue; num_vars_found += 1; - /* See ZEND_CALL_VAR_NUM macro in php-src */ - try_copy_proc_mem("zval", ((zval*)(remote_execute_data)) + ((int)(5 + i)), &zv, sizeof(zv)); + /* See ZEND_CALL_VAR_NUM macro in php-src. The frame-slot count is + version-dependent (PHP 7.0 differs from every later version); see + phpspy_frame_slot, defined per-phpv in phpspy_trace_tpl.c and for + USE_ZEND in structs/structs.h. */ + try_copy_proc_mem("zval", ((zval*)(remote_execute_data)) + ((int)(phpspy_frame_slot + i)), &zv, sizeof(zv)); try(rv, sprint_zval(context, &zv, tmp, sizeof(tmp), &tmp_len)); context->event.varpeek.entry = entry; context->event.varpeek.var = var; diff --git a/phpspy_trace_tpl.c b/phpspy_trace_tpl.c index f1ff746..e6fd374 100644 --- a/phpspy_trace_tpl.c +++ b/phpspy_trace_tpl.c @@ -39,6 +39,17 @@ #define sprint_pdo_binds concat2(sprint_pdo_binds_, phpv) #define sprint_pdo_bind concat2(sprint_pdo_bind_, phpv) +/* ZEND_CALL_FRAME_SLOT = ceil(sizeof(zend_execute_data) / sizeof(zval)). Measured + directly against every supported PHP version's real headers: 6 on 7.0 (which + still carries execute_data.called_scope, removed in 7.1), 5 on every later + version (sizeof(zend_execute_data) is 72 or 80 there, both of which round up + to 5 slots of 16 bytes). */ +#if phpv == 70 +#define phpspy_frame_slot 6 +#else +#define phpspy_frame_slot 5 +#endif + #include "phpspy_trace.c" #undef concat1 @@ -73,6 +84,7 @@ #undef trace_pdo #undef sprint_pdo_binds #undef sprint_pdo_bind +#undef phpspy_frame_slot #undef copy_executor_globals #undef copy_zarray_bucket #undef sprint_zstring diff --git a/struct_dump.gdb b/struct_dump.gdb index 10072fc..0ec8999 100644 --- a/struct_dump.gdb +++ b/struct_dump.gdb @@ -95,6 +95,13 @@ whatis zval fieldof zval u2.next printf "\n" +# Cross-check for phpspy_frame_slot (phpspy_trace_tpl.c / structs/structs.h): +# ZEND_CALL_FRAME_SLOT = ceil(sizeof(zend_execute_data) / sizeof(zval)). +printf "frame_slot\n" +printf " sizeof(zend_execute_data) %lu\n", sizeof(zend_execute_data) +printf " sizeof(zval) %lu\n", sizeof(zval) +printf "\n" + printf "Bucket\n" whatis Bucket fieldof Bucket val diff --git a/struct_dump.sh b/struct_dump.sh index 5ce2b61..a66afc8 100755 --- a/struct_dump.sh +++ b/struct_dump.sh @@ -1,28 +1,44 @@ #!/bin/bash this_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd) phpsrc_dir=$1 +only_phpv=$2 if [ -z "$phpsrc_dir" ]; then echo "Required: php-src directory" exit 1 fi +# All PHP versions phpspy ships struct mirrors for. 8.6 has no release tag yet +# (still dev), so it stays pinned to `master`, same as when the 8.6 structs +# were first cut. +all_phpvs=( + php-7.0.33 + php-7.1.33 + php-7.2.34 + php-7.3.33 + php-7.4.33 + php-8.0.30 + php-8.1.28 + php-8.2.18 + php-8.3.6 + php-8.4.25 + php-8.5.10 + master +) + +if [ -n "$only_phpv" ]; then + phpvs=("$only_phpv") +else + phpvs=("${all_phpvs[@]}") +fi + pushd "$phpsrc_dir" || exit 1 git fetch --tags -for phpv in php-7.0.33 \ - php-7.1.33 \ - php-7.2.34 \ - php-7.3.33 \ - php-7.4.33 \ - php-8.0.30 \ - php-8.1.28 \ - php-8.2.18 \ - php-8.3.6 \ - master +for phpv in "${phpvs[@]}" do git reset --hard HEAD \ && git clean -fdx \ - && git checkout $phpv \ + && git checkout "$phpv" \ && git clean -fdx \ && ./buildconf --force \ && ./configure \ diff --git a/structs/aarch64/php_structs_70.h b/structs/aarch64/php_structs_70.h index dafdeee..11fb6b3 100644 --- a/structs/aarch64/php_structs_70.h +++ b/structs/aarch64/php_structs_70.h @@ -1,8 +1,6 @@ #ifndef __php_structs_70_h #define __php_structs_70_h -/* TODO: These structs are wrong for aarch64 */ - #include typedef struct _zend_executor_globals_70 zend_executor_globals_70; @@ -122,8 +120,8 @@ struct __attribute__((__packed__)) _sapi_request_info_70 { struct __attribute__((__packed__)) _sapi_globals_struct_70 { uint8_t pad0[8]; /* 0 +8 */ sapi_request_info_70 request_info; /* 8 +48 */ - uint8_t pad1[384]; /* 56 +384 */ - double global_request_time; /* 440 +8 */ + uint8_t pad1[368]; /* 56 +368 */ + double global_request_time; /* 424 +8 */ }; struct __attribute__((__packed__)) _Bucket_70 { @@ -142,7 +140,6 @@ struct __attribute__((__packed__)) _zend_mm_heap_70 { size_t peak; /* 24 +8 */ }; - struct __attribute__((__packed__)) _zend_object_70 { uint8_t pad0[16]; /* 0 +16 */ zend_class_entry_70 *ce; /* 16 +8 */ diff --git a/structs/aarch64/php_structs_71.h b/structs/aarch64/php_structs_71.h index f7b8b28..6bbe8f1 100644 --- a/structs/aarch64/php_structs_71.h +++ b/structs/aarch64/php_structs_71.h @@ -1,8 +1,6 @@ #ifndef __php_structs_71_h #define __php_structs_71_h -/* TODO: These structs are wrong for aarch64 */ - #include typedef struct _zend_executor_globals_71 zend_executor_globals_71; @@ -121,8 +119,8 @@ struct __attribute__((__packed__)) _sapi_request_info_71 { struct __attribute__((__packed__)) _sapi_globals_struct_71 { uint8_t pad0[8]; /* 0 +8 */ sapi_request_info_71 request_info; /* 8 +48 */ - uint8_t pad1[384]; /* 56 +384 */ - double global_request_time; /* 440 +8 */ + uint8_t pad1[368]; /* 56 +368 */ + double global_request_time; /* 424 +8 */ }; struct __attribute__((__packed__)) _Bucket_71 { @@ -141,7 +139,6 @@ struct __attribute__((__packed__)) _zend_mm_heap_71 { size_t peak; /* 24 +8 */ }; - struct __attribute__((__packed__)) _zend_object_71 { uint8_t pad0[16]; /* 0 +16 */ zend_class_entry_71 *ce; /* 16 +8 */ diff --git a/structs/aarch64/php_structs_72.h b/structs/aarch64/php_structs_72.h index 3e7729b..0670045 100644 --- a/structs/aarch64/php_structs_72.h +++ b/structs/aarch64/php_structs_72.h @@ -1,8 +1,6 @@ #ifndef __php_structs_72_h #define __php_structs_72_h -/* TODO: These structs are wrong for aarch64 */ - #include typedef struct _zend_executor_globals_72 zend_executor_globals_72; @@ -121,8 +119,8 @@ struct __attribute__((__packed__)) _sapi_request_info_72 { struct __attribute__((__packed__)) _sapi_globals_struct_72 { uint8_t pad0[8]; /* 0 +8 */ sapi_request_info_72 request_info; /* 8 +48 */ - uint8_t pad1[384]; /* 56 +384 */ - double global_request_time; /* 440 +8 */ + uint8_t pad1[368]; /* 56 +368 */ + double global_request_time; /* 424 +8 */ }; struct __attribute__((__packed__)) _Bucket_72 { @@ -141,7 +139,6 @@ struct __attribute__((__packed__)) _zend_mm_heap_72 { size_t peak; /* 24 +8 */ }; - struct __attribute__((__packed__)) _zend_object_72 { uint8_t pad0[16]; /* 0 +16 */ zend_class_entry_72 *ce; /* 16 +8 */ diff --git a/structs/aarch64/php_structs_73.h b/structs/aarch64/php_structs_73.h index 2a02a7c..3cdde01 100644 --- a/structs/aarch64/php_structs_73.h +++ b/structs/aarch64/php_structs_73.h @@ -1,8 +1,6 @@ #ifndef __php_structs_73_h #define __php_structs_73_h -/* TODO: These structs are wrong for aarch64 */ - #include typedef struct _zend_executor_globals_73 zend_executor_globals_73; @@ -121,8 +119,8 @@ struct __attribute__((__packed__)) _sapi_request_info_73 { struct __attribute__((__packed__)) _sapi_globals_struct_73 { uint8_t pad0[8]; /* 0 +8 */ sapi_request_info_73 request_info; /* 8 +48 */ - uint8_t pad1[384]; /* 56 +384 */ - double global_request_time; /* 440 +8 */ + uint8_t pad1[368]; /* 56 +368 */ + double global_request_time; /* 424 +8 */ }; struct __attribute__((__packed__)) _Bucket_73 { @@ -141,7 +139,6 @@ struct __attribute__((__packed__)) _zend_mm_heap_73 { size_t peak; /* 24 +8 */ }; - struct __attribute__((__packed__)) _zend_object_73 { uint8_t pad0[16]; /* 0 +16 */ zend_class_entry_73 *ce; /* 16 +8 */ diff --git a/structs/aarch64/php_structs_74.h b/structs/aarch64/php_structs_74.h index c448737..7e526c6 100644 --- a/structs/aarch64/php_structs_74.h +++ b/structs/aarch64/php_structs_74.h @@ -1,8 +1,6 @@ #ifndef __php_structs_74_h #define __php_structs_74_h -/* TODO: These structs are wrong for aarch64 */ - #include typedef struct _zend_executor_globals_74 zend_executor_globals_74; @@ -121,8 +119,8 @@ struct __attribute__((__packed__)) _sapi_request_info_74 { struct __attribute__((__packed__)) _sapi_globals_struct_74 { uint8_t pad0[8]; /* 0 +8 */ sapi_request_info_74 request_info; /* 8 +48 */ - uint8_t pad1[384]; /* 56 +384 */ - double global_request_time; /* 440 +8 */ + uint8_t pad1[368]; /* 56 +368 */ + double global_request_time; /* 424 +8 */ }; struct __attribute__((__packed__)) _Bucket_74 { @@ -141,7 +139,6 @@ struct __attribute__((__packed__)) _zend_mm_heap_74 { size_t peak; /* 24 +8 */ }; - struct __attribute__((__packed__)) _zend_object_74 { uint8_t pad0[16]; /* 0 +16 */ zend_class_entry_74 *ce; /* 16 +8 */ diff --git a/structs/aarch64/php_structs_80.h b/structs/aarch64/php_structs_80.h index 20d19da..ddb40b1 100644 --- a/structs/aarch64/php_structs_80.h +++ b/structs/aarch64/php_structs_80.h @@ -1,8 +1,6 @@ #ifndef __php_structs_80_h #define __php_structs_80_h -/* TODO: These structs are wrong for aarch64 */ - #include typedef struct _zend_executor_globals_80 zend_executor_globals_80; @@ -121,8 +119,8 @@ struct __attribute__((__packed__)) _sapi_request_info_80 { struct __attribute__((__packed__)) _sapi_globals_struct_80 { uint8_t pad0[8]; /* 0 +8 */ sapi_request_info_80 request_info; /* 8 +48 */ - uint8_t pad1[384]; /* 56 +384 */ - double global_request_time; /* 440 +8 */ + uint8_t pad1[368]; /* 56 +368 */ + double global_request_time; /* 424 +8 */ }; struct __attribute__((__packed__)) _Bucket_80 { @@ -141,7 +139,6 @@ struct __attribute__((__packed__)) _zend_mm_heap_80 { size_t peak; /* 24 +8 */ }; - struct __attribute__((__packed__)) _zend_object_80 { uint8_t pad0[16]; /* 0 +16 */ zend_class_entry_80 *ce; /* 16 +8 */ diff --git a/structs/aarch64/php_structs_81.h b/structs/aarch64/php_structs_81.h index 6953dc4..484e81d 100644 --- a/structs/aarch64/php_structs_81.h +++ b/structs/aarch64/php_structs_81.h @@ -1,8 +1,6 @@ #ifndef __php_structs_81_h #define __php_structs_81_h -/* TODO: These structs are wrong for aarch64 */ - #include typedef struct _zend_executor_globals_81 zend_executor_globals_81; @@ -121,8 +119,8 @@ struct __attribute__((__packed__)) _sapi_request_info_81 { struct __attribute__((__packed__)) _sapi_globals_struct_81 { uint8_t pad0[8]; /* 0 +8 */ sapi_request_info_81 request_info; /* 8 +48 */ - uint8_t pad1[384]; /* 56 +384 */ - double global_request_time; /* 440 +8 */ + uint8_t pad1[368]; /* 56 +368 */ + double global_request_time; /* 424 +8 */ }; struct __attribute__((__packed__)) _Bucket_81 { @@ -141,7 +139,6 @@ struct __attribute__((__packed__)) _zend_mm_heap_81 { size_t peak; /* 24 +8 */ }; - struct __attribute__((__packed__)) _zend_object_81 { uint8_t pad0[16]; /* 0 +16 */ zend_class_entry_81 *ce; /* 16 +8 */ diff --git a/structs/aarch64/php_structs_82.h b/structs/aarch64/php_structs_82.h index dd7f0c5..1db3e48 100644 --- a/structs/aarch64/php_structs_82.h +++ b/structs/aarch64/php_structs_82.h @@ -1,8 +1,6 @@ #ifndef __php_structs_82_h #define __php_structs_82_h -/* TODO: These structs are wrong for aarch64 */ - #include typedef struct _zend_executor_globals_82 zend_executor_globals_82; @@ -121,8 +119,8 @@ struct __attribute__((__packed__)) _sapi_request_info_82 { struct __attribute__((__packed__)) _sapi_globals_struct_82 { uint8_t pad0[8]; /* 0 +8 */ sapi_request_info_82 request_info; /* 8 +48 */ - uint8_t pad1[384]; /* 56 +384 */ - double global_request_time; /* 440 +8 */ + uint8_t pad1[368]; /* 56 +368 */ + double global_request_time; /* 424 +8 */ }; struct __attribute__((__packed__)) _Bucket_82 { @@ -141,7 +139,6 @@ struct __attribute__((__packed__)) _zend_mm_heap_82 { size_t peak; /* 24 +8 */ }; - struct __attribute__((__packed__)) _zend_object_82 { uint8_t pad0[16]; /* 0 +16 */ zend_class_entry_82 *ce; /* 16 +8 */ diff --git a/structs/aarch64/php_structs_86.h b/structs/aarch64/php_structs_86.h index 3db5b59..2961e9e 100644 --- a/structs/aarch64/php_structs_86.h +++ b/structs/aarch64/php_structs_86.h @@ -119,8 +119,8 @@ struct __attribute__((__packed__)) _sapi_request_info_86 { struct __attribute__((__packed__)) _sapi_globals_struct_86 { uint8_t pad0[8]; /* 0 +8 */ sapi_request_info_86 request_info; /* 8 +48 */ - uint8_t pad1[368]; /* 56 +368 */ - double global_request_time; /* 424 +8 */ + uint8_t pad1[360]; /* 56 +360 */ + double global_request_time; /* 416 +8 */ }; struct __attribute__((__packed__)) _Bucket_86 { diff --git a/structs/structs.h b/structs/structs.h index b6daf99..3dba6f3 100644 --- a/structs/structs.h +++ b/structs/structs.h @@ -6,6 +6,9 @@ # undef snprintf # undef vsnprintf # undef HASH_ADD + /* ZEND_CALL_FRAME_SLOT (Zend/zend_compile.h) is already visible here via + main/SAPI.h's own includes; no extra #include is needed. */ +# define phpspy_frame_slot ZEND_CALL_FRAME_SLOT #else # if defined(__x86_64__) # include diff --git a/structs/x86_64/php_structs_86.h b/structs/x86_64/php_structs_86.h index bf1cf7f..723fc94 100644 --- a/structs/x86_64/php_structs_86.h +++ b/structs/x86_64/php_structs_86.h @@ -119,8 +119,8 @@ struct __attribute__((__packed__)) _sapi_request_info_86 { struct __attribute__((__packed__)) _sapi_globals_struct_86 { uint8_t pad0[8]; /* 0 +8 */ sapi_request_info_86 request_info; /* 8 +48 */ - uint8_t pad1[384]; /* 56 +384 */ - double global_request_time; /* 440 +8 */ + uint8_t pad1[376]; /* 56 +376 */ + double global_request_time; /* 432 +8 */ }; struct __attribute__((__packed__)) _Bucket_86 {