Thread overview
Array Bounds ve Try Catch
Mar 06, 2013
Salih Dinçer
Mar 06, 2013
Salih Dinçer
March 06, 2013

Merhaba,

Geçen hafta Talha, dizilerin ilk elemanına işaretçi ile erişirken hızlı olduğunu, yasal bir şekilde köşeli parantez içinde [0] yazarak erişirken ise yavaş kaldığını tespit etmişti...

Sonra bunun assembly koduna baktık ve gördük ki:

Her ne yaparsak yapalım bu şekilde yasal kullanımlarda 'core.exception.RangeError' (assembly'de _D2as7__arrayZ) sınıfının denetlemesine ve/veya hata kodunu atmasına mağruz kalıyoruz. Madem öyle, aşağıdaki gibi kullandığımızda (nedense Exception'da yakalayamadım, Throwable kullandım) kodu kısaltmanız mümkün:

import std.stdio;

void main() {
   size_t[] dizi = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ];
   size_t sayaç;
   bool devam = true;

Alıntı:

>
>     do {
>       try {
>         dizi[sayaç++].writeln();
>       } catch (Throwable hata) {
>         devam = false;
>       }
>     } while(devam);
> }
> ```

>

Yukarıdaki kodun assembly karşılığı aşağıda; ama sadece döngü kümesi:

Alıntı:
> '
> L70: '; do {'
> 		mov	ESI,-0Ch[EBP]            '; ESI <- sayaç'
> 		inc	dword ptr -0Ch[EBP]      '; sayaç++'
> 		cmp	ESI,-014h[EBP]           '; CarryFlag == True ise taşma var!'
> 		jb	 L85
> 		mov	EAX,0Bh
> 		call	  _D2as7__arrayZ@PC32
> L85:
> 		mov	EDX,-010h[EBP]           '; ECX <- sayaç'
> 		mov	EAX,-014h[EBP]           '; EAX <- dizi.length'
> 		mov	EAX,[ESI*4][EDX]         '; EAX <- dizi[sayaç]'
> 		call	  _D3std5stdio14__T7writelnTkZ7writelnFkZv@PC32
> 		jmp short	L99
> 		mov	byte ptr -8[EBP],0       '; devam == false'
> L99: '; } while(devam);'
> 		cmp	byte ptr -8[EBP],0
> 		jne	L70'
>

Bu kodda, taşma olması durumunda **L99** etiketine atlayarak program göçmeden sonlanmasını sağlıyoruz. Eğer **try/catch** kümesini kaldırıp basit bir 'if()' koşulu ile benzer şeyi yaparsak şu değişiklikler oluyor:

Alıntı:
>
>
>
do {
    dizi[sayaç++].writeln();
    if(sayaç == dizi.length) devam = false;
} while(devam);
>

Önceki assembly koduna çok benzemekte!

Alıntı:

>

'
L70: '; do {'
mov ESI,-0Ch[EBP]
inc dword ptr -0Ch[EBP]
cmp ESI,-014h[EBP]
jb L85
mov EAX,0Bh
call _D2as7__arrayZ@PC32
L85:
mov EDX,-0Ch[EBP] '; EDX <- sayaç'
mov EAX,-014h[EBP] '; EAX <- dizi.length'
mov EAX,[ESI*4][EDX]
call _D3std5stdio14__T7writelnTkZ7writelnFkZv@PC32
mov ECX,-0Ch[EBP] '; ECX <- sayaç'
cmp ECX,-014h[EBP] '; ZeroFlag == False ise eşitler!'
jne L99
mov byte ptr -8[EBP],0 '; devam == false'
L99: '; } while(devam);'
cmp byte ptr -8[EBP],0
jne L70'

Peki sizce ilk kodda try/catch hatayı nasıl yakalıyor olabilir? Normalde hatayı atan alt yordam _D2as7__arrayZ, ekrana yazı yazan ise _D3std5stdio14__T7writelnTkZ7writelnFkZv harici işlevi. Bir şekilde 'jne L99' satırını atlıyor olmalı ki döngü sonlansın.

Başarılar...

--
[ Bu gönderi, http://ddili.org/forum'dan dönüştürülmüştür. ]

March 06, 2013

Ben de anlamıyorum ama eğer hatayı da kendin atarsan _d_throwc işlevinin çağrıldığını görüyorsun:

import std.stdio;

void foo(int i)
{
   auto hata = new Exception("hata");
   if (i == 0x1111) {
       throw hata;
   }
}

void main()
{
   try {
       foo(0x1111);

   } catch (Exception) {
       writeln("yakalandı");
   }
}

Anlaşılan, _d_throwc bir yerlere bazı bilgiler yazıyor (en azından hata nesnesinin nerede olduğu). Ek olarak, 'jmp short L99' çağrısını etkisiz hale getiriyor olmalı. Belki de instruction pointer'ı (RIP mı?) değiştirerek işlevden dönüldüğünde o jmp'ın atlanmasını sağlıyordur.

Ali

--
[ Bu gönderi, http://ddili.org/forum'dan dönüştürülmüştür. ]

March 06, 2013

Yanıt için teşekkür ederim çünkü ufkumu açtı...

Ama ben de anlamıyorum...:D

Şöyle ki; mekanizmayı çözmek için hata iletilerinin hafızandan nasıl çekildiğine baktım. Çünkü sıralı push komutları açıkcası ben işkillendirdi. Bir avcı gibi iz sürdüğümde _TMP'x' ('x' yerinde sayılar) verileri gördüm. Bunlar db komutu ile statik işlenmişl metinler ve içlerinde çok alakasız şeyler de var. Neyse ki en başta bizimkileri gördüm. Kısaca ifade edersem:
Alıntı:

>

_TMP1: as.d....
_TMP2: yakalandı.......
_TMP4: std.traits.Demangle!(uint).Demangle.std.complex.Complex!(real).Complex./usr/include/d/dmd/phobos/std/complex.d..
_TMP6: null this.......
_TMP7: +.......
_TMP8: i.......
: : :

Not: as.d burada programın ismidir. Konudan bağımsız hemen alttaki paragrafı okumasanız da olur...:)

İşte böyle gerekli/gereksiz şeylerle dolan küçük bir veritabanımız mevcutmuş. Örneğin hiç std.datetime'ı kulanamamıza rağmen 16, 18, 21, 23, 25 numaralarda "is not a valid 'hour' of the 'day'" hata iletisi kaydedilmiş. Kırmızı ile işaretlediğim sözcükler saniye/ay ile dakika/yıl anlamını verecek şekilde çeşitlenmiş. Demek ki bu hata iletileri ile karşılaşıldığı zaman en sade şekilde o bellek bölgesine gidilip ekrana yazılıyor. Yoksa daha az yer kaplayacak şekilde bu hata iletileri için algoritma yazılabilirdi. Defalarca gereksiz bilgi...:)

Ali hocamın yazdığı kodun assembly karşlığı ise şöyle:

Alıntı:

>

' mov EAX,01111h
call _foo_işlevi
jmp short SON
push dword ptr _TMP4@SYM32[01Bh]
push dword ptr _TMP4@SYM32[01Dh]
call _writeln_işlevi
SON:
xor EAX,EAX
pop EDI
pop ESI
pop EBX
leave
ret'

Not: İşlev ve etkiket isimleri anlamı arttırmak için değiştirilmiştir...

Görüldüğü gibi EAX üzerinde foo() işlevine parametre gönderiliyor. Onun da assembly karşlığı ise şöyle:

Alıntı:

>

' push EBP
mov EBP,ESP
sub ESP,8
** mov -4[EBP],EAX '; int i = 01111h'**
mov ECX,offset FLAT:_D9Exception7__ClassZ@SYM32
push ECX
call _d_newclass@PC32
** push dword ptr _TMP2@SYM32[0Ah]
push dword ptr _TMP2@SYM32[0Ch]**
push dword ptr _TMP4@SYM32[0Eh]
push dword ptr _TMP4@SYM32[010h]
push 5
push 0
call _D6object9Exception6__ctorMFNaNbNfAyaAyakC6object9ThrowableZC9Exception@PC32
mov -8[EBP],EAX '; EAX'ı nedense gereksiz bir şekilde yedekliyor!'
add ESP,4 '; Stack Pointer'ı neden 1 birim (4 byte) ileri alıyor?'
** cmp dword ptr -4[EBP],01111h ; 'i == 0x1111 mi?'
jne L4C**
push dword ptr -8[EBP]
call _d_throwc@PC32
L4C:
leave
ret'

Çok kafa karıştırıcı olabilir ama sadece koyu kısımlara dikkatinizi çekmek istiyorum. Çünkü _TMP2'de gereçekten de "yakalandı" iletisi mevcut. Ama "hata" foo() işlevi içinde "hata" ifadesi olması gerekiyordu. Diyelim ki derleyici, bu şekilde daha hızlı olacağını ön gördü. Peki "hata" dizgesi nerede? O da rodata şeklinde TMP1'in hemen üstünde ama nasıl oluyor da ona ulaşılıyor onu çözemedim.

Sevgiler, saygılar...

--
[ Bu gönderi, http://ddili.org/forum'dan dönüştürülmüştür. ]