C/C++ için rr – kayıt ve yeniden oynatma hata ayıklayıcısı
(rr-project.org)- rr, Linux'ta başarısız olan bir çalıştırmayı bir kez kaydedip aynı çalıştırmayı gdb ile tekrar tekrar oynatmanıza olanak tanıyarak, yeniden üretmesi zor C/C++ hatalarını izlemeyi kolaylaştırır
- Yeniden oynatma sırasında adres alanı, yazmaçlar, sistem çağrısı verileri ve bellek yerleşimi her seferinde aynı kaldığından, nesne adresleri veya olay sırası gibi hata ayıklama ipuçları kaybolmaz
- Normal gdb komutlarını, betik yazımını ve IDE entegrasyonunu destekler; donanım veri watchpoint'lerini geri yönlü yürütme ile birleştirerek değerin değiştiği noktaya geri dönmeyi sağlar
- Firefox, Chrome, QEMU, LibreOffice, Go programları, çok süreçli iş yükleri ve kapsayıcılar dahil olmak üzere geniş bir yelpazeyi ele alır; ancak tek çekirdek emülasyonu, paylaşımlı bellek ve CPU·sistem çağrısı destek kapsamı konusunda kısıtları vardır
- Aralıklı hataları ya da fuzzer'lar ve rastgele hata enjeksiyonu ile ortaya çıkan başarısız çalıştırmaları saklayıp tekrar tekrar incelemeye imkan vererek, hata düzeltme maliyetini düşürüp yazılım kalitesini artırmada yararlıdır
rr'nin hata ayıklama modeli
- rr, Linux için bir C/C++ hata ayıklama aracıdır; gdb'nin yerine geçmektense, kayıt ve yeniden oynatma özellikleriyle gdb iş akışını güçlendirir
- Hatanın oluştuğu bir çalıştırma bir kez kaydedildiğinde, sonrasında aynı çalıştırmayı istediğiniz kadar tekrar hata ayıklayabilirsiniz
- Yeniden oynatma, canlı çalıştırmadaki gibi belirlenimsiz şekilde değişmez; her seferinde aynı durumla ilerler
- gdb altında verimli geri yönlü yürütme sunar
- breakpoint ve data watchpoint ayarlanabilir
- ilgi duyulan noktaya kadar hızlıca reverse-execute yapılabilir
- Gerçek uygulamalarda kullanılır ve birçok geliştirici tarafından gerçek hataları düzeltmek için değerlendirilmektedir
Desteklenen özellikler ve uygulama kapsamı
- rr'nin özellikleri, gerçek dünya uygulamaları ve gdb tabanlı iş akışları düşünülerek şekillendirilmiştir
- Benzer araçlara göre daha düşük ek yük; özellikle çoğunlukla tek iş parçacıklı iş yüklerinde düşük overhead
- Firefox, Chrome, QEMU, LibreOffice, Go programları gibi çeşitli uygulamaların kaydı ve yeniden oynatılmasını destekler
- Tüm kapsayıcılar dahil çok süreçli iş yüklerinin kaydı, yeniden oynatılması ve hata ayıklanmasını destekler
- gdb betik yazımı ve IDE entegrasyonu desteği
- Makineler arasında taşınabilen, kalıcı ve sıkıştırılmış trace desteği
- Aralıklı hataları daha iyi ortaya çıkaran Chaos mode sunar
Kayıt ve yeniden oynatma akışı
- Uygulama
rr record /your/application --argsile kaydedilir - Hata içeren tüm çalıştırma diske kaydedilir ve ardından
rr replayile hata ayıklanabilir - Yeniden oynatma sırasında canlı çalıştırma değil, kaydedilmiş trace hata ayıklanır
- Adres alanı, yazmaç içerikleri ve sistem çağrısı verileri her yeniden oynatmada aynı kalır
- Çoğu normal gdb komutu olduğu gibi kullanılabilir
- Hata ayıklama oturumunu yeniden başlatmak gerektiğinde, gdb'nin
runkomutuyla kayıt baştan oynatılır- Yeniden başlatmadan sonra da aynı çalıştırma tekrar oynatılır
- Hata ayıklama durumu yeniden başlatmalar arasında korunur
- Dinamik olarak ayrılmış nesnelerin
thisişaretçisi de her yeniden oynatma oturumunda aynıdır- Bellek tahsisi her seferinde aynı olduğundan, izlenecek adres sabit olarak yazılabilir
Geri yönlü yürütme ve watchpoint
- rr, bir değerin neden yanlış ayarlandığını ararken sonuçtan nedene doğru geri gitmeye dayalı hata ayıklamayı mümkün kılar
- Firefox layout hata ayıklama örneğinde,
mRect.widthdeğerinin yanlış olduğu görüldükten sonrawatch -l mRect.widthvereverse-contkullanılır - Donanım watchpoint'i ile reverse execution birleştirilerek değerin
11220'den12000'e değiştiği nokta bulunur - Bu yaklaşım, sorunun görünür hale geldiği andan gerçek değişiklik noktasına kadar geri izleme işini azaltır
Kurulum ve başlangıç
- Kaynaktan derlemek için Building And Installing yönergeleri izlenebilir
- Paketler uygun değilse kaynaktan derleme önerilir
- Çekirdek değişiklikleri veya OS güncellemeleri bazen rr tarafında değişiklik gerektirebilir
- Fedora kurulum örneğinde
rr-5.9.0-Linux-$(uname -m).rpmindirilipsudo dnf installile kurulur - Ubuntu kurulum örneğinde
rr-5.9.0-Linux-$(uname -m).debindirilipsudo dpkg -iile kurulur
Aralıklı hataları ele alma yaklaşımı
- rr'nin çıkış noktası, aralıklı hataların hata ayıklamasını kolaylaştırmaktı
- Aralıklı hatalarda bazı çalıştırmalarda hata görünmez, bu da hata ayıklamayı zorlaştırır
- Test çalıştırmaları düşük overhead ile kaydedilir; hata oluştuğunda ilgili çalıştırma hata ayıklayıcıda tekrar tekrar oynatılabilir
- Deterministik yeniden oynatma, sıradan hata ayıklamada da ipuçlarının birikerek korunmasını sağlar
- Normal hata ayıklayıcılarda yeniden çalıştırma sırasında ilgilenilen nesnenin adresi veya önemli olayların sırası değişebilir ve önceki bilgi işe yaramaz hale gelebilir
- rr'de başarısız çalıştırmaya dair bilgi, yeniden oynatma tekrar edilse de korunur
- Hata ayıklama genellikle sonuçtan nedene doğru giden bir süreç olduğu için, zamanda geriye doğru yürütme imkanı neden takibini kolaylaştırır
- rr, düşük overhead'li bir kayıt-yeniden oynatma sistemi ve gdb'nin reverse execution komutları için pratik bir arka uç sunar
- Büyük küçük birçok projede geliştiricilerin düzenli olarak kullandığı çalışan bir araçtır
Deterministik yeniden oynatmanın garanti ettiği durum
- rr, Linux kullanıcı alanı süreç gruplarını kaydeder
- Kaydedilen süreçlerin çekirdekten aldığı tüm girdileri ve belirlenimsiz CPU etkilerini yakalar
- Yeniden oynatma, komut düzeyi kontrol akışı, bellek ve yazmaç içeriklerinin korunacağını garanti eder
- Bellek yerleşimi her zaman aynıdır; nesne adresleri değişmez, yazmaç değerleri aynı kalır ve sistem çağrıları aynı veriyi döndürür
- Fuzzer'lar ve rastgele hata enjeksiyonu araçları rr ile birlikte kullanıldığında daha güçlü hale gelir
- Bu araçlar aralıklı hatalar üretmekte iyidir, ancak aynı hatayı tekrar üretmek zor olabilir
- rr, rastgele çalıştırmayı kaydeder ve hata oluşursa kaydedilmiş kayıt üzerinden sorunun deterministik olarak hata ayıklanmasını sağlar
Mevcut kayıt-yeniden oynatma araçlarından farkı
- rr, eski bir fikir olan record-and-replay debugging yaklaşımını belirli tasarım hedeflerine göre uygular
- İlk odak noktası Firefox idi
- Birçok kayıt-yeniden oynatma tekniği belirli bir dil gerektirir veya ölçek açısından Firefox'u ele almakta yetersiz kalır
- Firefox karmaşık bir uygulama olduğundan, Firefox hata ayıklamasında işe yarıyorsa genelde başka yerlerde de yararlı olma ihtimali yüksektir
- Yaygın olarak dağıtılabilir olmasına önem verir
- rr, standart Linux çekirdeği ve genel amaçlı donanım üzerinde çalışır
- Sistem yapılandırması değişikliği gerektirmez
- Diğer birçok teknik çekirdek değişiklikleri veya işletim sisteminin sanal makinede çalıştırılmasını gerektirir
- Düşük çalışma zamanı overhead'ini hedefler
- gdb iş akışının yerini alabilmesi için, gdb kullanırken alınan sonuca benzer hızda sonuç vermesi gerekir
- Düşük overhead, testlerin bozulmasını da azaltır
- Tasarım sadeliğine önem verir
- Dinamik ikili enstrümantasyon gibi karmaşık tekniklere dayanan yaklaşımlardan kaçınır
- Bu sadelik, rr'nin sağlamlığına ve düşük overhead'ine de katkı sağlar
Performans ve kısıtlar
- rr'nin overhead'i uygulamanın iş yüküne göre değişir
- Firefox test paketinde kayıt performansı pratik düzeydedir
- Bazı durumlarda slowdown ≤ 1.2x seviyesine kadar iner
- 10 dakika süren bir test paketi, rr ile kayıt sırasında yaklaşık 12 dakika sürer
- Overhead, iş yüküne göre büyük ölçüde değişebilir
- Çoğu tek iş parçacıklı programda, bilinen rakip kayıt-yeniden oynatma sistemlerine göre çok daha düşük overhead'e sahiptir
- Başlıca kısıtlar şunlardır
- Tek çekirdekli bir makineyi emüle ettiğinden, paralel programlar tek çekirdek çalıştırmanın getirdiği slowdown'u yaşar
- Kayıt ağacının dışındaki süreçlerle bellek paylaşan süreçler kaydedilemez
- X shared memory gibi özellikler kayıt sürecinde otomatik olarak devre dışı bırakılır
- Görece yeni x86 CPU'lar veya Apple M1+ gibi belirli ARM CPU'lar gerekir
- Kaydedilen sürecin çalıştırdığı tüm sistem çağrılarının bilinmesi gerekir
- Firefox ve çeşitli uygulamalar için gereken geniş bir sistem çağrısı aralığını destekler, ancak tam değildir
- Desteklenmeyen sistem çağrıları GitHub issue olarak bildirilebilir
- Çekirdek değişiklikleri, sistem kütüphanesi güncellemeleri veya yeni CPU ailelerine uyum için güncelleme gerekebilir
Kaynaklar ve topluluk
- Extended Technical Report, rr'nin çalışma biçimini ve performansını ele alan bir genel bakış kaynağıdır
- rr wiki, rr ile ilgili teknik konuları kapsar
- Sorular mailing list veya #rr on chat.mozilla.org üzerinden sorulabilir
- Demo videoları da sunulmaktadır
1 yorum
Hacker News yorumları
rr'de değişken değişim breakpoint'leri ile reverse-continue'u birlikte kullanarak büyük bir kod tabanını tersine mühendislik yapmakta oldukça başarılı oldum
Derinlerdeki çekirdek mantığı çıkarmak biraz zaman aldı ama faydalıydı
Birinin rr'yi Rust'a port etmeye çalıştığını ve yaklaşık 60 bin satıra ulaştıktan sonra projeyi arşivlediğini belirtmek de değer
C++'tan Rust'a yeniden yazımın etkileri, avantajları, sınırlamaları ve zorluklarını karşılaştırmak için ilginç bir vaka incelemesi gibi görünüyor
https://github.com/sidkshatriya/rd/
Ancak bunu resmi sürüme taşımayı zorlaştıran iki büyük neden vardı: 1) rr'de 10 yıl boyunca birikmiş, tuhaf kernel/process davranışlarını ele almak için yapılmış çok sayıda karmaşık düzeltme var ve bunların port sırasında kaybolmasından endişe ettik, 2) rr üzerinde geliştirilmiş kapalı kaynak bir proje olan remix[0]'i de birlikte port etmemiz gerekiyordu
[0] https://robert.ocallahan.org/2020/12/rr-remix-efficient-repl...
Rust'ı çok iyi bilmiyorum ama C ile birlikte çalışabilirliği iyi gibi görünüyor. Zaten iyi çalışan bir yazılım için Rust'ta yeniden yazmanın faydası ne olur, merak ediyorum
Kabaca bakınca rr 1.0'ın 3 yılda çıktığı, 3-4 kadar büyük katkı aldığı ve en az 5 kişinin katkıda bulunduğu görülüyor. Bugünkü rr ise bunun üstüne eklenmiş 10 yıllık ek çalışmanın sonucu
Gerçekten yalnızca C/C++ için olup olmadığını merak ediyorum
Kısıtlı anlayışıma göre bir debugger'ın sembol listesi, Windows'taki .pdb dosyaları ve Linux'taki karşılıkları, sistem çağrılarını anlama ve benzeri şeylere ihtiyacı var. Hedef ikiliyi neyin ürettiğini çok da umursamadığını düşünüyordum. Tabii bunun yerel kod olduğu varsayımıyla
rr'nin Rust, Zig, Odin, Nim gibi dillerde de çalışıp çalışmadığını merak ediyorum. Python, JS, C# gibi yönetilen bellek kullanan dilleri elbette beklemiyorum
Zig'in allocator'ı ile birlikte oldukça kullanışlı. Free sırasında 0xaa byte yazıyor ve adresleri yeniden kullanmıyor; bu da çökme olasılığını artırıyor, ardından o belleğe bir watchpoint koyup serbest bırakıldığı ana kadar geri sarabiliyorsunuz
https://github.com/python/devguide/issues/1283
https://morepypy.blogspot.com/2016/07/reverse-debugging-for-...
https://github.com/mesalock-linux/mesapy/blob/mesapy2.7/READ...
Sistemi yalnızca GDB perspektifinden gösteriyor ama yorumlanan dil de olsa derlenen dil de olsa çalışabiliyor
Çalışmadığı durum, sürücünün eşlenmiş adresleri doğrudan güncellemesi. CUDA buna bir örnek; yeniden oynatmak için sürücü etkileşimini modellemek gerekiyor ve UVM'ye gelmeden önce bile durum böyle
Bir başka güzel yanı da RR'nin süreç ağacını kaydetmesi; böylece çalıştırılan dosyanın başlattığı diğer süreçleri kolayca inceleyebiliyorsunuz
Ancak rr tüm sistem çağrılarını sarmak zorunda olduğundan Linux'a çok özeldir; bu yüzden Windows'ta çalışmaz. Linux'taki sembol bilgi biçimi DWARF'tır
rr gerçekten harika ama onu “büyük silah” olarak kullanmaya karar verdiğim neredeyse her seferde sorun eşzamanlılık hatasıydı ve bu yüzden rr çoğu zaman yeniden üretemedi
Yine de bazı dillerin rr'yi araç zincirine doğrudan eklemesi gerçekten güzel olurdu. Elbette rr/gdb'yi her zaman “öylece” kullanabilirsiniz ama rr çağırmayı ayarlamanın ve kullanmanın Python'un pdb'si kadar kolay olduğunu düşünün
Temelde çalışan thread'i sık sık değiştirerek birden fazla çekirdeğin aynı anda çalıştığı durumu taklit eder. Bende gerçekten birkaç race condition bulmuştu ama elbette sınırları var
Bir MPI işinde rr'yi yalnızca rank 0'a bağladım ve farklı send/recv sıralarının soruna yol açtığı noktayı buldum. Üstelik çok sayıda yerel kod üretimiyle bağlantılı bir Python modeli vardı, yani epey karmaşık bir sorundu
Kullanmak istiyorum ama yaptığım işlerin çoğunda bir şekilde GPU işin içinde oluyor
Gerçekten ihtiyaç duyduğum zaman Mac'teydim ve ne yazık ki rr yalnızca Linux'ta çalışıyor
Mac'te çalışan ve muhtemelen multithreading'i de destekleyebilen Undodb var ama ne yazık ki yaklaşık 50 bin dolar tutuyor
rr tabanlı olsa da, tüm program çalışmasını sorgulanabilir bir veritabanı olarak ekleyen https://pernos.co/ da bakmaya değer
Şunları yapabiliyor
GDB içinde yerleşik geri yönlü hata ayıklama da var: https://www.sourceware.org/gdb/wiki/ProcessRecord/Tutorial
rr'nin daha fazla özellik ve esneklik sunduğunu düşünüyorum ama her hâlükârda GDB'nin kendisi de bir süredir geri yönlü hata ayıklama yapabiliyordu
GDB'nin implementasyonu, istediğiniz anda kaydı başlatıp durdurabildiği için rr'den daha kullanışlı, ancak verimliliği birkaç basamak daha düşük. Yalnızca çok küçük kod parçalarında işe yarıyor; onun dışında pratikte sonsuza kadar sürüyor ya da kaynakları tüketiyor
Çalıştı ve bug'ı izlemeye yardımcı oldu ama dayanılmaz derecede yavaştı. Kullanılabilir hâle getirmek için girdi boyutunu küçültmem gerekti, neyse ki bunu yaptıktan sonra da sorunu yeniden üretebildim
İstediğiniz program durumuna 10000'den az yeniden başlatmayla ikili arama yaparak ulaşabiliyorsanız, geri yürütme kullanmaktan daha az makine zamanı harcarsınız. Hatta yavaşlama o kadar büyük ki, debugger'ı etkileşimli olarak neredeyse doğru noktaya birkaç kez götürüp ardından programı yeniden başlatmak çoğu zaman yeterli oluyor
gdb'nin replay özelliğini faydalı şekilde kullanabildiğim durum, programı başladıktan 1 saniyeden kısa süre içinde çökerten bir girdi dosyasına sahip olmamdı. Bu sayede “bu değişken yanlış” noktasından “bu değişken bu yanlış değere nasıl ayarlandı?” sorusuna kadar yalnızca birkaç dakika bekleyerek geriye gidebildim
Windows'ta aynı amaç için WinDbg kullanılabilir
Çok iş parçacıklı sorunları hata ayıklama desteği daha iyi
https://www.forrestthewoods.com/blog/windbg-time-travelling-...
rr ise kayıt-tekrar oynatma tabanlı bir zaman yolculuğu hata ayıklama implementasyonu kullanıyor ve doğru uygulandığında çok daha düşük overhead mümkün. En son baktığımda rr'nin overhead'i yaklaşık 2 kat yavaşlama düzeyindeydi; doğru hatırlıyorsam başka kayıt-tekrar oynatma zaman yolculuğu hata ayıklayıcılarında %10 düzeyini bile görmüştüm
%10, WinDbg'den 100 kat daha düşük maliyet demek ve production'da sürekli açık tutmak için yeterince düşük. Bu oyunun kurallarını değiştiren bir fark
Çok eskiden VMWare Workstation da bu özelliği destekliyordu
Yalnızca kullanıcı alanı programları değil, VM içindeki kernel ve driver'lar için de mümkündü. Özellik yayımlandı, birkaç sürüm boyunca vardı, sonra şirket içi politik nedenlerle kaldırıldı
Eklentilerle genişletilebiliyordu ve eklentiler emüle edilen CPU üzerinde tam denetime sahip olabiliyordu. Benzer bir şey hâlâ bir yerlerde var mı merak ediyorum
rr'nin Ryzen CPU sorununun artık kesin olarak geçmişte kalıp kalmadığını merak ediyorum
Oldukça yeni bir Threadripper Pro 7950 üzerinde Firefox çalıştırmayı kaydetmek için rr'yi her gün, tüm gün kullanıyorum; Pernosco ile birlikte de kullanıyorum. GitHub'daki rr wiki'sinde bunu çalıştırmanın nasıl yapılacağı anlatılıyor. Küçük bir workaround uyguladıktan sonra gayet kararlı çalışıyor
Eski tartışmalar da var
https://news.ycombinator.com/item?id=31617600 (Haziran 2022)
https://news.ycombinator.com/item?id=18388879 (Kasım 2018)