Modern araçlarla WebAssembly hatalarını ayıklama

Ingvar Stepanyan
Ingvar Stepanyan

Geldiğimiz nokta

Chrome, bir yıl önce Chrome DevTools'da yerel WebAssembly hata ayıklama için ilk desteği duyurmuştu.

Temel adım desteğini gösterdik ve kaynak haritalar yerine DWARF bilgilerinin kullanımının gelecekte bize sunacağı fırsatlardan bahsettik:

  • Değişken adlarını çözümleme
  • Okunaklı hale getirme türleri
  • Kaynak dillerdeki ifadeleri değerlendirme
  • …ve daha fazlası!

Bugün, söz verdiğimiz özelliklerin hayata geçirildiğini ve Emscripten ile Chrome DevTools ekiplerinin bu yıl özellikle C ve C++ uygulamaları için kaydettiği ilerlemeyi göstermekten heyecan duyuyoruz.

Başlamadan önce, bunun yeni deneyimin beta sürümünün hâlâ olduğunu, tüm araçların en son sürümünü kendi sorumluluğunuzda kullanmanız gerektiğini ve herhangi bir sorunla karşılaşırsanız lütfen https://issues.chromium.org/issues/new?noWizard=true&template=0&component=1456350 adresinden bildirin.

Geçen seferkiyle aynı basit C örneğiyle başlayalım:

#include <stdlib.h>

void assert_less(int x, int y) {
  if (x >= y) {
    abort();
  }
}

int main() {
  assert_less(10, 20);
  assert_less(30, 20);
}

Derlemek için en son Emscripten'i kullanırız ve hata ayıklama bilgilerini dahil etmek üzere orijinal gönderide olduğu gibi bir -g işareti iletiriz:

emcc -g temp.c -o temp.html

Artık oluşturulan sayfayı bir yerel ana makine HTTP sunucusundan (ör. serve ile) yayınlayabilir ve en son Chrome Canary sürümünde açabiliriz.

Bu kez, Chrome DevTools ile entegre olan ve WebAssembly dosyasında kodlanmış tüm hata ayıklama bilgilerini anlamasına yardımcı olan bir yardımcı uzantıya da ihtiyacımız var. Lütfen şu bağlantıya giderek yükleyin: goo.gle/wasm-debugging-extension

DevTools Denemeler bölümünde WebAssembly hata ayıklama özelliğini de etkinleştirmeniz gerekir. Chrome Geliştirici Araçları'nı açın, DevTools bölmesinin sağ üst köşesindeki dişli () simgesini tıklayın, Deneysel paneline gidin ve WebAssembly Hata Ayıklama: DWARF desteğini etkinleştir'i işaretleyin.

Geliştirici Araçları ayarlarının Deneyimler bölmesi

Ayarlar'ı kapattığınızda DevTools, ayarları uygulamak için kendisini yeniden yüklemeyi önerir. Biz de bunu yapalım. Tek seferlik kurulum bu kadar.

Şimdi Kaynaklar paneline geri dönebilir, İstisnalarda duraklat'ı (⏸ simgesi) etkinleştirebilir, ardından Tespit edilen istisnalarda duraklat'ı işaretleyip sayfayı yeniden yükleyebiliriz. Geliştirici Araçları'nın bir istisnada duraklatıldığını göreceksiniz:

&quot;Yakalanan istisnalarda duraklat&quot;ın nasıl etkinleştirileceğini gösteren Kaynaklar panelinin ekran görüntüsü

Varsayılan olarak, Emscripten tarafından oluşturulan bir yapıştırma kodunda durur ancak sağ tarafta hatanın yığın izlemesini temsil eden bir Çağrı Yığını görünümü görebilirsiniz ve abort'yi çağıran orijinal C satırına gidebilirsiniz:

DevTools, &quot;assert_less&quot; işlevinde duraklatıldı ve Kapsam görünümünde &quot;x&quot; ve &quot;y&quot; değerlerini gösteriyor

Artık Kapsam görünümüne bakarak C/C++ kodundaki değişkenlerin orijinal adlarını ve değerlerini görebilirsiniz. Böylece $localN gibi bozuk adların ne anlama geldiğini ve yazdığınız kaynak kodla nasıl ilişkili olduğunu öğrenmek zorunda kalmazsınız.

