1 puan yazan GN⁺ 2024-07-22 | 1 yorum | WhatsApp'ta paylaş
  • 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 --args ile kaydedilir
  • Hata içeren tüm çalıştırma diske kaydedilir ve ardından rr replay ile 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 run komutuyla 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 this iş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.width değerinin yanlış olduğu görüldükten sonra watch -l mRect.width ve reverse-cont kullanılır
  • Donanım watchpoint'i ile reverse execution birleştirilerek değerin 11220'den 12000'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).rpm indirilip sudo dnf install ile kurulur
  • Ubuntu kurulum örneğinde rr-5.9.0-Linux-$(uname -m).deb indirilip sudo dpkg -i ile 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
    • Video 7: Firefox kayıt ve yeniden oynatma için hızlı demo
    • Video 8: rr'nin temel özelliklerinin ayrıntılı gösterimi
    • Video 9: Robert O'Callahan'dan rr hakkında üst düzey teknik sunum

1 yorum

 
GN⁺ 2024-07-22
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ı

    • İlginç görünüyor; paylaşılabilecek bir yazı özeti olup olmadığını merak ediyorum
    • O büyük kod tabanının, çok da yerinde bir ifade olmasa da, dinamik tipli bir dille yazıldığını varsaymanın doğru olup olmadığını merak ediyorum
  • 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/

    • rr bakımcısının bakış açısından Sid'in çalışması iyiydi ve biz de destekledik
      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...
    • Birkaç yıldır süren “X/Y/Z'yi Rust'ta yeniden yazalım” akımını pek anlayamıyorum
      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
    • İyi bir analiz olurdu ama kolay görünmüyor
      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 ile kullanıyorum
      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
    • Geçmişte çok belirli bir rpython derlemesinde rr'yi çalıştırmıştım; bu konuya ilginin sürüyor olması şaşırtıcı olabilir
      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...
    • Julia'da RR'yi çok kullanıyorum
      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
    • rr, asıl debugger kısmında gdb kullandığı için, gdb'de çalışan şey rr'de de çalışır
      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
    • Rust'ta da kullanılıyor: https://bitshifter.github.io/rr+rust/index.html#1
  • 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

    • rr çalıştırırken chaos mode seçeneğini kullanırsanız bazı eşzamanlılık sorunlarını ortaya çıkarabilir
      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
    • Benim de rr ile gerçekten yakalayabildiğim bir eşzamanlılık hatası olmuştu
      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
    • rr'nin ne yazık ki desteklemediği bir başka şey de GPU
      Kullanmak istiyorum ama yaptığım işlerin çoğunda bir şekilde GPU işin içinde oluyor
    • Bende de durum benzer
      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

    [...] Yalnızca yanlış değere tıklamanız yeterli. Tüm program geçmişi elinizde olduğu için Pernosco, bu değerin nereden geldiğini anında açıklayabilir. Değer, memcpy ya da register içine/dışına taşıma gibi olaylar boyunca geriye doğru izlenir ve “originated” olduğu noktaya ulaşana kadar her adım, otomatik olarak açılan “Dataflow” panelinde gösterilir. Kodu okuyup anlamanıza ve ne olmuş olabileceğini düşünmenize gerek kalmadan, gerçekten ne olduğunu doğrudan debugger'a sorabilirsiniz.
    https://pernos.co/examples/race-condition

  • 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

    • Bunu söylemek istiyorsanız, büyük olasılıkla gerçekten kullanmamışsınızdır
      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
    • O dönemde rr'nin henüz desteklemediği bir platformda GDB'nin yerleşik geri yönlü hata ayıklamasını bir kez başarıyla kullandım
      Ç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
    • gdb'nin yerleşik replay implementasyonu programda yaklaşık 10000 kat yavaşlamaya neden oluyor
      İ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
    • Doğru hatırlıyorsam rr, gdb'ninkinden önce çıkmıştı
  • 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-...

    • WinDbg, komut düzeyi emülasyona dayalı bir zaman yolculuğu implementasyonu kullandığı için, bu tekniğin getirdiği 10 ila 20 kat yavaşlama oluyor
      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ı

    • Ondan da önce AMD SimNow vardı sanırım
      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

    • Evet
      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)