1 puan yazan GN⁺ 2025-04-24 | 1 yorum | WhatsApp'ta paylaş
  • Windows 11 24H2'de Skimmer deniz uçağının kaybolması veya spawn olduktan hemen sonra oyuncunun anormal derecede yüksek bir irtifaya fırlaması sorunu yeniden üretildi; neden işletim sistemi değil, oyunun içindeki eski bir veri işleme hatasıydı
  • Skimmer'ın vehicles.ide satırında uçak için gereken iki tekerlek ölçeği değeri eksikti, ancak CFileLoader::LoadVehicleObject sscanf dönüş değerini kontrol etmediği için ilklendirilmemiş yerel değişkenleri olduğu gibi kullandı
  • Eski Windows ortamlarında hemen önceki araç TopFun'ın 0.7 tekerlek ölçeği değeri tesadüfen stack üzerinde kaldığı için Skimmer normalmiş gibi görünüyordu; ancak Windows 11 24H2'de LeaveCriticalSectionın stack kullanımının değişmesiyle bu tesadüf bozuldu
  • Hatalı tekerlek ölçeği süspansiyon hesaplamalarını ve çarpışma kutusunun Z koordinatını bozdu; bu da oluşturulma yüksekliği ile pervane hızı hesaplarına kadar yayılarak kamera konumu anomalisi, burn-in efekti ve SilentPatch ortamında döngünün kilitlenmesine yol açtı
  • Çözüm, vehicles.ide içindeki Skimmer satırına -1, 0.7, 0.7, -1 eklemek veya bir sonraki SilentPatch hotfix'ini uygulamak; girdi verisi doğrulaması ve derleyici uyarılarının yönetimi uzun vadeli uyumluluğu doğrudan etkiliyor

Windows 11 24H2'de ortaya çıkan Skimmer belirtileri

  • SilentPatch issue tracker'ına, Windows 11 24H2 güncellemesinden sonra Skimmer uçağının oyundan tamamen kaybolduğuna dair rapor eklendi
    • Trainer ile de spawn edilemiyordu ve normal spawn konumunda da bulunamıyordu
    • Hem mod içeren oyun kurulumunda hem de yalnızca SilentPatch uygulanmış vanilla kopyada yeniden üretilebildi
  • GTAForums'ta da 2024 Kasım ayından itibaren aynı sorun bildirildi; bazı kullanıcılar SilentPatch'ten şüphelense de tamamen modsuz oyunda da aynı durum görüldü
  • Windows 10 22H2 ve Windows 11 23H2'de Skimmer normal şekilde spawn oluyordu; Windows 11 24H2 kullanıcıları ise aynı hatayı yaşıyordu
  • 24H2 sanal makinesinde uzaktan debug sonucu, diğer uçaklar ve tekneler normalken yalnızca Skimmer'ın kaybolduğu görüldü

Anormal irtifa ve bitmeyen pervane döngüsü

  • Skimmer script ile zorla oluşturulup CJ içine bindirildiğinde oyuncu 1.0287648030984853e+0031 m, yani yaklaşık 10.3 nonillion metre yüksekliğe fırlıyor
  • SilentPatch kuruluysa oyun, oyuncuyu yukarı fırlattıktan hemen sonra bir döngüye girip donuyor
  • SilentPatch yoksa oyun donmuyor, ancak kameranın sonsuza yakın bir konuma gitmesiyle oluşan meşhur burn-in efekti ortaya çıkıyor
  • Donmanın olduğu nokta, CPlane::PreRender içindeki rotor bıçağı açısını normalize eden döngüydü
    • m_fBladeSpeed değeri 3.73340132e+29 seviyesine çıkıyor
    • 6.2831855 tekrar tekrar çıkarılsa da kayan nokta gösterimi nedeniyle değer değişmediği için döngü sona ermiyor
  • Pervane hızı uçağın irtifasıyla orantılı bir değerden türetildiğinden, bu durum Skimmer'ın en baştan anormal derecede yüksek bir konumda oluşturulduğuna işaret ediyor

