2 puan yazan GN⁺ 2024-04-27 | 1 yorum | WhatsApp'ta paylaş
  • Bun v1.1.5, çökme veya panic durumunda Zig/C++ yığın bilgilerini kişisel veri içermeyen yaklaşık 150 baytlık bir URL ile ileten bun.report çökme raporlayıcısını ekledi
  • Mevcut işletim sistemi çökme raporlayıcıları ve core dump’lar; debug sembolleri, performans, gizlilik ve yürütülebilir dosya boyutu açısından büyük yük getirdiği için Bun gibi bir CLI aracına uygulanması zor
  • Yeni yöntem, ASLR nedeniyle anlamı belirsizleşen adresleri modüle göreli adreslere dönüştürüyor; sunucu da commit SHA’sı ve platforma uygun debug sembolleriyle fonksiyon adlarını geri yüklüyor
  • URL’de platform, alt komut, commit SHA’sı, özellik bayrakları, yığın adresleri, çökme tipi ve mesajı bulunuyor; yığın adresleri kısa olması için base64 VLQ ile kodlanıyor
  • JavaScript/TypeScript kaynak kodu veya ortam değişkenleri gönderilmiyor; Bun ekibinin tanılama için ihtiyaç duyduğu Zig/C++ yığın bilgileri ve bazı metaveriler iletiliyor

Bun neden kendi çökme raporlayıcısını geliştirdi?

  • Yazı yazıldığı sırada Bun’da 2.600’den fazla açık GitHub issue bulunuyor ve bazı issue’ların yeniden üretilmesi ve debug edilmesi özellikle zor
  • Sentry gibi çökme raporlama servisleri uygulamalar ve SaaS ürünleri için uygun olsa da, Bun gibi CLI araçlarında core dump yüklemek gizlilik, performans ve yürütülebilir dosya boyutu sorunlarını büyütüyor
  • Bun v1.1.5, Zig ve C++ çökme raporları için küçük, yeni bir format sunuyor
    • Çökme raporu yaklaşık 150 baytlık bir URL’ye sığıyor
    • Kişisel veri içermiyor

Yalnızca işletim sistemi çökme raporlayıcılarının yetersiz kaldığı noktalar

  • macOS gibi bazı işletim sistemlerinde yerleşik çökme raporlayıcıları var; ancak bunlardan doğru şekilde yararlanmak için genellikle uygulamayla birlikte debug sembollerini de dağıtmak gerekiyor
  • Debug sembolleri Bun dağıtım boyutunu ciddi ölçüde artırıyor
    • Linux debug sembolleri yaklaşık 30 MB
    • macOS debug sembolleri yaklaşık 9 MB
    • Windows .pdb dosyası 250 MB’ın üzerinde
  • Bun yürütülebilir dosyası örneğinde llvm-strip öncesi ve sonrası boyut 60M’den 51M’ye düşüyor
  • Debug sembolleri olmadan çökme meydana geldiğinde yığın izinde yalnızca ??? ve adresler kalıyor; bu da kullanım değerini azaltıyor
  • ASLR(Address space layout randomization) nedeniyle fonksiyon adreslerine rastgele ofsetler karışıyor ve bu haliyle fonksiyon adları geri yüklenemiyor

bun.report nasıl çalışıyor?

  • Bun v1.1.5’te bir çökme veya panic gerçekleştiğinde Bun; sürüm, platform, çalıştırma argümanları, bellek kullanımı ve çökme mesajıyla birlikte bir bun.report bağlantısı yazdırıyor
  • Kullanıcı bağlantıyı açtığında önceden doldurulmuş bir GitHub issue formuna yönlendiriliyor
  • URL’nin içinde yeniden eşlenmiş yığın izi kodlanmış halde bulunuyor
  • Sunucu, URL’deki bilgilere dayanarak yığın adreslerini geri yüklüyor ve Bun ekibinin okuyabileceği bir çökme raporuna dönüştürüyor

