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
  • Şık yazdırma türleri
  • Kaynak dillerdeki ifadeleri değerlendirme
  • ...ve çok daha fazlası!

Bugün, vadedilen özelliklerin hayata geçtiğini ve Emscripten ve Chrome Geliştirici Araçları ekiplerinin bu yıl, özellikle de C ve C++ uygulamalarında 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 Geliştirici Araçları ile entegre olan ve WebAssembly dosyasında kodlanmış tüm hata ayıklama bilgilerini anlamlandırmasına yardımcı olan bir yardımcı uzantıya da ihtiyacımız olacak. 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; seçeneğinin nasıl etkinleştirileceğini gösteren Kaynaklar panelinin ekran görüntüsü

Varsayılan olarak Emscripten tarafından oluşturulan birleştirici kodda durur, ancak sağda, hatanın yığın izlemeyi temsil eden Çağrı Grubu görünümünü görebilir ve abort komutunu çağıran orijinal C satırına gidebilirsiniz:

Geliştirici Araçları &quot;assert_less&quot; işlevinde duraklatıldı ve kapsam görünümünde &quot;x&quot; ile &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++ kodunu kullanarak 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 kesirli şekli görebiliyorum:

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 ayrılma noktası ayarlayalım ve biraz daha ileri atlamak için yürütme işlemine devam edelim.

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

Bu noktada palette aracımız rastgele renklerle dolduruldu. Hem diziyi hem de bağımsız SDL_Color yapılarını genişletip her şeyin iyi göründüğünü doğrulamak için bileşenlerini inceleyebiliriz (örneğin, "alfa" kanalı her zaman tam opaklığa ayarlanır). Benzer şekilde, center değişkeninde depolanan karmaşık sayının reel 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ütmeye birkaç kez devam edelim. Kapsam görünümüne tekrar bakarak, değişken adını izleme listesine ekleyerek, konsolda değerlendirerek veya fareyle kaynak kodda değişkenin üzerine gelerek iç x öğesinin 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 bizim için önceden oluşturulmuş bir SDL kitaplığı sunmasını istedik. Bu kitaplığı kendi kaynağından derlemek yerine şu anda hata ayıklayıcının ilişkili kaynakları bulmasının bir yolu yok. 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 olabilir ve çoğu Web geliştiricisinin hiçbir zaman uğraşması gerekmeyecektir. Ancak zaman zaman, hata ayıklama bilgileri olmadan oluşturulmuş bir kitaplıkta hata ayıklamak isteyebilirsiniz. Bu, kontrolünüzün olmadığı üçüncü bir taraf kitaplığı veya yalnızca üretim aşamasında ortaya çıkan hatalardan biriyle karşılaştığınız için 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, bazı basit senaryolarda işe yaradı ancak genişletme açısından özellikle uygun değildi ve verilerin bayt değerleri dışındaki biçimlerde yeniden yorumlanmasına 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ıklamayı 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ıran ve farklı işlevlerde harcanan sürenin ayrıntılı bir dökümünü sunan Geliştirici Araçları Performans panelini kullanmanız gerekir:

Çeşitli Wasm işlevlerinin gösterildiği profil oluşturma paneli

Alternatif olarak, uygulamanızı Geliştirici Araçları kapalıyken çalıştırabilir ve işiniz bittiğinde Konsolu incelemek için bu araçları 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 derleme sunucusunda derleme yaparken, derleme sırasında kullanılan kaynak dosyalarına giden yolların, Chrome Geliştirici Araçları'nın çalıştığı kendi dosya sisteminizdeki 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. Sonuç olarak, 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 etmek için...

Vay canına, bir sürü yeni özellik sunduk.

Tüm bu yeni entegrasyonlarla Chrome Geliştirici Araçları, yalnızca JavaScript için değil, C ve C++ uygulamaları için de geçerli, güçlü ve kullanışlı bir hata ayıklayıcı haline gelir. Böylece, ç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çimlendiricileri 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

Chrome Canary, Yeni geliştirilenler veya Beta'yı varsayılan geliştirme tarayıcınız olarak kullanabilirsiniz. Bu önizleme kanalları, en son DevTools özelliklerine erişmenizi sağlar, en yeni web platformu API'lerini test etmenize olanak tanır 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 özellikleri, güncellemeleri veya Geliştirici Araçları ile ilgili diğer konuları görüşmek için aşağıdaki seçenekleri kullanın.