Başlangıç
PHP ve Laravel ile yıllardır çalışıyorum. Her PHP geliştiricisi gibi, o klasik döngüden geçtim: Xdebug’ı kurmak, xdebug.mode=debug ayarını yapmak, PHPStorm’da yolları eşlemek, Docker’ın iç ağ IP’sinin değişmeyeceğini umarak dua etmek ve sonunda çalıştığında… çok yavaş. Her istek saniyeler alıyor.
Ancak gerçek sorun asla performans değildi. Sorun kurulumdu. Docker’da Laravel Sail üzerinden çalışan projelerle, başka projeler WSL’de, bazıları ise staging’e SSH ile bağlanıyordu. Her ortam, farklı bir Xdebug yapılandırması gerektiriyordu. Ağ değişti mi? Yeniden yap. Yeni bir konteyner başlattın mı? Yolları yeniden eşle. Farklı bir makineye geçtin mi? Sıfırdan başla.
Dönüm noktası bir Pazar günüydü. Bir Laravel projesinde üretim hatasıyla hızlıca ilgilenmem gerekiyordu ve Xdebug’ın Docker üzerinden bağlanmasını sağlamak için 40 dakika harcadım. Nihayet bağlandığında, kırılma noktası yanlış satıra düştü çünkü yol eşlemesi bir seviye kaymıştı. O anda düşündüm: “Bu kadar zor olmamalı. Ya hata ayıklayıcı hiç uzantı gerektirmeden çalışıyorsa? Ya kodun bir parçası olursa?”
DDLess böyle doğdu.
İterasyon 1: phpdbg
Kodu enjekte etmeden önceki ilk fikrim var olan bir şeyi kullanmaktı. PHP’nin phpdbg adında, entegre bir etkileşimli hata ayıklayıcısı var. Zend Engine opcode’larını kullanıyor, hızlı ve uzantı gerektirmiyor. Mükemmel görünüyordu.
Ancak sorun şu ki, phpdbg terminalde etkileşimli olacak şekilde tasarlanmış. Dış bir UI ile entegrasyon için temiz bir iletişim protokolü yok. Grafiğe bir arayüzle çalışması için metin çıktısını ayrıştırmam gerekecekti ve bu PHP sürümleri arasında değişiyor. Dahası, Laravel Sail veya Docker gibi ortamlarda iyi çalışmıyor çünkü süreç giriş noktası olması gerekiyor ve bu durum web sunucusu varsa çalışmıyor.
Birkaç saat içinde bu yolun uyumluluk çukuru haline geleceğini anladım ve bunu bıraktım.
İterasyon 2: Regex ve Tokenizer
Bir sonraki fikir: Hata ayıklama çağrılarını doğrudan PHP koduna enjekte etsem? Belki her çalıştırılabilir satırdan önce bir ddless_step_check(__FILE__, __LINE__) gibi. PHP bu değiştirilmiş kodu çalıştırırsa, uzantıya ihtiyaç duymadan yürütme akışının tam kontrolüne sahip olurdum.
İlk adım, “enjeksiyon yapılabilir” satırları bulmaktı, yani kırılma noktasını koymanın mantıklı olduğu yerler. Bir yoruma, bir sınıf beyanına veya çok satırlı bir dizenin ortasında bir kırılma noktası koymanın bir anlamı yok.
İlk uygulamam PHP’nin token_get_all() fonksiyonunu kullanarak kodu tokenize etti ve nerede enjekte edileceğine karar vermek için regex ile bir durum makinesi oluşturdu. Bu, basit durumlar için çalışıyordu:
$x = 1; // enjekte edilebilir
echo $x; // enjekte edilebilir
if ($x > 0) { // enjekte edilebilir
Ama kenar durumlar sonsuzdu. Çok satırlı zincirleme metod çağrıları:
$query->where('status', 'active')
->orderBy('created_at') // devam, yeni bir ifade değil
->get();
İçinde PHP kodu bulunan heredoc/nowdoc dizeleri. Kapatma ] işaretinin bir sonraki satırda olduğu çok satırlı diziler. elseif gibi özel işlemler gerektiren durumlar, çünkü bir elseif öncesinde bir enjeksyon yapmak, if zincirini bozar. Aynı durum else, catch ve finally için de geçerli.
660 satıra yakın kod gerekli oldu ddless_analyze_code_tokens(), ddless_is_safe_to_inject_before(), ve birkaç yardımcı fonksiyon arasında. Her hafta yeni bir durum, her şeyi bozdu. Bir geliştirici farklı bir kodlama kalıbı kullandığında, enstrümantasyon bir sözdizimi hatası oluşturuyordu.
İnşaatımı kum üzerine kurduğumu biliyordum. Ama bir süre çalıştı ve ana fikrin – kod enstrümantasyonu ve dosya tabanlı IPC’nin birleşimi – uygulanabilir olduğunu doğrulamama izin verdi.
Daha fazlasını görmek için: https://ddless.com/blog/technical-journey-building-php-debugger
Kaynak: Orijinal Makale