Adresleri okunabilir bir yığın izine dönüştürme süreci

  • Fonksiyon adresi, uygulama kodunun bellekte yüklendiği konumu gösteren bir pointer’dır ve güvenlik nedeniyle rastgele ofset içerir
  • Temel fikir, ham adresten ikilinin base address değerini çıkararak göreli adres elde etmektir
  • Gerçek uygulama, platformlara göre değişen API farkları nedeniyle daha karmaşıktır
    • Windows, GetModuleHandleExW üzerinde GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS bayrağını kullanır ve modül pointer’ını base address olarak alır
    • Linux, dl_iterate_phdr ile yüklü modülleri dolaşır ve adresi içeren modülün dl_phdr_info.dlpi_addr değerini base address olarak kullanır
    • macOS, _dyld_image_count ve _dyld_get_image_header ile modülleri dolaşır; _dyld_get_image_vmaddr_slide ile ASLR slide değerini alır
      • macOS sonuç adreslerinde image offset kalır; Bun için bu değer 0x100000000’dır
      • URL’yi kısa tutmak için bu ofset kaldırılır, ancak llvm-symbolizer ile yeniden eşlemeden önce tekrar eklenmesi gerekir
  • Linux ve macOS’ta ilk modül ana uygulama ikilisini gösterir
  • Windows’ta modül adı ile peb.ProcessParameters.ImagePathName karşılaştırılarak ana ikili olup olmadığı anlaşılabilir
  • Bun, debug sembollerini yerelde indirip ayrıştırmak yerine demangling işlemini sunucuya bırakıyor
    • Sunucu debug sembollerini önbelleğe alabilir
    • Yığın izini birkaç saniye içinde demangle edebilir
    • Aynı zamanda yeni bir GitHub issue açan bağlantı görevini görür

bun.report URL yapısı

  • bun.report URL’sinde şu bilgiler kodlanır
    • Platform: Platformu gösteren tek harf. Örneğin w, x86_64 Windows’u; M, aarch64 macOS’u gösterir
    • Subcommand: bun test, bun install, bun run gibi alt komutları gösteren tek harf
    • Commit SHA: Geçerli Bun sürümünün commit SHA’sı; daha sonra debug sembollerini almak için kullanılır
    • Feature Flags: Çökmeden önce kullanılan API’leri ve özellikleri gösteren işaretler
    • Stack Trace Addresses: Önceki adımda hesaplanan adresler
    • Crash Type: Çökme tipini gösteren tek harf
    • Crash Message: Çökme tipine göre biçimi değişen mesaj
  • URL’deki sürüm numarası, gerçek işleme için değil insanlar tarafından okunabilmesi için gösterilir
  • Yalnızca bu bilgilerle bile bazı çökme özellikleri elle anlaşılabilir
    • w tanımlayıcısı görüldüğünde bunun bir Windows çökmesi olduğu hızla anlaşılır
    • Dizenin sonundaki A2 görüldüğünde segmentasyon hatası tanımlanabilir

Kısa URL için VLQ kodlama

  • Yığın izi adresleri, URL’yi kısa tutmak için base64 Variable Length Quantity(VLQ) sayıları olarak kodlanır
  • VLQ, küçük sayıları daha az karakterle ifade ederken büyük sayıları da kodlayabilir
  • JavaScript source map’lerinde satır numaralarını saklarken de aynı teknik kullanılır
  • Sunucu, VLQ değerlerini tekrar göreli adreslere çözer; commit hash’i ve platformu kullanarak debug sembollerini indirir ve llvm-symbolizer ile fonksiyon adlarını demangle eder
  • Örnek çökmede, Windows’un modül çözücü kodunun bir parçası olan dirInfoCachedMaybeLog içinde bir assertion’ın başarısız olduğu ortaya çıkar