Çarpışma kutusunu bozan süspansiyon hesaplaması

  • Script oluşturma fonksiyonu CCarCtrl::CreateCarForScript, kendisine verilen Z koordinatına GetDistanceFromCentreOfMassToBaseOfModel sonucunu ekliyor
  • Skimmer'ın çarpışma kutusu incelendiğinde bbox.sup.z değerinin -4.30747210e+33 gibi anlamsız bir değere bozulduğu görüldü
  • Veri breakpoint'i ile izleme sonucunda, ilk yükleme anındaki çarpışma kutusu değerinin normal olduğu anlaşıldı
    • Başlangıçtaki bbox.sup.z değeri -2.21952772 idi
    • Sonrasında araç ilk kez spawn edildiğinde SetupSuspensionLines, süspansiyon yüksekliğini yansıtarak çarpışma kutusunun Z koordinatını güncelliyor
  • Sorun, süspansiyon çizgisi hesaplamasına giren girdilerden birindeydi
    • Hesaplamada handling.cfg içindeki süspansiyon üst-alt sınırları ve vehicles.ide içindeki tekerlek ölçeği kullanılıyor
    • Skimmer'ın handling.cfg değerleri diğer uçaklardan büyük ölçüde farklı değildi

Skimmer'ın kısa vehicles.ide satırı

  • Skimmer'ın vehicles.ide tanımı diğer uçaklara göre daha kısa ve son 4 parametre eksik
  • Eksik değerlerden 2'si ön ve arka tekerlek ölçeği
  • Teknelerde bu değerler olmasa da sorun çıkmıyor, ancak Skimmer uçaklar arasında bu parametreleri atlayan tek örnek
  • Görünüşe göre Skimmer, Vice City'de tekne olarak tanımlanmışken San Andreas'ta uçağa dönüştürülürken yeni gereken parametreler eklenmedi
  • Eksik parametreler geri eklendiğinde Skimmer normal çalışıyor

sscanf dönüş değerini kontrol etmeyen loader

  • CFileLoader::LoadVehicleObject, vehicles.ide içindeki bir satırı sscanf ile parse ederken tüm parametrelerin her zaman mevcut olduğunu varsayıyor
  • Bu fonksiyon sscanf dönüş değerini kontrol etmiyor ve son parametrelerin çoğu için varsayılan değer de vermiyor
    • wheelModelID ilklendirilmiyor
    • frontWheelScale ve rearWheelScale de ilklendirilmiyor
    • Yalnızca wheelUpgradeClass -1 olarak ilklendiriliyor
  • Skimmer gibi değeri eksik satırlarda tekerlek ölçeği değişkenleri ilklendirilmemiş durumda kalıyor ve bu değer araç verisine yayılıyor
  • SilentPatch düzeltmesi, sscanf çağrısını sararak son 4 değer için varsayılanlar sağlaması şeklinde
    • wheelModelID = -1
    • frontWheelSize = 0.7f
    • rearWheelSize = 0.7f
    • wheelUpgradeClass = -1
  • Düzeltme commit'i SilentPatch deposuna yansıtıldı

20 yıl boyunca gizli kalmasının nedeni

  • San Andreas statik olarak derlenmiş CRT kullandığı için, Windows'un CRT düzeyindeki hotfix'lerinin sscanf davranışını değiştirmiş olması söz konusu değil
  • Windows 10'da Skimmer parse edilmeden hemen önceki yerel değişken konumunda 0.7 değeri kalıyordu
    • Bu değer, Skimmer'dan hemen önce tanımlanan TopFun'ın tekerlek ölçeğiyle aynıydı
    • TopFun satırında -1, 0.7, 0.7, -1 bulunuyor
  • vehicles.ide sırayla okunuyor ve her satır için LoadVehicleObject çağrılıyor
  • Windows 10'da LoadVehicleObject çağrıları arasında ilgili stack konumu üzerine yazılmadığı için Skimmer tesadüfen TopFun'ın tekerlek ölçeğini miras alıyordu
  • Windows 11 24H2'de bir sonraki satırı okuma sürecinde fgets içindeki LeaveCriticalSection daha fazla stack alanı kullandı ve böylece geride kalan değerlerin üstüne yazıldı