Bu durum yalnızca tam sayılar gibi ilkel değerler için değil, yapı, sınıf, dizi vb. gibi karmaşık türler için de geçerlidir.

Zengin tür desteği

Bu noktaları açıklamak için daha karmaşık bir örneğe bakalım. Bu kez aşağıdaki C++ koduyla bir Mandelbrot fraktalı çizeceğiz:

#include <SDL2/SDL.h>
#include <complex>

int main() {
  // Init SDL.
  int width = 600, height = 600;
  SDL_Init(SDL_INIT_VIDEO);
  SDL_Window* window;
  SDL_Renderer* renderer;
  SDL_CreateWindowAndRenderer(width, height, SDL_WINDOW_OPENGL, &window,
                              &renderer);

  // Generate a palette with random colors.
  enum { MAX_ITER_COUNT = 256 };
  SDL_Color palette[MAX_ITER_COUNT];
  srand(time(0));
  for (int i = 0; i < MAX_ITER_COUNT; ++i) {
    palette[i] = {
        .r = (uint8_t)rand(),
        .g = (uint8_t)rand(),
        .b = (uint8_t)rand(),
        .a = 255,
    };
  }

  // Calculate and draw the Mandelbrot set.
  std::complex<double> center(0.5, 0.5);
  double scale = 4.0;
  for (int y = 0; y < height; y++) {
    for (int x = 0; x < width; x++) {
      std::complex<double> point((double)x / width, (double)y / height);
      std::complex<double> c = (point - center) * scale;
      std::complex<double> z(0, 0);
      int i = 0;
      for (; i < MAX_ITER_COUNT - 1; i++) {
        z = z * z + c;
        if (abs(z) > 2.0)
          break;
      }
      SDL_Color color = palette[i];
      SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, color.a);
      SDL_RenderDrawPoint(renderer, x, y);
    }
  }

  // Render everything we've drawn to the canvas.
  SDL_RenderPresent(renderer);

  // SDL_Quit();
}

Bu uygulamanın hâlâ oldukça küçük olduğunu (50 satır kod içeren tek bir dosya) görebilirsiniz. Ancak bu sefer grafikler için SDL kitaplığı ve C++ standart kitaplığındaki karmaşık sayılar gibi bazı harici API'leri de kullanıyorum.

Hata ayıklama bilgilerini içermesi için yukarıdakiyle aynı -g işaretiyle derleyeceğim. Ayrıca Emscripten'den SDL2 kitaplığını sağlamasını ve rastgele boyutlu belleğe izin vermesini isteyeceğim:

emcc -g mandelbrot.cc -o mandelbrot.html \
     -s USE_SDL=2 \
     -s ALLOW_MEMORY_GROWTH=1

Oluşturulan sayfayı tarayıcıda ziyaret ettiğimde, rastgele renklerle güzel bir fraktalı şekil görüyorum:

Demo sayfası

Geliştirici Araçları'nı tekrar açtığımda orijinal C++ dosyasını görebiliyorum. Ancak bu sefer kodda hata yok (oh be!). Bunun yerine kodumuzun başına bir kesme noktası koyalım.

Sayfayı tekrar yüklediğimizde hata ayıklayıcı, C++ kaynağımızın tam içinde duraklar:

DevTools, &quot;SDL_Init&quot; çağrısında duraklatıldı

Tüm değişkenlerimizi sağ tarafta görebiliriz ancak şu anda yalnızca width ve height başlatılmıştır. Bu nedenle, incelenecek çok fazla şey yoktur.

Ana Mandelbrot döngümüzün içine başka bir kesme noktası ayarlayalım ve biraz ileri atlamak için yürütmeyi devam ettirelim.

Geliştirici Araçları, iç içe yerleştirilmiş döngüler içinde duraklatıldı

Bu noktada palette bazı rastgele renklerle doldurulmuştur. Hem diziyi hem de tek tek SDL_Color yapılarını genişletip her şeyin iyi göründüğünü doğrulamak için bileşenlerini inceleyebiliriz (örneğin, "alpha" kanalının her zaman tam opaklığa ayarlandığından emin olabiliriz). Benzer şekilde, center değişkeninde depolanan karmaşık sayının gerçek ve sanal bölümlerini genişletip kontrol edebiliriz.