Özellik bayraklarının kodlanması

  • URL ayrıca 64 bit tamsayı da kodlar; her bit, Bun’ın belirli bir özelliğinin kullanılıp kullanılmadığına karşılık gelir
  • Bu bayraklar, hangi API’lerin ve sistemlerin çökmeye etki etmiş olabileceğine dair ipucu verir
    • .env dosyası otomatik yüklendiğinde dotenv özelliği ayarlanır
    • fetch() kullanıldığında fetch özelliği ayarlanır
  • Bun, özellik kullanımını global değişken konteyneriyle izler ve her API’nin içinde ilgili sayıyı artırarak kullanımı işaretler
  • Zig’in derleme zamanı metaprogramlamasını kullanarak özellik listesini dolaşır ve her özellik için 1 bit kullanan packed struct’ı dinamik olarak oluşturur
  • inline for kullanıldığında özellik listesi derleme zamanında dolaşılır, ancak gerçek bit ayarlama çalışma zamanında yapılabilir
  • Mevcut Features struct’ına yeni bir özellik eklendiğinde çökme raporlayıcısı da tekrar tekrar kod yazmadan bunu işler
  • Aynı yöntem C veya Rust makrolarıyla da mümkün olsa da, Bun tarafındaki uygulamada Zig comptime daha basit ve okunabilir bir yol olarak kullanılmıştır

Core dump ile farkı

  • Core dump çok daha fazla bilgi içerir; ancak boyutu büyüktür, faydalı olması için debug sembolleri gerekir ve hassas ya da gizli birçok bilgi içerebilir
  • Bun’ın yeni raporlama yöntemi JavaScript/TypeScript kaynak kodu, ortam değişkenleri ve diğer hassas bilgilerin gönderilmesini önler
  • Varsayılan olarak her şeyi göndermek yerine, sorunun tanılanması için gerekli olma olasılığı yüksek Zig/C++ yığın izi ve birkaç ayrıntı gönderilir
  • Ek bilgi gerekirse kullanıcıdan ayrıca istenebilir
  • Önceden olduğu gibi yalnızca eşlenmemiş adreslerin kaldığı duruma kıyasla, Bun ekibinin çökmeleri tanılaması kolaylaşır

Demo

  • Çökme raporlayıcısını test etmek için küçük bir web uygulaması bun.report adresinde sunuluyor
  • Herhangi bir çökme raporu URL’sinin sonuna /view eklerseniz ilgili web uygulaması ekranına gidilir