Windows 11 24H2 yalnızca tetikleyiciydi

  • Dahili WinAPI fonksiyonlarının stack kullanma biçimi sözleşmeyle garanti edilen bir davranış değil ve önceden haber verilmeden değişebilir
  • Windows 11 24H2, yalnızca oyunun bağımlı olduğu tesadüfi stack kalıntısını ortadan kaldırdı; asıl neden oyundaki tanımsız davranış
  • Windows 10'da da tekerlek ölçeğinin hemen yanındaki yerel değişken zaten LeaveCriticalSection tarafından üzerine yazılabilecek durumdaydı; yani oyun bu hatayla yıllar önce de karşılaşabilecek durumdaydı
  • San Andreas Windows 98'i de desteklediği için, bu hata en az 10'dan fazla Windows sürümü ve birçok Wine sürümünde tesadüfen görünmeden kaldı
  • Resmî 1.01 PC yamasında bu hata düzeltilmedi, ancak orijinal Xbox sürümünde varsayılan 1.0 değeri ekleyen bir düzeltme vardı
    • Steam 3.0, newsteam, RGL Xbox kod dalı tabanlı olduğu için bu düzeltmeyi miras aldı
    • War Drum Studios'un Android, X360, PS3 sürümleri ve Definitive Edition da etkileniyor

SilentPatch neden varsayılan olarak 0.7 seçti?

  • SilentPatch, Rockstar'ın Xbox düzeltmesindeki 1.0 yerine varsayılan tekerlek ölçeği olarak 0.7 kullanıyor
  • Bunun için üç gerekçe sunuluyor
    • PC sürümünde Skimmer bugüne kadar fiilen TopFun'ın 0.7 tekerlek ölçeğiyle çalışıyordu
    • Su üzerinde yüzen diğer tekne olmayan araçlar Sea Sparrow ve Vortex de 0.7 tekerlek ölçeği kullanıyor
    • Oyundaki birçok otomobilin tekerlek ölçeği de 0.7

Elle düzeltme yöntemi

  • Kod düzeltmesi bir sonraki SilentPatch hotfix'ine dahil edilecek
  • Hemen düzeltmek için San Andreas dizinindeki data\vehicles.ide dosyasını Not Defteri ile açıp 460, skimmer ile başlayan satırı değiştirmek yeterli
  • Değiştirilecek satır şu şekilde
460, 	skimmer,	skimmer, 	plane,		SEAPLANE,	SKIMMER,	null,	ignore,		5,	0,	0,		-1, 0.7, 0.7,		-1

Eski oyun uyumluluğunun bıraktığı ders

  • Bu sorun San Andreas'taki basit bir hataydı ve ilgili fonksiyon baştan beri doğru çalışabilecek bir kod değildi
  • Dahili uygulamanın stack yerleşimindeki değişimler bile, hatalı bir uygulama belirli bir davranışa tesadüfen bağımlıysa uyumluluk sorunlarına yol açabilir
  • Benzer bir örnek olarak, Windows 10'da bozulan Bully: Scholarship Edition da yanlış varsayımlara dayanırken işletim sistemi değişiklikleriyle sorun yaşamıştı
  • San Andreas'ın temel sorunu, eksik yapılandırma satırlarını elemeden geçiren girdi verisi doğrulamasının yokluğu idi
  • Bu kod büyük olasılıkla baştan beri derleyici uyarısı üretiyordu; uyarıları görmezden gelmek veya kapatmak, uzun süre gizli kalan hataların gerçek kullanıcı sorunlarına dönüşmesine neden olabilir