Kapsam görünümü aracılığıyla erişmesi zor olan, derin iç içe yerleştirilmiş bir mülke erişmek istiyorsanız Console değerlendirmesini de kullanabilirsiniz. Ancak daha karmaşık C++ ifadelerinin henüz desteklenmediğini unutmayın.

&quot;palette[10].r&quot; ifadesinin sonucunu gösteren konsol paneli

Yürütmeyi birkaç kez devam ettirelim. Böylece, Kapsam görünümüne tekrar bakarak, değişken adını izleme listesine ekleyerek, konsolda değerlendirerek veya kaynak koddaki değişkenin üzerine gelerek iç x değerinin nasıl değiştiğini görebiliriz:

Kaynaktaki &quot;x&quot; değişkeninin &quot;3&quot; değerini gösteren ipucu

Buradan C++ ifadelerine adım atabilir veya adım atlayabilir ve diğer değişkenlerin nasıl değiştiğini gözlemleyebiliriz:

&quot;color&quot;, &quot;point&quot; ve diğer değişkenlerin değerlerini gösteren ipuçları ve kapsam görünümü

Hata ayıklama bilgileri mevcut olduğunda bu yöntemler mükemmel şekilde çalışır. Peki hata ayıklama seçenekleriyle derlenmemiş bir kodda hata ayıklama yapmak istersek ne olur?

Ham WebAssembly hata ayıklama

Örneğin, Emscripten'den kaynağından derlemek yerine bizim için önceden derlenmiş bir SDL kitaplığı sağlamasını istedik. Bu nedenle, en azından şu anda hata ayıklayıcının ilişkili kaynakları bulması mümkün değil. SDL_RenderDrawColor'e girmek için tekrar başlayalım:

&quot;mandelbrot.wasm&quot; dosyasının kaynak kodunun ayrıştırılmış görünümünü gösteren Geliştirici Araçları

Ham WebAssembly hata ayıklama deneyimine geri döndük.

Bu biraz korkutucu görünse de çoğu web geliştiricisinin hiç uğraşması gerekmeyecek bir konudur. Ancak bazen, hata ayıklama bilgileri olmadan oluşturulmuş bir kitaplıkta hata ayıklama yapmak isteyebilirsiniz. Bunun nedeni, üzerinde hiçbir kontrolünüzün olmadığı bir üçüncü taraf kitaplığı olması veya yalnızca üretimde ortaya çıkan hatalardan biriyle karşılaşmak olabilir.

Bu durumlarda yardımcı olmak için temel hata ayıklama deneyiminde de bazı iyileştirmeler yaptık.

Öncelikle, daha önce ham WebAssembly hata ayıklama özelliğini kullandıysanız artık tüm kod ayrıştırmasının tek bir dosyada gösterildiğini fark edebilirsiniz. Artık wasm-53834e3e/ wasm-53834e3e-7 Kaynaklar girişinin hangi işleve karşılık geldiğini tahmin etmeniz gerekmez.

Yeni ad oluşturma şeması

Demonte görünümündeki adları da iyileştirdik. Daha önce yalnızca sayısal dizinler veya işlevler söz konusu olduğunda hiç ad gösterilmiyordu.

Artık WebAssembly ad bölümündeki ipuçlarını, içe/dışa aktarma yollarını kullanarak ve diğer tüm yöntemler başarısız olursa $func123 gibi öğenin türüne ve dizine göre adları diğer disassemblör araçlarına benzer şekilde oluşturuyoruz. Yukarıdaki ekran görüntüsünde, bu durumun biraz daha okunaklı yığın izleme ve kod ayıklama bilgilerine nasıl yardımcı olduğunu görebilirsiniz.

Tür bilgisi olmadığında, ilkellerin dışındaki değerleri incelemek zor olabilir. Örneğin, işaretçiler normal tam sayılar olarak gösterilir ve bellekte arkalarında nelerin depolandığını bilmek mümkün olmaz.

Bellek denetimi

Daha önce, tek tek baytları aramak için yalnızca Kapsam görünümünde env.memory ile temsil edilen WebAssembly bellek nesnesini genişletebiliyordunuz. Bu yöntem bazı basit senaryolarda işe yaramasına rağmen genişletmek için pek uygun değildi ve verileri bayt değerleri dışındaki biçimlerde yeniden yorumlamaya izin vermiyordu. Bu konuda size yardımcı olacak yeni bir özellik de ekledik: doğrusal bellek denetleyicisi.

