Featured image of post PHP Source Code Reading

PHP Source Code Reading

In-depth Understanding of PHP

Getting Started (Environment Setup)

Create a project directory and add Dockerfile

FROM centos:7

# Install dependencies
RUN yum -y install gcc gcc-c++ gdb autoconf libjpeg libjpeg-devel libpng libpng-devel freetype freetype-devel libxml2 libxml2-devel zlib zlib-devel glibc glibc-devel glib2 glib2-devel bzip2 bzip2-devel ncurses ncurses-devel curl curl-devel e2fsprogs e2fsprogs-devel krb5 krb5-devel libidn libidn-devel openssl openssl-devel openldap openldap-devel nss_ldap openldap-clients openldap-servers gd gd2 gd-devel gd2-devel perl-CPAN pcre-devel libicu-devel wget

# Download specific PHP version
RUN wget -O /tmp/php.tar.gz https://www.php.net/distributions/php-7.1.0.tar.gz
RUN mkdir ~/php71 && tar -xvf /tmp/php.tar.gz --strip-components 1  -C ~/php71

# Installation directory: /var/php71
# Source directory: /var/www
###################################################################
# 1. Generate Makefile
# 2. Compile
# 3. Install
RUN cd ~/php71 && \
    ./configure --prefix=/var/php71 --enable-fpm --enable-debug --enable-phpdbg-debug CFLAGS="-g3 -gdwarf-4" && \
    make && \
    make install

# Configure PHP & FPM
RUN cp ~/php71/php.ini-production /var/php71/lib/php.ini  && \
    cp /var/php71/etc/php-fpm.conf.default /var/php71/etc/php-fpm.conf  && \
    echo $'export PATH=$PATH:/var/php71/bin:/var/php71/sbin' >> ~/.bashrc

# Install Nginx for FPM debugging
RUN yum -y install epel-release && \
    yum -y install nginx

# Nginx FPM configuration
RUN echo $'server {\n\
               listen       9999;\n\
               root         /var/www;\n\
               location / {\n\
                   root   /var/www;\n\
                   index  index.php index.html index.htm;\n\
               }\n\
               error_page   500 502 503 504  /50x.html;\n\
               location ~ \.php$ {\n\
                   fastcgi_pass   127.0.0.1:9000;\n\
                   fastcgi_index  index.php;\n\
                   fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;\n\
                   include        fastcgi_params;\n\
               }\n\
}' > /etc/nginx/conf.d/fpm.conf

# FPM worker configuration
RUN echo $'[www]\n\
           user = nobody\n\
           group = nobody\n\
           listen = 127.0.0.1:9000\n\
           pm = static\n\
           pm.max_children = 1' > var/php71/etc/php-fpm.d/www.conf

Create docker-compose.yml in project directory

version: '3'
services:
  centos:
    build: ./
    tty: true
    cap_add:
      - SYS_PTRACE
    working_dir: /var/www
    volumes:
      - ./:/var/www
    ports:
      - 9999:9999

Build and start container

  • docker-compose up -d && docker-compose exec centos bash

Debugging with GDB

docker-compose exec centos bash
# Launch TUI mode
gdb --tui
# Debug executable
gdb php
# Attach to process
gdb --pid=xxx
Common Commands Description
run Restart program
start Step-by-step execution from start
list View source code (l)
set Set variable values
next Step over (n)
step Step into (s)
break Set breakpoint (b)
delete Delete breakpoint
finish Exit current function
continue Resume execution (c)
print Print values (p)
quit Exit GDB (q)
info View local variables (i)

Debugging PHP-FPM

  • Configured with single worker process for easier tracing
docker-compose exec centos bash
php-fpm
nginx

# Find worker process ID
ps aux | grep fpm
gdb --pid=xxx
  • Use Understand for code navigation
  • Download matching PHP version source code for analysis

Adding Extensions (Optional)

  • Requirements:
    • PHP source matching installed version
  • Steps (e.g., curl extension):
    cd ~/php71/ext/curl
    /var/php71/bin/phpize
    ./configure --with-php-config=/var/php71/bin/php-config
    make && make install
    

Byte Alignment Example

struct A 
{
    int a;        // 4 bytes
    char b;       // 1 byte
    short c;      // 2 bytes (aligned to 2n)
    char d;       // 1 byte
};
/* Memory layout: aaaa b0cc d000 (Total 12 bytes) */

Endianness

  • Big-Endian: 0x1A2B3C4D0x1A | 0x2B | 0x3C | 0x4D
  • Little-Endian: 0x1A2B3C4D0x4D | 0x3C | 0x2B | 0x1A

Variable Storage (ZVAL)

typedef union _zend_value {
	zend_long         lval;        // Integer
	double            dval;        // Double
	zend_refcounted  *counted;     // Reference counted types
	zend_string      *str;         // String
	zend_array       *arr;         // Array
	// ... other types
} zend_value;

struct _zval_struct {
	zend_value        value;       // Actual value
	union {
		struct {
			ZEND_ENDIAN_LOHI_4(
				zend_uchar    type,         // Variable type
				zend_uchar    type_flags,
				zend_uchar    const_flags,
				zend_uchar    reserved)
		} v;
		uint32_t type_info;
	} u1;
	// ... other fields
};

PHP Execution Flow

  1. Lexical Analysis: Convert source to tokens
  2. Parsing: Generate Abstract Syntax Tree (AST)
  3. Compilation: Produce OPcodes
  4. Zend VM: Execute OPcodes as machine code

Lifecycle

CLI Mode

php_module_startup → php_request_startup → php_execute_script → php_request_shutdown → php_module_shutdown

FPM Mode

         ┌─────────────────────┐
         │  fcgi_accept_request│
         └─────────────────────┘
                 ▲
php_module_startup│
                 │
          php_request_startup
                 │
          php_execute_script
                 │
          php_request_shutdown
                 │
          php_module_shutdown

FAQ

  • Struct Hack: char[1] in zend_string enables single memory allocation
  • Typedef Conventions:
    • Global types defined in headers
    • File-local types declared with structs
  • FPM Process Management:
    • Master process manages workers
    • kill -9 on master leaves workers running