- 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
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-initgibi, başlatılmamış değişkenleri belirli bir değerle ya da rastgele bir değerle başlatan çeşitli derleyici seçenekleri varAma 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
Hata ayıklama amacıyla bunu yapan araçlar var ama o modda program çok daha yavaş çalışıyor
Muhtemelen Linux çekirdeği bakımcılarının kullanıcı alanını asla bozmamakta ısrar etmesinin sebeplerinden biri de bu
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
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
scanfdö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üyorsscanfdönüş değerini kontrol etmesen bile varsayılan bir uyarı yokKüçük bir örnekte
g++ -Wall -Wextra -Wunused-resultversen de uyarı çıkmıyorAma 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 zorundaBu bug’ı yakalayacak genel bir statik analiz yöntemi var gibi görünmüyor
Yine de
scanfiç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 duruyorBö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
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
Bu Windows sürümünde critical section lock/unlock uygulamasında tam olarak neyin değiştiğini daha çok merak ediyorum
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
whiledöngüsü yazılmış gibi hissettiriyorAma
sscanfile JSON parse edip GTA5 yükleme süresini 5 dakika uzatabildiklerini görünce beklentim çok yüksek değilDerleyicinin 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 çıkarfmod’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ı