1 yorum

 
GN⁺ 2024-04-27
Hacker News yorumları
  • Genel stack trace yerine bu yöntemin kullanılmasının nedeni birkaç MB’lık debug sembollerini dağıtmamaksa, daha iyi bir seçenek olan debug tablolarına yalnızca fonksiyon adlarını koyma yolunu göz ardı etmiş gibi görünüyor.
    Stack trace’i görmek için bir web servisi kullanmak zorunda kalmaktan çok daha iyi bir yöntem ve bu yalnızca teoride değil, LLVM’de zaten uygulanmış durumda: https://clang.llvm.org/docs/UsersManual.html#cmdoption-gline...

    • Genel stack trace yerine bu yöntemi kullanmanın asıl nedeni debug sembollerinin boyutu değil, GitHub issue’suna crash report koyacak kadar sabırlı insanın çok az olması.
      Tek bir URL ile gerekli içeriği neredeyse otomatik doldurursanız bu yeterince kolaylaşır; geliştiricilerin gerçekten crash report göndermesi de ancak böyle olur. Boyut da önemliydi, kullanıcıya bir dezavantaj yaratmamaya çalıştılar; ama asıl mesele tüm süreci çok kolay hale getirmek.
    • “Daha iyi seçeneği göz ardı etmişler”, “bariz şekilde daha iyi” gibi ifadeler biraz kesin hükümlü geliyor. Muhtemelen böyle bir olasılığın farkındaydılar.
      Bu kullanım senaryosunda stack trace’i bir web servisiyle görmek zorunda olmak büyük bir dezavantaj değil. Frontend JavaScript bundle’ını obfuscate/minify edip source map’i Sentry’ye yükledikten sonra, kullanıcının tarayıcısından gelen stack trace’i Sentry’de geri çözerek görmeye çok benziyor. Kullanıcı zaten o stack trace’i görmeyecek; benim de Sentry ile bakmam rahatsız edici değil. Öyle olmasaydı zaten hiç göremeyecektim.
    • Tartışmanın bağlamını ve hangi ödünleşimlerin yapıldığını bilmeden “bariz”, “sadece” başka bir şey yapılması gerektiğini söyleyerek eleştirmek biraz kibirli duyuluyor.
      Alternatif önermenin birçok yolu var.
    • macOS/iOS’ta Mach-O binary’sine yalnızca LC_FUNCTION_STARTS bölümünü koyup dağıtmak da mümkün.
      Bu platformlarda sistem kütüphanelerinin fonksiyon adlarını, tam debug sembolleri olmadan da sembolleştirmenin bulma yöntemi bu.
    • Yine de oldukça büyüyebilir. Şahsen buna değer olduğunu düşünüyorum ve genelde koşulsuz ekliyorum, ama çoğu yazılım bunu yapmıyor.
  • Harika ve çok yaratıcı. Birçok projenin bu yöntemi izlemesi iyi olur. Temel nokta, çalıştırılabilir dosya/paylaşımlı nesne bazında göreli program sayacı ile stack trace bırakmak.
    Bildiğim kadarıyla Bun statik linklenmiş; dinamik linklenen bir sistemde ise normalleştirilmiş her program sayacının başına küçük sayısal bir paylaşımlı nesne ID’si eklemek gerekir.

    • Tamamen yeni bir yöntem değil. Oyunlar gibi sembollerin dağıtılamadığı ortamlarda, oyuncunun PC’sinde crash olduğunda sık kullanılır.
      Örneğin Unreal Engine crash reporter yıllardır böyle basit bir format gönderebiliyor ve her stack frame’den oldukça doğru fonksiyon/satır numarası geri çıkarabiliyor. Yine de genelde stack değişkenleri de varsa ne olduğuna dair ek ipuçları verdiği için minidump daha çok tercih ediliyor.
  • Microsoft bu tür şeylerde gerçekten iyi. SQL Server’da kişisel verileri çıkarılmış minidump kullanılıyordu; çok küçüktü ve son derece yararlıydı.
    O zaman bile, 15 yıl önce, production SQL Server’ın tam dump’ı taşınması zor olacak kadar devasa bir dosyaydı.

    • Merak ediyorum; Microsoft’un iç servisleri için miydi, yoksa müşteri dağıtım ortamları için mi? İkincisiyse neyin kişisel olarak tanımlanabilir bilgi olduğunu nasıl biliyorlardı?
  • Zig ile ilgili ilk tweet’i gördükten sonra birkaç yıl Bun’ı takip ettim, yakın zamanda kullanmaya başladım; ekstra bir uğraş olmadan gayet iyi çalışıyor.

  • Bun oldukça çekici. Birkaç küçük örnek projede denedim; hızı iyi ve paket yönetimi ile JavaScript runtime’ını birleştirmiş olması hoşuma gidiyor.
    Ancak ciddi projelerimin çoğunda Dependabot kullanıyorum. Dependabot’un Bun desteğinin üzerinde çalışıldığını ya da en azından bazı depo issue’larında tartışıldığını biliyorum; bu yüzden destek yayınlanana kadar kullanmayı erteliyorum.

    • Biz de geçişten önce Dependabot desteği olmaması nedeniyle tereddüt ettik, ama Renovate’in Bun ile çalıştığını ve şimdilik yeterli bir alternatif olduğunu gördük.
      Hiç pişman değiliz. Hızlanan kısımların birikerek sağladığı tasarruf ve geliştirici deneyimindeki büyük iyileşme, beklediğimiz kadar değerli.
  • Buna ne kadar çok özen gösterildiğini fark edecek çok kişi olmayacak. Bun ekibinin craft’ına ne kadar önem verdiğini göstermesi güzel.

  • Bun şaşırtıcı, ama yakın zamanda Fastify ile HTTP/2 sunucusu kurmaya çalıştığımda olmadı.
    node:http2 createServer is not yet implemented in Bun hatası çıktı ve mesajın işaret ettiği issue aslında HTTP/2 istemci desteğiyle ilgili. İstemci desteği v1.0.13’te zaten yayınlandı: https://bun.sh/blog/bun-v1.0.13#http2-client-support
    NotImplementedError mesajı sunucu tarafındaki issue’ya işaret edecek şekilde değiştirilmeli: https://github.com/oven-sh/bun/issues/8823
    HTTP/2 sunucu desteği en üst sıralardaki özellik isteklerinden biri: https://github.com/oven-sh/bun/issues?q=is%3Aissue+is%3Aopen...
    Bu özellik geldiğinde çok daha fazla kişi Bun’a geçebilir gibi görünüyor.

    • Bun’ın şu anki durumu bu. Bir uygulamanın tamamlanmasını beklersiniz; bittikten sonra başka bir API implementasyonuna daha ihtiyaç olduğunu fark edersiniz, yine beklersiniz; bu kez gelir ama çeşitli edge case’lerde crash olur, sonra tekrar beklersiniz; döngü böyle sürer.
      Bun yaşam döngüsünde hâlâ çok erken bir aşamada. Yine de projeden beklentim yüksek.
  • Gerçekten Bun kullanan var mı merak ediyorum. Beklendiği kadar iyi mi?

    • Henüz production’da kullanmadım, ama tek seferlik script’ler ve yan projeler için çok iyiydi.
      TypeScript Node ortamında ts-node, ts-jest, ESM desteği, top-level await vb. ayarlamak gereğinden fazla zahmetli. Son Node sürümleri bazı sıkıntıları azaltsa da bun init kadar basit değil. Bun shell API’sini de keyifle kullanıyorum: https://bun.sh/blog/the-bun-shell
    • REPL’e ihtiyacınız yoksa ya da native modül kullanmayı planlamıyorsanız iyi. REPL var deniyor ama her güncellendiğinde hep 6 saniyeden fazla gecikiyor; bu da çok can sıkıcı.
      Hata mesajları da Node’a kıyasla epey kötü. Bir süre kullandım ama bugünlerde Node’a —loader tsx eklemek istediğim her şeyi sağlıyor ve dezavantajı da yok. Basit bir sunucu, örneğin WebSocket kullanan ve native modüle gerek duymayacağınızdan emin olduğunuz bir şeyse Bun değerlendirilebilir. Hatta böyle birkaç servisi çalıştırıyorum.
    • 1.0 çıkar çıkmaz kullanmaya başladım ve geri dönmedim. Şimdi tüm projelerime uyguluyorum.
    • Yaklaşık 15 bin satırlık bir programlama dili projesinde geliştirme ve test runner olarak kullanıyorum; şimdiye kadar Bun’a özgü bir sorun yaşamadım.
      Anında başlama hızı hâlâ şaşırtıyor.
    • Son zamanlarda kullanıyorum ve çok iyi. TypeScript derlemeyi düşünmek zorunda kalmamak gibi yaşam kalitesi iyileştirmeleri gerçekten rahat ve hızlı da.
      Hâlâ eksikleri var, ama benim için şimdiden Node’dan daha iyi.
  • Bu yazı aynı zamanda harika bir Zig vaka çalışması gibi hissettiriyor. İlginç.

  • Bun, REPL’i kullanmadan önce 37 paket indirmek zorunda. İnternet yoksa REPL de yok.
    bun repl çalıştırıldığında bun-repl paket manifest’inin indirilemediğine dair hata veriyor. Büyük bir sorun değil, ama tek bir çalıştırılabilir dosyayı PATH’e koyunca kurulum yapmadan hemen çalışmasını bekliyordum ve buna bayağı heveslenmiştim.

    • REPL implementasyonuna henüz öncelik verilmedi. Mevcut REPL, topluluk tarafından uygulanmış bun-repl npm paketi.
      İçeride bun repl, bunx bun-repl ile aynı işi yapıyor.