1 yorum

 
GN⁺ 2025-04-24
Hacker News yorumları
  • Bu tür yazılar ancak Raymond Chen’den beklenecek seviyede ve bu çok büyük bir övgü
    Bunun tam olarak neden böyle olduğunu daha derine inip ortaya çıkarmış olması sevindirici

  • Ben şahsen sözleşmede yer almayan davranışlar varsa bunların rastgeleleştirilmesi gerektiğini düşünüyorum
    Örneğin dil bir map dolaşma sırasını garanti etmiyorsa, sırayı bilerek rastgeleleştirmeli
    Aksi halde “çalıştığı sürece iyi gidip sonra bir gün bozulan” kırılgan kodlar ortaya çıkıyor

    • -ftrivial-auto-var-init gibi, başlatılmamış değişkenleri belirli bir değerle ya da rastgele bir değerle başlatan çeşitli derleyici seçenekleri var
      Ama her fonksiyon çağrısında tüm stack içeriğini rastgeleleştirmek ya da sıfırla doldurmak performansı korkunç etkiler, o yüzden genelde böyle yapılmıyor
    • Bu seviyede rastgeleleştirme fazla maliyetli
      Hata ayıklama amacıyla bunu yapan araçlar var ama o modda program çok daha yavaş çalışıyor
    • Sözleşme açısından, asıl yazıdaki şu ders de var: “Uyumluluk konusunda ilginç bir ders. Uygulamada bir bug varsa ve belirli bir davranışa istemeden bağımlı hale gelmişse, dahili uygulamadaki stack yerleşimi değişikliği bile uyumluluk etkisi yaratabilir”
      Muhtemelen Linux çekirdeği bakımcılarının kullanıcı alanını asla bozmamakta ısrar etmesinin sebeplerinden biri de bu
    • Hayır. https://www.hyrumslaw.com/ akılda tutulmalı
      Bir API’nin yeterince çok kullanıcısı varsa, sözleşmenin neyi vaat ettiği önemli olmaz; sistemin gözlemlenebilir her davranışına birileri bağımlı hale gelir
      Rastgeleleştirmeyi vaat edersen, birileri o rastgeleleştirmeye de bağımlı olacaktır
      Sonra onu da sonsuza kadar kaldıramaz hale gelirsin
    • C gibi dillerin avantajlarından biri, yalnızca seçtiğin özelliklerin bedelini ödüyor olman diye de görülebilir
      Kullanmadığın değişken başlatmaları gibi gereksiz ek yükleri zorunlu olarak ödemezsin
  • “Derleyici uyarılarını görmezden gelmeyin” kısmında, burada nasıl bir derleyici hatası bekleneceğini pek bilmiyorum
    En fazla scanf dönüş değerinin argüman sayısıyla uyumlu olup olmadığını kontrol etmemek olabilir mi? Onun dışında derleyicinin bilemeyeceği bir veri dosyası hatası gibi görünüyor

    • g++ 11.4 ile denersem sscanf dönüş değerini kontrol etmesen bile varsayılan bir uyarı yok
      Küçük bir örnekte g++ -Wall -Wextra -Wunused-result versen de uyarı çıkmıyor
    • Başlatılmamış belleğe erişmek tanımsız davranış olduğu için, sanitizer bunu yakalardı
    • İyi nokta. Okurken ben de belirsiz biçimde “başlatılmamış bellek kullanımı” uyarısının bunu yakalayacağını düşünmüştüm
      Ama satırın tamamı tek bir sscanf çağrısıyla parse edildiği için, derleyicinin statik analizi değerlerin artık başlatıldığını varsaymak zorunda
      Bu bug’ı yakalayacak genel bir statik analiz yöntemi var gibi görünmüyor
      Yine de scanf için özel bir uyarı yazılıp, önceden başlatılmış değerler geçirilmesi ya da dönüş değerinin kontrol edilmesi zorunlu kılınabilir gibi duruyor
  • Böyle derin teknik analiz yazıları okumak her zaman keyifli
    Yapay zeka çağında bu tür yazıların daha mı nadirleşeceğini, yoksa olmayacağını mı merak ediyorum

    • Daha nadir hale geleceğini sanmıyorum. Her zaman derine inen üst düzey mühendisler olacaktır
      Yapay zeka onların yerini almayacak; son 50 yılı aşkın yazılım geliştirme yenilikleri de alamadı
      Üst seviye programlama dilleriyle çalışan yüz binlerce, hatta belki milyonlarca geliştirici stack ile heap arasındaki farkı okulda kabaca öğrendikleri bir teori olarak biliyor; günlük işlerinde buna kafa yormak zorunda kalmadıkları için de ilgilenmiyorlar
    • Genel yazılım mühendisliği zanaatkârlıktan daha çok teknisyenliğe kayabilir ama bu tür yazılar kendi doğası gereği zanaatkâr işi bir üsluptan çıkıyor gibi
  • Bu Windows sürümünde critical section lock/unlock uygulamasında tam olarak neyin değiştiğini daha çok merak ediyorum

    • Kullanılan stack boyutunun ya da stack guard bölgesinin arttığı anlaşılıyor
  • Bu koddan rahatsız olan tek kişi ben miyim?
    while (this->m_fBladeAngle > 6.2831855) { this->m_fBladeAngle = this->m_fBladeAngle - 6.2831855; }
    Bölme yapmaya üşenip sonsuz döngüye girebilecek bir while döngüsü yazılmış gibi hissettiriyor

    • GTA geliştiricilerinin PlayStation 2 gibi ortamlarda kayan noktalı bölmenin daha yavaş olması yüzünden böyle hack’ler yaptığını düşünmek istiyorum
      Ama sscanf ile JSON parse edip GTA5 yükleme süresini 5 dakika uzatabildiklerini görünce beklentim çok yüksek değil
    • Büyük ihtimalle performans içindi. Çıkarma işlemi kayan noktalı bölmeden daha ucuz
      Derleyicinin bunu daha da optimize eden teknikleri de olabilir
      Bunun sonsuz döngüye girme ihtimali pratikte yok. Underflow olabilir ama bunun için açının zaten 2*pi’den küçük olması gerekir; o durumda da döngüden çıkar
    • Olasılık düşük ama değer küçükse bu döngü bölmeden daha hızlı olabilir
    • Gerçekten de öyle. Yazarı belli ki fmod’u hiç bilmiyormuş
  • Erişim sorunu yaşayanlar bu bağlantıyı kullanabilir
    https://web.archive.org/web/20250423144746/https://cookieplm...

  • C/C++ bildiğim için blogun başından itibaren kabaca ne olduğunu, yani başlatılmamış değişken sorunu olduğunu tahmin ettim
    Değişkenleri başlatmadan bırakmaya izin veren bir dil olması şaşırtıcı. Bu yüzden, bizzat gördüğüm üretim hataları dahil sayısız bug ortaya çıktı ve bunları yakalamak için çoğu zaman ek derleyici bayraklarına, statik analiz araçlarına ya da Valgrind gibi araçlara güvenmek gerekiyor
    Daha yeni diller varsayılan sıfır değeri kullanmak ya da kullanımdan önce başlatmayı zorunlu kılmak gibi farklı çözümler benimsese de insanlar dönüp dolaşıp yine C/C++’a geliyor

  • “Bütün bu bulgular bug’ın Windows 11 24H2 sorunu olmadığını kanıtlıyor. Dahili WinAPI işlevlerinin stack kullanımı gibi şeyler sözleşmenin parçası değildir ve önceden haber verilmeksizin her an değişebilir” kısmı bana yıllar önce okuduğum harika bir yazıyı hatırlattı
    Ana fikri, yeterince başarılı bir API’de özel API diye bir şeyin kalmadığıydı

    • O yazıyı bulup link verirsen iyi olur. Argümanı merak ettim
    • Bununla ilgili bir XKCD çizgi romanı olduğunu sanıyorum