env.memory simgesini sağ tıkladığınızda Hafızayı incele adlı yeni bir seçenek görürsünüz:

Kapsam bölmesinde &quot;env.memory&quot; üzerinde &quot;Hafızayı İncele&quot; öğesini gösteren içerik menüsü

Tıklandığında, WebAssembly belleğini onaltılık ve ASCII görünümlerinde inceleyebileceğiniz, belirli adreslere gidebileceğiniz ve verileri farklı biçimlerde yorumlayabileceğiniz bir Bellek Denetleyicisi açılır:

Belleğin onaltılık ve ASCII görünümlerini gösteren DevTools&#39;daki Bellek Denetleyicisi bölmesi

Gelişmiş senaryolar ve uyarılar

WebAssembly kodunda profil oluşturma

Geliştirici Araçları'nı açtığınızda WebAssembly kodu, hata ayıklama özelliğini etkinleştirmek için optimize edilmemiş bir sürüme "indirilir". Bu sürüm çok daha yavaştır. Bu nedenle, DevTools açıkken console.time, performance.now ve diğer kod hız ölçüm yöntemlerine güvenemezsiniz. Elde ettiğiniz sayılar gerçek dünya performansını hiç yansıtmaz.

Bunun yerine, kodu tam hızda çalıştıracak ve farklı işlevlerde harcanan sürenin ayrıntılı dökümünü sunacak DevTools Performans panelini kullanmanız gerekir:

Çeşitli Wasm işlevlerini gösteren profil oluşturma paneli

Alternatif olarak, uygulamanızı Geliştirici Araçları kapalıyken çalıştırabilir ve Konsol'u incelemek için işlem tamamlandıktan sonra Geliştirici Araçları'nı açabilirsiniz.

Profil oluşturma senaryolarını gelecekte iyileştireceğiz ancak şimdilik bu konuda dikkatli olmanız gerekiyor. WebAssembly katmanlandırma senaryoları hakkında daha fazla bilgi edinmek isterseniz WebAssembly derleme ardışık düzeni ile ilgili dokümanlarımıza göz atın.

Farklı makinelerde (Docker / ana makine dahil) derleme ve hata ayıklama

Docker, sanal makine veya uzak bir derleme sunucusunda derleme yaparken, derleme sırasında kullanılan kaynak dosyaların yollarının, Chrome DevTools'un çalıştığı kendi dosya sisteminizde bulunan yollarla eşleşmediği durumlarla karşılaşabilirsiniz. Bu durumda, dosyalar Kaynaklar panelinde gösterilir ancak yüklenemez.

Bu sorunu düzeltmek için C/C++ uzantı seçeneklerine bir yol eşleme işlevi uyguladık. İsteğe bağlı yolları yeniden eşlemek ve DevTools'un kaynakları bulmasına yardımcı olmak için bu seçeneği kullanabilirsiniz.

Örneğin, ana makinenizdeki proje C:\src\my_project yolunun altındaysa ancak bu yolun /mnt/c/src/my_project olarak temsil edildiği bir Docker kapsayıcısında derlendiyse bu yolları ön ek olarak belirterek hata ayıklama sırasında yeniden eşleyebilirsiniz:

C/C++ hata ayıklama uzantısının Seçenekler sayfası

Eşleşen ilk ön ek "kazanır". Diğer C++ hata ayıklayıcılarını biliyorsanız bu seçenek, GDB'deki set substitute-path komutuna veya LLDB'deki target.source-map ayarına benzer.

Optimize edilmiş derlemelerde hata ayıklama

Diğer tüm dillerde olduğu gibi, optimizasyonlar devre dışı bırakılırsa hata ayıklama en iyi şekilde çalışır. Optimizasyonlar, işlevleri birbirine satır içi olarak yerleştirebilir, kodu yeniden sıralayabilir veya kod parçalarını tamamen kaldırabilir. Tüm bunların hata ayıklayıcıyı ve dolayısıyla kullanıcı olarak sizi şaşırtma olasılığı vardır.

