HN Tanıtımı: FlowTracker – Java programlarında veri akışını izleme
(github.com/coekie)- FlowTracker, Java programlarının veriyi okuma, işleme ve yazma sürecini izleyen bir Java agent’tır; çıktının hangi girdi, dosya, ağ ya da kod sabitinden geldiğini bağlayarak gösterir
- Çalışan programı gözlemleyerek dosya ve ağ I/O’sunu gösterir; özellikle girdi ile çıktı arasındaki eşleşmeyi izleyerek Java programının çıktısının ne anlama geldiğini ve neden üretildiğini anlamaya yardımcı olur
- Spring PetClinic demosunda HTTP yanıtının başlıklarından Thymeleaf şablonlarına, veritabanı değerlerine ve SQL ekleme betiklerine kadar iz sürerek yazılım yığınının birden çok katmanında gezinmek mümkündür
- İçeride JVM yükleme anında bytecode’u enstrümante eder; JDK metot hook’ları, veri akışı analizi,
ThreadLocaltabanlı çağrı izleme veClassOriginTracker’ı birleştirerek string/karakter/byte odaklı köken eşlemesini korur - Mevcut durumu üretime hazır olmaktan çok kavram kanıtına yakındır; bazı örnek programlarda iyi çalışmış olsa da her program için uygun değildir ve büyük ek yük nedeniyle çalışma hızını ciddi ölçüde düşürür
FlowTracker neyi izliyor?
- FlowTracker, Java programı içinde verinin nasıl okunduğunu, aktarıldığını, dönüştürüldüğünü ve yazıldığını izleyen bir Java agent’tır
- Yalnızca dosya ve ağ I/O’sunu göstermekle kalmaz, program çıktısının hangi girdiden geldiğini de bağlayarak gösterir
- Amaç, bir Java programının çıktısının ne anlama geldiğini ve programın bu çıktıyı neden yazdığını anlamaktır
- Mevcut proje, program davranışına bu bakış açısından bakıldığında hangi içgörülerin elde edilebileceğini araştıran bir proof-of-concept’tir
Demo: Spring PetClinic’te HTTP yanıtının kökenini izleme
- FlowTracker PetClinic demo, Spring PetClinic’in bir HTTP isteğini işlemesini ve şablonlar ile veritabanı verilerine dayanarak HTML sayfası üretmesini tarayıcıda görmeyi sağlar
- Ekranda PetClinic’in ağ üzerinden gönderdiği HTTP yanıtı gösterilir; yanıt gövdesinin bir kısmına tıklandığında alt görünümde o kısmın nereden geldiği görülebilir
- Soldaki ağaçtan veya mobilde sol alttaki düğmeden izlenen girdi/köken ya da çıktı/sink seçilebilir
-
HTTP işleme katmanı
"HTTP/1.1"veya HTTP başlıklarına tıklarsanız yanıtın bu bölümününorg.apache.coyotepaketindeki Apache Coyote sınıfları tarafından üretildiğini görebilirsiniz- FlowTracker hangi kodun hangi çıktıyı ürettiğini gösterir
-
Thymeleaf şablon katmanı
"html"veya"head"gibi HTML etiket adlarına tıklarsanız ilgili HTML bölümününlayout.htmldosyasından geldiğini görebilirsinizlayout.html’e tıkladıktan sonra alttaki renkli+düğmesine basarsanız o dosyadan gelen tüm bölümler aynı renkle gösterilir- Aşağı kaydırdığınızda yanıtın bir bölümünün başka bir dosya olan
ownerDetails.html’den geldiğini doğrulayabilirsiniz <veya>karakterine tıklarsanız ilgili karakterin Thymeleaf şablon kütüphanesi tarafından yazıldığını görebilirsiniz
-
Veritabanı değerlerinin kökeni
- HTML sayfasındaki tabloda veritabanından gelen bilgiler bulunur
- Tablodaki
George’a tıklarsanız bu değerin veritabanından geldiği seviyesinin ötesine geçilerek, değeri ilk başta veritabanına ekleyen SQL betiğine kadar izlenir - Bu demoda SQL betiğine kadar izlenebilmesinin nedeni, bellek içi veritabanı kullanıldığı için veritabanı içeriğinin JVM dışına çıkmamış olmasıdır
MySQL demosu ve framework bağımsızlığı
- Aynı PetClinic demosu MySQL veritabanıyla çalıştırıldığında değerler veritabanı bağlantı noktasına kadar izlenir
- Bu durumda değeri oluşturmak için daha önce gönderilen SQL sorgusunu ve MySQL JDBC sürücüsünün veritabanıyla nasıl iletişim kurduğuna dair ayrıntıları görebilirsiniz
- FlowTracker PetClinic mysql demo, FlowTracker’ın veritabanı SSL bağlantısı üzerinden iletilen şifresi çözülmüş içeriği yakaladığını da gösterir
- Spring PetClinic yalnızca bir örnektir; FlowTracker belirli bir framework’e veya kütüphaneye bağlı değildir
- javac demo, Java derleyicisini gözlemleyerek üretilen class dosyası biçimini ve içindeki bytecode’u anlamada FlowTracker’ın nasıl yardımcı olduğunu gösterir
Kullanım ve dikkat edilmesi gerekenler
- FlowTracker şu anda üretime hazır olmaktan çok proof-of-concept’e yakındır
- Birçok örnek programda iyi çalışmıştır, ancak her programda iyi çalışacağı garanti edilmez
- Yüksek ek yük getirdiğinden programın çalışması çok daha yavaşlar
- Kullanım adımları:
- Github releases pages üzerinden
flowtracker-*.jaragent jar’ını indirin - Java komut satırına
-javaagent:path/to/flowtracker.jarekleyin - FlowTracker’ı engelleyen bazı JVM optimizasyonlarını kapatmak için
java -jar flowtracker.jar jvmoptsçıktısını da komut satırına ekleyin - Varsayılan olarak FlowTracker 8011 portunda bir web sunucusu başlatır; tarayıcıda
http://localhost:8011/adresini açmanız yeterlidir
- Github releases pages üzerinden
- Daha ayrıntılı yapılandırma seçenekleri
USAGE.mdiçinde yer alır
İç işleyiş: bytecode enstrümantasyonu ve Tracker modeli
- FlowTracker, JVM sınıfları yüklerken class dosyalarına, yani bytecode’a kod enjekte eden bir enstrümantasyon agent’ıdır
- Enjekte edilen kod, program veriyi okurken, aktarırken ve yazarken bellekteki veri ile kökenleri arasındaki eşlemeyi korur
- İzleme odağı
String,char,byte[]gibi metin ve ikili veriler üzerindedir; sayısal, yapılandırılmış veya hesaplanmış veriler merkeze alınmaz - Kullanılan yöntemler:
- Bazı JDK metot çağrılarını FlowTracker sürümündeki metot çağrılarıyla değiştirir
- Girdi ve çıktıları izlemek için JDK’nin kritik noktalarına kod enjekte eder
- Metot içindeki yerel değişkenleri ve stack değerlerini izlemek için veri akışı analizi ve daha derin enstrümantasyon yapar
- Metot çağrılarının öncesine ve sonrasına, ayrıca çağrılan metodun başına ve sonuna kod ekleyerek argümanları ve dönüş değerlerini
ThreadLocalile izler
-
Tracker veri modeli
Tracker: İzlenen nesnenin içeriğini ve köken bilgisini tutarcontent:InputStreamveyaOutputStreamüzerinden geçen tüm byte’lar gibi verilersource: İçeriğin belirli bir aralığını başka bir tracker’ın belirli bir aralığıyla bağlarTrackerRepository: İlgilenilen nesneler ile bunlara karşılık gelenTracker’ı bağlayan büyük bir globalMap<Object, Tracker>tutarTrackerPoint: Bir tracker içindeki bir konumu gösterir ve tek birbytekökeni gibi tekil bir primitive değeri temsil eder
Temel enstrümantasyon: JDK hook’ları ve ASM
- FlowTracker, belirli JDK metotları çağrıldığında hook metodu çağrıları ekleyerek
Tracker’ı güncel tutar - En basit örnek
System.arraycopy’dirjava.lang.System.arraycopyçağrısınıcom.coekie.flowtracker.hook.SystemHook.arraycopyçağrısıyla değiştirirSystemHook, gerçekarraycopy’yi çağırdıktan sonraTrackerRepository’den kaynak ve hedef dizilerin tracker’larını alır ve hedef tracker’ı kaynağı gösterecek şekilde günceller
- Bu tür enstrümantasyon için ASM bytecode manipülasyon kütüphanesi kullanılır
- Hook’ların çoğu çağıran tarafa değil, JDK metodunun içindeki çağrılan tarafa eklenir
- Örneğin
FileInputStream.read(byte[])sonunaFileInputStreamHook.afterReadByteArrayçağrısı eklenir - Bu enstrümantasyon, ASM’nin
AdviceAdapter’ını kullanan annotation tabanlı özel bir mikro framework ile uygulanır
- Örneğin
- FlowTracker,
java.io.FileInputStream,java.io.FileOutputStream,sun.nio.ch.FileChannelImpl,sun.nio.ch.IOUtil,sun.nio.ch.NioSocketImplgibi JDK’nın I/O ile ilgili sınıflarına hook ekler - İlgili implementasyonlar:
primitive değerlerin izlenmesi ve metot içi veri akışı analizi
bytegibi primitive değerlerin nesneler gibi identity’si olmadığından,TrackerRepository’ninMapanahtarı olarak güvenli biçimde izlenemezler- FlowTracker, primitive değerlerin kaynağını metot içindeki yerel değişkenlerde ayrıca saklayacak şekilde kodu yeniden yazar
- Örneğin
byte b = x[1]sonrasındaArrayHook.getElementTracker(x, 1)ileb’nin tracker’ı alınır;y[2] = bsırasında daArrayHook.setElementTracker(y, 2, bTracker)ile hedef diziye kaynak bilgisi kaydedilir - Bunun için FlowTracker, ASM’nin analiz özellikleri üzerinde sembolik yorumlama (symbolic interpretation) gerçekleştirir
- Metodun her noktasında yerel değişkenlerdeki ve stack’teki değerlerin nereden geldiğini ve nereye gittiğini modeller
- İlgili implementasyonlar:
- FlowValue ve ArrayLoadValue
- MergedValue: if ifadeleri veya loop’lar gibi kontrol akışı nedeniyle bir değerin birden fazla konumdan gelebileceği durumları işler
- FlowInterpreter: ASM
Interpretergenişletmesi olarak bytecode komutlarını yorumlar ve uygunFlowValue’yi oluşturur - Store ve ArrayStore
- FlowTransformer: tüm analiz ve enstrümantasyon sürecini yürütür
- Tüm primitive değerler izlenmez; odak
bytevecharüzerindedir,intvelongise daha sınırlı şekilde ele alınır
Metot çağrılarını aşan veri akışı
- Yalnızca metot içi analiz, primitive değerlerin başka metotların argümanlarına ve dönüş değerlerine aktığı durumları işleyemez
- FlowTracker,
Invocationiçinde argümanların ve dönüş değerininPointTracker’ını saklar ve metot çağrısından hemen önce bunuThreadLocal’a koyar - Çağrılan metodun başlangıç noktasında
Invocation.start(...)ileThreadLocal’daki bilgi çıkarılarak primitive argümanların kaynağı kullanılabilir - Bu yöntemle
out.write(b)gibi primitive bir değer metoda geçirildiğinde dewrite(byte value)içindevalue’nun tracker’ı devralınabilir - İlgili implementasyonlar:
Kodun kendisini veri kaynağı olarak ele almak
- FlowTracker’ın izlediği başlıca kaynaklar, I/O’dan ve kodun kendisinden gelen değerlerdir
- Koddan gelen değerler arasında
'a',"abc"gibi primitive veStringsabitleri bulunur - Bu tür sabitler için her sınıf başına bir
ClassOriginTrackeroluşturulur ve sınıf ile sabit referanslarını metin olarak temsil eden içerik saklanır - Bir sabite başvurulduğunda, ilgili değerin tracker’ı bu metin temsilindeki konumu işaret eder
- Bu model, sabitleri kodun metinsel temsilinden okunmuş gibi ele aldığı için I/O izleme modeline benzer hale gelir
- Performans nedeniyle
constantPointmetodunun her metod çalıştırıldığında çağrılmasını önlemek için ConstantDynamic (JEP 309) kullanılır - İlgili implementasyonlar:
String literal işleme ve kısıtlar
- String literal’lar yeni bir
Stringkopyası oluşturur veString.valueiçindekibyte[]değeriniClassOriginTrackerile ilişkilendirir String s = "abc";gibi bir ifadeString s = StringHook.constantString("abc", 1234, 81);biçiminde yeniden yazılır- Bu yöntem, JVM’in normalde sağladığı String interning garantisini bozar
- Normalde aynı String sabitinin tüm görünümleri aynı instance’a başvurmalıdır
- Instrumentation sonrasında bu garantiye dayanan kod bozulabilir
- FlowTracker bu sorunu azaltmak için bazı mekanizmalar sunar
- ConstantDynamic kullanarak, aynı satırdaki aynı String literal birden çok kez çalıştırılsa bile her seferinde aynı instance’ın döndürülmesini sağlar
- Bazı
stringA == stringBifadeleriniObjects.equals(stringA, stringB)olarak yeniden yazarak belirli açılardan aynı instance gibi görünmesini sağlar java.lang.*gibi bazı paketlerde String literal izlemeyi devre dışı bırakır- Bu davranış,
USAGE.mdiçindekibreakStringInterningile yapılandırılabilir
- İlgili implementasyonlar:
İzlenmeyen değerler için fallback
- FlowTracker programdaki tüm değerleri izlemez
- Bunun nedenleri performans kaygıları, henüz uygulanmamış kısımlar, düşük ilgili değerler ve birden çok kaynağın birleşiminden doğan değerleri temsil etmek için daha karmaşık bir veri modeline ihtiyaç duyulmasıdır
- Daha önce izlenmeyen bir değer, izlemenin başlaması gereken bir noktaya ulaştığında, sabitlere benzer şekilde
ClassOriginTracker’a bağlanır ve konumu"<?>"olarak temsil edilir - Örneğin dizi uzunluğu izlenmediği için
write(array.length)çağrıldığında,Invocation’awriteçağrı noktasının kod konumunu işaret eden birPointTrackeraktarılır - Sonuç olarak, ikili formattaki çıktıda asıl kaynak görülemese bile, çevredeki izlenen dizgeler ve kod konumu sayesinde değerin anlamını hızlıca yorumlamak mümkün olabilir
Ele alınabilecek diğer uygulama konuları
- MergedValue, dallanma ve döngülerden geçen değerlerin izlenmesini ele alır; bu, veri akışı analizinin en zor kısımlarından biri olarak kabul edilir
- String concatenation, indification (JEP 280) aracılığıyla
StringConcatFactorytarafından döndürülenMethodHandlea hook eklenerek işlenir - Kaynak kodu bulma, Vineflower ile decompile etme ve bytecode ile kaynak satırlarını ilişkilendirme de uygulamaya dahildir
- ClassLoader yapılandırması, bootclasspath bağımlılıkları ve uygulama çakışmalarından kaçınmaya; shading ve nested jar olmadan hızlı bir geliştirme döngüsünü korumaya odaklanır
- Alanlarda saklanan primitive değerlerin izlenmesi de uygulamaya dahildir
- Frontend, Jetty ve JAX-RS tabanlı bir web sunucusu ile Svelte tabanlı bir web arayüzünden oluşur
1 yorum
Hacker News yorumları
Enstrümantasyon için bir enstrümantasyon ajanı yerine resmi Clojure derleyicisinin bir fork’unu kullanıyor; geliştirme sırasında derleyiciyi kolayca değiştirmeyi sağlayan Clojure özelliğinden yararlanarak ek bytecode ekliyor
Clojure programlarının yürütme kayıtlarında ilginç olan şey, değerlerin çoğunun değişmez olması; bu yüzden yalnızca pointer’ları tutarak snapshot alınabiliyor
Orijinal yazıdaki demo bir web uygulamasında gezinme olduğu için, ilgilenenler adına FlowStorm ile bir web uygulamasını debug etmeyi gösteren demoyu da bırakıyorum: https://www.youtube.com/watch?v=h8AFpZkAwPo
Java/JVM ekosistemindeki araçların bu kadar iyi olması hoşuma gidiyor. En son böyle şaşırdığım zaman jitwatch’ı gördüğüm zamandı: https://github.com/AdoptOpenJDK/jitwatch
FlowTracker bana, güvenilmeyen kullanıcı girdilerinin veya gizli değerlerin program içinde nasıl aktığını izleyip sızdırılmamasını ya da doğrulanmadan kullanılmamasını sağlayan taint analysis’i biraz hatırlatıyor
Arama anahtar sözcüğü “dynamic taint tracking/analysis”
https://github.com/gmu-swe/phosphor
https://github.com/soot-oss/SootUp
https://github.com/feliam/klee-taint
İleride böyle araçların hata izlerken ilk savunma hattı haline geldiğini rahatlıkla hayal edebiliyorum
FlowTracker’ı geliştirirken işin büyük kısmı, belirli örnek programlarda izlemenin çalışmasını sağlamaktan çıktı
Hedef sonucu biliyordum; ama belirli bir örneğin çalışması için hangi düşük seviyeli mekanizmaları desteklemek gerektiğini öngörmek zordu ve bu çoğu zaman verinin içinden geçtiği JDK veya kütüphanelerin iç uygulama ayrıntılarına bağlıydı
Ama HTML öğesinin, o veriyi DB’ye koyan SQL script’ine bağlanması böyle bir şey değildi
Beklediğim ya da özellikle tasarladığım bir şey değildi; kendiliğinden oldu. Bu yüzden ben de epey şaşırdım ve bu yaklaşımla başka neler yapılabileceği konusunda heyecanlandım
Verinin geçici mi olduğu, yoksa yeniden yazılması mı gerektiğini izleyebilmenin de bir yolu olsa güzel olurdu
Bu tür kısıtları baştan ne kadar çok tanımlayabilirsek o kadar iyi
Smalltalk’ta her şey nesne ve mesaj olduğu için geriye doğru izlenebilir ve etkileşime girilebilir
Daha fazla genişletmeye zaman ayıramadım, ama web geliştirici araçlarının bu tür tam yığın atıf takibinden büyük fayda görebileceğini düşünüyorum
Ancak bu tarz çözümleri mevcut framework’lere entegre etmek büyük bir zorluk gibi geliyor
[1] HTML Source Maps - https://github.com/connorjclark/html-source-maps https://docs.google.com/document/d/19XYWiPL9h9vA6QcOrGV9Nfkr...
https://www.youtube.com/watch?v=TWAMr72VaaU&t=164s ve https://witheve.com/
FlowTracker’ın yaptığı iş genişletilirse SQL ya da başka injection açıkları da bulunabilir. Bu yüzden aklındaki aracın benzer bir yaklaşım kullanmış olması mümkün
Ya da “bu string oluşturulduğu andan ekranıma ulaşana kadar neler gördü?” gibi bir soru
Bu, o yöne atılmış bir adım gibi görünüyor
Şimdilik ara vermem gerekiyor, ama çalışır hale getirip kurcalamayı dört gözle bekliyorum