Daha sınırlı bir hata ayıklama deneyimi sizi rahatsız etmiyorsa ve yine de optimize edilmiş bir derlemede hata ayıklama yapmak istiyorsanız işlev iç içe yerleştirme dışındaki optimizasyonların çoğu beklendiği gibi çalışır. Kalan sorunları gelecekte ele almayı planlıyoruz ancak şimdilik -O düzeyinde optimizasyonlarla derleme yaparken devre dışı bırakmak için lütfen -fno-inline kullanın. Örneğin:

emcc -g temp.c -o temp.html \
     -O3 -fno-inline

Hata ayıklama bilgilerini ayırma

Hata ayıklama bilgileri, kodunuz, tanımlanmış türler, değişkenler, işlevler, kapsamlar ve konumlar (hata ayıklayıcı için yararlı olabilecek her şey) hakkında birçok ayrıntıyı korur. Bu nedenle, genellikle kodun kendisinden daha büyük olabilir.

WebAssembly modülünün yükleme ve derleme işlemini hızlandırmak için bu hata ayıklama bilgilerini ayrı bir WebAssembly dosyasına ayırabilirsiniz. Emscripten'de bunu yapmak için istenen dosya adıyla birlikte bir -gseparate-dwarf=… işareti iletin:

emcc -g temp.c -o temp.html \
     -gseparate-dwarf=temp.debug.wasm

Bu durumda, ana uygulama yalnızca bir dosya adı temp.debug.wasm depolar ve yardımcı uzantı, DevTools'u açtığınızda dosyayı bulup yükleyebilir.

Yukarıda açıklanan optimizasyonlarla birlikte kullanıldığında bu özellik, uygulamanızın neredeyse optimize edilmiş üretim derlemelerini göndermek ve daha sonra yerel taraftaki bir dosyayla bu derlemelerde hata ayıklama yapmak için bile kullanılabilir. Bu durumda, uzantının yan dosyayı bulmasına yardımcı olmak için saklanan URL'yi de geçersiz kılmamız gerekir. Örneğin:

emcc -g temp.c -o temp.html \
     -O3 -fno-inline \
     -gseparate-dwarf=temp.debug.wasm \
     -s SEPARATE_DWARF_URL=file://[local path to temp.debug.wasm]

Devam edecek…

Vay be, ne kadar çok yeni özellik varmış.

Tüm bu yeni entegrasyonlarla Chrome Geliştirici Araçları, yalnızca JavaScript için değil, C ve C++ uygulamaları için de uygun, güçlü bir hata ayıklayıcı haline gelir. Bu sayede, çeşitli teknolojilerde geliştirilen uygulamaları paylaşılan, platformlar arası bir web'e taşımak hiç olmadığı kadar kolay hale gelir.

Ancak yolculuğumuz henüz sona ermedi. Bundan sonra üzerinde çalışacağımız bazı konular:

  • Hata ayıklama deneyimindeki pürüzleri giderme.
  • Özel tür biçimlendiriciler için destek eklendi.
  • WebAssembly uygulamaları için profil oluşturma özelliğinde iyileştirmeler üzerinde çalışıyoruz.
  • Kullanılmayan kodun bulunmasını kolaylaştırmak için kod kapsamı desteği eklendi.
  • Konsolda değerlendirme için ifadelere yönelik destek iyileştirildi.
  • Daha fazla dil için destek ekliyoruz.
  • …ve daha fazlası!

Bu sırada, mevcut beta sürümünü kendi kodunuzda deneyerek ve karşılaştığınız sorunları https://issues.chromium.org/issues/new?noWizard=true&template=0&component=1456350 adresinden bildirerek bize yardımcı olabilirsiniz.

Önizleme kanallarını indirme

Varsayılan geliştirme tarayıcınız olarak Chrome Canary, Yeni Geliştirilenler veya Beta sürümünü kullanabilirsiniz. Bu önizleme kanalları, en son DevTools özelliklerine erişmenize, en yeni web platformu API'lerini test etmenize ve sitenizdeki sorunları kullanıcılarınızdan önce bulmanıza yardımcı olur.

Chrome Geliştirici Araçları Ekibi ile iletişime geçme

Yeni özellikler, güncellemeler veya Geliştirici Araçları ile ilgili başka herhangi bir konu hakkında konuşmak için aşağıdaki seçenekleri kullanın.